fuchsia: Add support for FIDL/JS responses
Responses are always returned asynchronously as JS promises (the newly added zx.objectWaitOne() helper also returns a promise). The wait and response are serviced by libasync waits, and so must be cancelled when the v8 context is torn down. The (default) dispatcher must outlive any v8 contexts with which it is used. Bug: 883496 Change-Id: Ib8735c6ec4196d36b53929341d57e843abaeea3b Reviewed-on: https://chromium-review.googlesource.com/c/1240204 Commit-Queue: Scott Graham <scottmg@chromium.org> Reviewed-by: Wez <wez@chromium.org> Reviewed-by: Jeremy Roman <jbroman@chromium.org> Cr-Commit-Position: refs/heads/master@{#600238}
This commit is contained in:
gin
tools/fuchsia/fidlgen_js
@ -20,6 +20,7 @@ using v8::Maybe;
|
||||
using v8::MaybeLocal;
|
||||
using v8::Number;
|
||||
using v8::Object;
|
||||
using v8::Promise;
|
||||
using v8::String;
|
||||
using v8::Uint32;
|
||||
using v8::Value;
|
||||
@ -171,6 +172,20 @@ bool Converter<Local<Object>>::FromV8(Isolate* isolate,
|
||||
return true;
|
||||
}
|
||||
|
||||
Local<Value> Converter<Local<Promise>>::ToV8(Isolate* isolate,
|
||||
Local<Promise> val) {
|
||||
return val.As<Value>();
|
||||
}
|
||||
|
||||
bool Converter<Local<Promise>>::FromV8(Isolate* isolate,
|
||||
Local<Value> val,
|
||||
Local<Promise>* out) {
|
||||
if (!val->IsPromise())
|
||||
return false;
|
||||
*out = Local<Promise>::Cast(val);
|
||||
return true;
|
||||
}
|
||||
|
||||
Local<Value> Converter<Local<ArrayBuffer>>::ToV8(Isolate* isolate,
|
||||
Local<ArrayBuffer> val) {
|
||||
return val.As<Value>();
|
||||
|
@ -135,6 +135,15 @@ struct GIN_EXPORT Converter<v8::Local<v8::Object> > {
|
||||
v8::Local<v8::Object>* out);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct GIN_EXPORT Converter<v8::Local<v8::Promise>> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Promise> val);
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
v8::Local<v8::Promise>* out);
|
||||
};
|
||||
|
||||
template<>
|
||||
struct GIN_EXPORT Converter<v8::Local<v8::ArrayBuffer> > {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
|
@ -15,6 +15,7 @@ enum GinEmbedder {
|
||||
kEmbedderNativeGin,
|
||||
kEmbedderBlink,
|
||||
kEmbedderPDFium,
|
||||
kEmbedderFuchsia,
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
@ -42,7 +42,10 @@ static_library("runtime") {
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//base",
|
||||
"//gin",
|
||||
"//third_party/fuchsia-sdk:async",
|
||||
"//third_party/fuchsia-sdk:async_default",
|
||||
"//v8",
|
||||
]
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ function %(name)s() {}
|
||||
for x in method.maybe_request]
|
||||
if len(param_names):
|
||||
self.f.write('/**\n')
|
||||
# TODO(crbug.com/883496): Emit @param type comments here.
|
||||
# TODO(crbug.com/883496): Emit @param and @return type comments.
|
||||
self.f.write(' */\n')
|
||||
self.f.write('%(name)s.prototype.%(method_name)s = '
|
||||
'function(%(param_names)s) {};\n\n' % {
|
||||
@ -287,13 +287,42 @@ function %(proxy_name)s() {
|
||||
'offset': param.offset })
|
||||
|
||||
self.f.write(
|
||||
''' var $result = zx.channelWrite(this.channel,
|
||||
$encoder.messageData(),
|
||||
$encoder.messageHandles());
|
||||
if ($result !== zx.ZX_OK) {
|
||||
throw "zx.channelWrite failed: " + $result;
|
||||
''' var $writeResult = zx.channelWrite(this.channel,
|
||||
$encoder.messageData(),
|
||||
$encoder.messageHandles());
|
||||
if ($writeResult !== zx.ZX_OK) {
|
||||
throw "zx.channelWrite failed: " + $writeResult;
|
||||
}
|
||||
};
|
||||
''')
|
||||
|
||||
if method.has_response:
|
||||
self.f.write('''
|
||||
return zx
|
||||
.objectWaitOne(this.channel, zx.ZX_CHANNEL_READABLE, zx.ZX_TIME_INFINITE)
|
||||
.then(() => new Promise(res => {
|
||||
var $readResult = zx.channelRead(this.channel);
|
||||
if ($readResult.status !== zx.ZX_OK) {
|
||||
throw "channel read failed";
|
||||
}
|
||||
|
||||
var $view = new DataView($readResult.data);
|
||||
|
||||
var $decoder = new $fidl_Decoder($view, []);
|
||||
$decoder.claimMemory(%(size)s - $fidl_kMessageHeaderSize);
|
||||
''' % { 'size': method.maybe_response_size })
|
||||
for param, ttname in zip(method.maybe_response, type_tables):
|
||||
self.f.write(
|
||||
''' var %(param_name)s = _kTT_%(type_table)s.dec($decoder, %(offset)s);
|
||||
''' % { 'type_table': ttname,
|
||||
'param_name': _CompileIdentifier(param.name),
|
||||
'offset': param.offset })
|
||||
|
||||
self.f.write('''
|
||||
res(%(args)s);
|
||||
}));
|
||||
''' % { 'args': ', '.join(x.name for x in method.maybe_response) })
|
||||
|
||||
self.f.write('''};
|
||||
|
||||
''')
|
||||
|
||||
|
@ -17,6 +17,9 @@ const $fidl__kAlignmentMask = 0x7;
|
||||
|
||||
const $fidl__kLE = true;
|
||||
|
||||
const $fidl__kUserspaceTxidMask = 0x7fffffff;
|
||||
var $fidl__nextTxid = 1;
|
||||
|
||||
function $fidl__align(size) {
|
||||
return size + (($fidl__kAlignment - (size & $fidl__kAlignmentMask)) &
|
||||
$fidl__kAlignmentMask);
|
||||
@ -40,6 +43,8 @@ function $fidl_Encoder(ordinal) {
|
||||
*/
|
||||
$fidl_Encoder.prototype._encodeMessageHeader = function(ordinal) {
|
||||
this.alloc($fidl_kMessageHeaderSize);
|
||||
var txid = $fidl__nextTxid++ & $fidl__kUserspaceTxidMask;
|
||||
this.data.setUint32($fidl_kMessageTxidOffset, txid, $fidl__kLE);
|
||||
this.data.setUint32($fidl_kMessageOrdinalOffset, ordinal, $fidl__kLE);
|
||||
};
|
||||
|
||||
@ -89,29 +94,61 @@ $fidl_Encoder.prototype.messageHandles = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Array} data
|
||||
* @param {Array} handles
|
||||
*/
|
||||
function $fidl_Decoder(data, handles) {
|
||||
this.data = data;
|
||||
this.handles = handles;
|
||||
this.nextOffset = 0;
|
||||
this.nextHandle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} size
|
||||
*/
|
||||
$fidl_Decoder.prototype.claimMemory = function(size) {
|
||||
var result = this.nextOffset;
|
||||
this.nextOffset = $fidl__align(size);
|
||||
return result;
|
||||
}
|
||||
|
||||
$fidl_Decoder.prototype.claimHandle = function() {
|
||||
return this.handles[this.nextHandle++];
|
||||
}
|
||||
|
||||
|
||||
// Type tables and encoding helpers for generated Proxy code.
|
||||
const _kTT_int8 = {
|
||||
enc: function(e, o, v) { e.data.setInt8(o, v, $fidl__kLE); },
|
||||
enc: function(e, o, v) { e.data.setInt8(o, v); },
|
||||
dec: function(d, o) { return d.data.getInt8(o); },
|
||||
};
|
||||
|
||||
const _kTT_int16 = {
|
||||
enc: function(e, o, v) { e.data.setInt16(o, v, $fidl__kLE); },
|
||||
dec: function(d, o) { return d.data.getInt16(o, $fidl__kLE); },
|
||||
};
|
||||
|
||||
const _kTT_int32 = {
|
||||
enc: function(e, o, v) { e.data.setUint32(o, v, $fidl__kLE); },
|
||||
dec: function(d, o) { return d.data.getInt32(o, $fidl__kLE); },
|
||||
};
|
||||
|
||||
const _kTT_uint8 = {
|
||||
enc: function(e, o, v) { e.data.setUint8(o, v, $fidl__kLE); },
|
||||
enc: function(e, o, v) { e.data.setUint8(o, v); },
|
||||
dec: function(d, o) { return d.data.getUint8(o); },
|
||||
};
|
||||
|
||||
const _kTT_uint16 = {
|
||||
enc: function(e, o, v) { e.data.setUint16(o, v, $fidl__kLE); },
|
||||
dec: function(d, o) { return d.data.getUint16(o, $fidl__kLE); },
|
||||
};
|
||||
|
||||
const _kTT_uint32 = {
|
||||
enc: function(e, o, v) { e.data.setUint32(o, v, $fidl__kLE); },
|
||||
dec: function(d, o) { return d.data.getUint32(o, $fidl__kLE); },
|
||||
};
|
||||
|
||||
const _kTT_String_Nonnull = {
|
||||
|
@ -4,20 +4,191 @@
|
||||
|
||||
#include "tools/fuchsia/fidlgen_js/runtime/zircon.h"
|
||||
|
||||
#include <lib/async/default.h>
|
||||
#include <lib/async/wait.h>
|
||||
#include <lib/zx/channel.h>
|
||||
#include <zircon/errors.h>
|
||||
#include <zircon/syscalls.h>
|
||||
#include <zircon/types.h>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
#include "gin/arguments.h"
|
||||
#include "gin/array_buffer.h"
|
||||
#include "gin/converter.h"
|
||||
#include "gin/data_object_builder.h"
|
||||
#include "gin/function_template.h"
|
||||
#include "gin/public/gin_embedders.h"
|
||||
|
||||
namespace {
|
||||
|
||||
fidljs::WaitSet& GetWaitsForIsolate(v8::Isolate* isolate) {
|
||||
return *static_cast<fidljs::WaitSet*>(
|
||||
isolate->GetData(gin::kEmbedderFuchsia));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace fidljs {
|
||||
|
||||
class WaitPromiseImpl : public async_wait_t {
|
||||
public:
|
||||
WaitPromiseImpl(v8::Isolate* isolate,
|
||||
v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Promise::Resolver> resolver,
|
||||
zx_handle_t handle,
|
||||
zx_signals_t signals)
|
||||
: async_wait_t({ASYNC_STATE_INIT, &WaitPromiseImpl::StaticOnSignaled,
|
||||
handle, signals}),
|
||||
isolate_(isolate),
|
||||
wait_state_(WaitState::kCreated),
|
||||
failed_start_status_(ZX_OK) {
|
||||
context_.Reset(isolate_, context);
|
||||
resolver_.Reset(isolate_, resolver);
|
||||
}
|
||||
|
||||
~WaitPromiseImpl() {
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
|
||||
switch (wait_state_) {
|
||||
case WaitState::kCreated:
|
||||
// The wait never started, so reject the promise (but don't attempt to
|
||||
// cancel the wait).
|
||||
DCHECK_NE(failed_start_status_, ZX_OK);
|
||||
RejectPromise(failed_start_status_, 0);
|
||||
break;
|
||||
|
||||
case WaitState::kStarted:
|
||||
// The wait was started, but has not yet completed. Cancel the wait and
|
||||
// reject the promise. The object is being destructed here because it's
|
||||
// been removed from the set of waits attached to the isolate, so
|
||||
// we need not remove it.
|
||||
CHECK_EQ(async_cancel_wait(async_get_default_dispatcher(), this),
|
||||
ZX_OK);
|
||||
RejectPromise(ZX_ERR_CANCELED, 0);
|
||||
break;
|
||||
|
||||
case WaitState::kCompleted:
|
||||
// The callback has already been called and so the promise has been
|
||||
// resolved or rejected, and the wait has been removed from the
|
||||
// dispatcher, so there's nothing to do.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool BeginWait() {
|
||||
DCHECK_EQ(wait_state_, WaitState::kCreated);
|
||||
zx_status_t status = async_begin_wait(async_get_default_dispatcher(), this);
|
||||
if (status == ZX_OK) {
|
||||
wait_state_ = WaitState::kStarted;
|
||||
} else {
|
||||
failed_start_status_ = status;
|
||||
}
|
||||
return status == ZX_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
static void StaticOnSignaled(async_dispatcher_t* dispatcher,
|
||||
async_wait_t* wait,
|
||||
zx_status_t status,
|
||||
const zx_packet_signal_t* signal) {
|
||||
auto* self = static_cast<WaitPromiseImpl*>(wait);
|
||||
self->OnSignaled(status, signal);
|
||||
}
|
||||
|
||||
void OnSignaled(zx_status_t status, const zx_packet_signal_t* signal) {
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK_EQ(wait_state_, WaitState::kStarted);
|
||||
DCHECK_NE(status, ZX_ERR_CANCELED)
|
||||
<< "wait should have been canceled before shutdown";
|
||||
|
||||
wait_state_ = WaitState::kCompleted;
|
||||
|
||||
if (status == ZX_OK &&
|
||||
(signal->observed & signal->trigger) == signal->trigger) {
|
||||
ResolvePromise(signal->observed);
|
||||
} else {
|
||||
RejectPromise(status, signal->observed);
|
||||
}
|
||||
|
||||
GetWaitsForIsolate(isolate_).erase(this);
|
||||
// |this| has been deleted.
|
||||
}
|
||||
|
||||
void ResolvePromise(zx_signals_t observed) {
|
||||
v8::Local<v8::Promise::Resolver> resolver(resolver_.Get(isolate_));
|
||||
v8::Local<v8::Context> context(context_.Get(isolate_));
|
||||
v8::Local<v8::Object> value = gin::DataObjectBuilder(isolate_)
|
||||
.Set("status", ZX_OK)
|
||||
.Set("observed", observed)
|
||||
.Build();
|
||||
resolver->Resolve(context, value).ToChecked();
|
||||
}
|
||||
|
||||
void RejectPromise(zx_status_t status, zx_signals_t observed) {
|
||||
v8::Local<v8::Promise::Resolver> resolver(resolver_.Get(isolate_));
|
||||
v8::Local<v8::Context> context(context_.Get(isolate_));
|
||||
v8::Local<v8::Object> value = gin::DataObjectBuilder(isolate_)
|
||||
.Set("status", status)
|
||||
.Set("observed", observed)
|
||||
.Build();
|
||||
resolver->Reject(context, value).ToChecked();
|
||||
}
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
v8::Global<v8::Context> context_;
|
||||
v8::Global<v8::Promise::Resolver> resolver_;
|
||||
enum class WaitState {
|
||||
kCreated,
|
||||
kStarted,
|
||||
kCompleted,
|
||||
} wait_state_;
|
||||
zx_status_t failed_start_status_;
|
||||
|
||||
THREAD_CHECKER(thread_checker_);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WaitPromiseImpl);
|
||||
};
|
||||
|
||||
} // namespace fidljs
|
||||
|
||||
namespace {
|
||||
|
||||
v8::Local<v8::Promise> ZxObjectWaitOne(gin::Arguments* args) {
|
||||
zx_handle_t handle;
|
||||
if (!args->GetNext(&handle)) {
|
||||
args->ThrowError();
|
||||
return v8::Local<v8::Promise>();
|
||||
}
|
||||
|
||||
zx_signals_t signals;
|
||||
if (!args->GetNext(&signals)) {
|
||||
args->ThrowError();
|
||||
return v8::Local<v8::Promise>();
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Promise::Resolver> maybe_resolver =
|
||||
v8::Promise::Resolver::New(args->GetHolderCreationContext());
|
||||
v8::Local<v8::Promise::Resolver> resolver;
|
||||
if (maybe_resolver.ToLocal(&resolver)) {
|
||||
auto wait = std::make_unique<fidljs::WaitPromiseImpl>(
|
||||
args->isolate(), args->GetHolderCreationContext(), resolver, handle,
|
||||
signals);
|
||||
if (wait->BeginWait()) {
|
||||
// The wait will always be notified asynchronously, so it's OK to delay
|
||||
// the add until after it has completed successfully. Move |wait| into the
|
||||
// set of active waits.
|
||||
GetWaitsForIsolate(args->isolate()).insert(std::move(wait));
|
||||
}
|
||||
|
||||
// If BeginWait() fails, then |wait| will be deleted here, causing the
|
||||
// returned promise to be rejected.
|
||||
return resolver->GetPromise();
|
||||
}
|
||||
|
||||
return v8::Local<v8::Promise>();
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> ZxChannelCreate(v8::Isolate* isolate) {
|
||||
zx::channel c1, c2;
|
||||
zx_status_t status = zx::channel::create(0, &c1, &c2);
|
||||
@ -53,6 +224,47 @@ zx_status_t ZxChannelWrite(gin::Arguments* args) {
|
||||
return status;
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> ZxChannelRead(gin::Arguments* args) {
|
||||
zx_handle_t handle;
|
||||
if (!args->GetNext(&handle)) {
|
||||
args->ThrowError();
|
||||
return gin::DataObjectBuilder(args->isolate())
|
||||
.Set("status", ZX_ERR_INVALID_ARGS)
|
||||
.Build();
|
||||
}
|
||||
zx::unowned_channel ch(handle);
|
||||
|
||||
uint32_t data_size;
|
||||
uint32_t num_handles;
|
||||
zx_status_t status =
|
||||
ch->read(0, nullptr, 0, &data_size, nullptr, 0, &num_handles);
|
||||
DCHECK_EQ(status, ZX_ERR_BUFFER_TOO_SMALL);
|
||||
|
||||
std::vector<zx_handle_t> handles;
|
||||
handles.resize(num_handles);
|
||||
|
||||
v8::Local<v8::ArrayBuffer> buf =
|
||||
v8::ArrayBuffer::New(args->isolate(), data_size);
|
||||
uint32_t actual_bytes, actual_handles;
|
||||
status = ch->read(0, buf->GetContents().Data(), data_size, &actual_bytes,
|
||||
handles.data(), handles.size(), &actual_handles);
|
||||
DCHECK_EQ(actual_bytes, data_size);
|
||||
DCHECK_EQ(actual_handles, num_handles);
|
||||
CHECK_EQ(actual_handles, 0u) << "Handle passing untested";
|
||||
|
||||
if (status != ZX_OK) {
|
||||
return gin::DataObjectBuilder(args->isolate())
|
||||
.Set("status", status)
|
||||
.Build();
|
||||
}
|
||||
|
||||
return gin::DataObjectBuilder(args->isolate())
|
||||
.Set("status", status)
|
||||
.Set("data", buf)
|
||||
.Set("handles", handles)
|
||||
.Build();
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> StrToUtf8Array(gin::Arguments* args) {
|
||||
std::string str;
|
||||
// This converts the string to utf8 from ucs2, so then just repackage the
|
||||
@ -86,7 +298,11 @@ v8::Local<v8::Object> GetOrCreateZxObject(v8::Isolate* isolate,
|
||||
|
||||
namespace fidljs {
|
||||
|
||||
void InjectZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global) {
|
||||
ZxBindings::ZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global)
|
||||
: isolate_(isolate), wait_set_(std::make_unique<WaitSet>()) {
|
||||
DCHECK_EQ(isolate->GetData(gin::kEmbedderFuchsia), nullptr);
|
||||
isolate->SetData(gin::kEmbedderFuchsia, wait_set_.get());
|
||||
|
||||
v8::Local<v8::Object> zx = GetOrCreateZxObject(isolate, global);
|
||||
|
||||
#define SET_CONSTANT(k) \
|
||||
@ -146,6 +362,12 @@ void InjectZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global) {
|
||||
base::BindRepeating(&zx_handle_close))
|
||||
->GetFunction());
|
||||
SET_CONSTANT(ZX_HANDLE_INVALID);
|
||||
zx->Set(
|
||||
gin::StringToSymbol(isolate, "objectWaitOne"),
|
||||
gin::CreateFunctionTemplate(isolate, base::BindRepeating(ZxObjectWaitOne))
|
||||
->GetFunction());
|
||||
SET_CONSTANT(ZX_HANDLE_INVALID);
|
||||
SET_CONSTANT(ZX_TIME_INFINITE);
|
||||
|
||||
// Channel APIs.
|
||||
zx->Set(gin::StringToSymbol(isolate, "channelCreate"),
|
||||
@ -156,6 +378,10 @@ void InjectZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global) {
|
||||
gin::StringToSymbol(isolate, "channelWrite"),
|
||||
gin::CreateFunctionTemplate(isolate, base::BindRepeating(&ZxChannelWrite))
|
||||
->GetFunction());
|
||||
zx->Set(
|
||||
gin::StringToSymbol(isolate, "channelRead"),
|
||||
gin::CreateFunctionTemplate(isolate, base::BindRepeating(&ZxChannelRead))
|
||||
->GetFunction());
|
||||
SET_CONSTANT(ZX_CHANNEL_READABLE);
|
||||
SET_CONSTANT(ZX_CHANNEL_WRITABLE);
|
||||
SET_CONSTANT(ZX_CHANNEL_PEER_CLOSED);
|
||||
@ -175,4 +401,9 @@ void InjectZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global) {
|
||||
#undef SET_CONSTANT
|
||||
}
|
||||
|
||||
ZxBindings::~ZxBindings() {
|
||||
wait_set_->clear();
|
||||
isolate_->SetData(gin::kEmbedderFuchsia, nullptr);
|
||||
}
|
||||
|
||||
} // namespace fidljs
|
||||
|
@ -5,12 +5,53 @@
|
||||
#ifndef TOOLS_FUCHSIA_FIDLGEN_JS_RUNTIME_ZIRCON_H_
|
||||
#define TOOLS_FUCHSIA_FIDLGEN_JS_RUNTIME_ZIRCON_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/containers/flat_set.h"
|
||||
#include "base/containers/unique_ptr_adapters.h"
|
||||
#include "base/macros.h"
|
||||
#include "v8/include/v8.h"
|
||||
|
||||
namespace fidljs {
|
||||
|
||||
// Adds Zircon APIs bindings to |global|, for use by JavaScript callers.
|
||||
void InjectZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global);
|
||||
class WaitPromiseImpl;
|
||||
|
||||
// A WaitSet is associated with each Isolate and represents all outstanding
|
||||
// waits that are queued on the dispatcher.
|
||||
//
|
||||
// If the wait completes normally, the contained promise is resolved, the
|
||||
// WaitPromiseImpl is marked as completed, and then deleted (by removing it from
|
||||
// the pending set).
|
||||
//
|
||||
// If the caller shuts down with outstanding waits pending, the asynchronous
|
||||
// waits are canceled by clearing the set (which deletes all the
|
||||
// WaitPromiseImpls). If a WaitPromiseImpl has not completed when it is
|
||||
// destroyed, it cancels the outstanding wait in its destructor.
|
||||
//
|
||||
// WaitPromiseImpl is responsible for resolving or rejecting promises. If the
|
||||
// object was created, but a wait never started it will not have been added to
|
||||
// the wait set, and so will reject the promise immediately. Otherwise, the
|
||||
// promise will be resolved or rejected when the asynchronous wait is signaled
|
||||
// or canceled.
|
||||
using WaitSet =
|
||||
base::flat_set<std::unique_ptr<WaitPromiseImpl>, base::UniquePtrComparator>;
|
||||
|
||||
class ZxBindings {
|
||||
public:
|
||||
// Adds Zircon APIs bindings to |global|, for use by JavaScript callers.
|
||||
ZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global);
|
||||
|
||||
// Cleans up attached storage in the isolate added by the bindings, and
|
||||
// cancels any pending asynchronous requests. It is important this this be
|
||||
// done before the v8 context is torn down.
|
||||
~ZxBindings();
|
||||
|
||||
private:
|
||||
v8::Isolate* const isolate_;
|
||||
std::unique_ptr<WaitSet> wait_set_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ZxBindings);
|
||||
};
|
||||
|
||||
} // namespace fidljs
|
||||
|
||||
|
@ -2,11 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <lib/fidl/cpp/binding.h>
|
||||
#include <lib/fidl/cpp/internal/pending_response.h>
|
||||
#include <lib/fidl/cpp/internal/weak_stub_controller.h>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/fuchsia/async_dispatcher.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/test/launcher/unit_test_launcher.h"
|
||||
#include "base/test/test_suite.h"
|
||||
#include "base/test/test_timeouts.h"
|
||||
#include "gin/converter.h"
|
||||
#include "gin/modules/console.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
@ -64,15 +70,66 @@ TEST_F(FidlGenJsTest, BasicJSSetup) {
|
||||
EXPECT_EQ("HAI", result);
|
||||
}
|
||||
|
||||
void LoadAndSource(gin::ShellRunner* runner, const base::FilePath& filename) {
|
||||
std::string contents;
|
||||
ASSERT_TRUE(base::ReadFileToString(filename, &contents));
|
||||
|
||||
runner->Run(contents, filename.MaybeAsASCII());
|
||||
}
|
||||
|
||||
class BindingsSetupHelper {
|
||||
public:
|
||||
explicit BindingsSetupHelper(v8::Isolate* isolate)
|
||||
: isolate_(isolate),
|
||||
handle_scope_(isolate),
|
||||
delegate_(),
|
||||
runner_(&delegate_, isolate),
|
||||
scope_(&runner_),
|
||||
zx_bindings_(
|
||||
std::make_unique<fidljs::ZxBindings>(isolate, runner_.global())) {
|
||||
// TODO(scottmg): Figure out how to set up v8 import hooking and make
|
||||
// fidl_Xyz into $fidl.Xyz. Manually inject the runtime support js files
|
||||
// for now. https://crbug.com/883496.
|
||||
LoadAndSource(&runner_, base::FilePath(kRuntimeFile));
|
||||
LoadAndSource(&runner_, base::FilePath(kTestBindingFile));
|
||||
|
||||
zx_status_t status = zx::channel::create(0, &server_, &client_);
|
||||
EXPECT_EQ(status, ZX_OK);
|
||||
|
||||
runner_.global()->Set(gin::StringToSymbol(isolate, "testHandle"),
|
||||
gin::ConvertToV8(isolate, client_.get()));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T Get(const std::string& name) {
|
||||
T t;
|
||||
EXPECT_TRUE(gin::Converter<T>::FromV8(
|
||||
isolate_, runner_.global()->Get(gin::StringToV8(isolate_, name)), &t));
|
||||
return t;
|
||||
}
|
||||
|
||||
void DestroyBindingsForTesting() { zx_bindings_.reset(); }
|
||||
|
||||
zx::channel& server() { return server_; }
|
||||
zx::channel& client() { return client_; }
|
||||
gin::ShellRunner& runner() { return runner_; }
|
||||
|
||||
private:
|
||||
v8::Isolate* isolate_;
|
||||
v8::HandleScope handle_scope_;
|
||||
FidlGenJsTestShellRunnerDelegate delegate_;
|
||||
gin::ShellRunner runner_;
|
||||
gin::Runner::Scope scope_;
|
||||
std::unique_ptr<fidljs::ZxBindings> zx_bindings_;
|
||||
zx::channel server_;
|
||||
zx::channel client_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BindingsSetupHelper);
|
||||
};
|
||||
|
||||
TEST_F(FidlGenJsTest, CreateChannelPair) {
|
||||
v8::Isolate* isolate = instance_->isolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
FidlGenJsTestShellRunnerDelegate delegate;
|
||||
gin::ShellRunner runner(&delegate, isolate);
|
||||
gin::Runner::Scope scope(&runner);
|
||||
|
||||
fidljs::InjectZxBindings(isolate, runner.global());
|
||||
BindingsSetupHelper helper(isolate);
|
||||
|
||||
std::string source = R"(
|
||||
var result = zx.channelCreate();
|
||||
@ -85,25 +142,16 @@ TEST_F(FidlGenJsTest, CreateChannelPair) {
|
||||
}
|
||||
)";
|
||||
|
||||
runner.Run(source, "test.js");
|
||||
helper.runner().Run(source, "test.js");
|
||||
|
||||
zx_status_t status = ZX_ERR_INTERNAL;
|
||||
EXPECT_TRUE(gin::Converter<zx_status_t>::FromV8(
|
||||
isolate, runner.global()->Get(gin::StringToV8(isolate, "result_status")),
|
||||
&status));
|
||||
auto status = helper.Get<zx_status_t>("result_status");
|
||||
EXPECT_EQ(status, ZX_OK);
|
||||
|
||||
zx_handle_t handle = ZX_HANDLE_INVALID;
|
||||
auto h1 = helper.Get<zx_handle_t>("result_h1");
|
||||
EXPECT_NE(h1, ZX_HANDLE_INVALID);
|
||||
|
||||
EXPECT_TRUE(gin::Converter<zx_handle_t>::FromV8(
|
||||
isolate, runner.global()->Get(gin::StringToV8(isolate, "result_h1")),
|
||||
&handle));
|
||||
EXPECT_NE(handle, ZX_HANDLE_INVALID);
|
||||
|
||||
EXPECT_TRUE(gin::Converter<zx_handle_t>::FromV8(
|
||||
isolate, runner.global()->Get(gin::StringToV8(isolate, "result_h2")),
|
||||
&handle));
|
||||
EXPECT_NE(handle, ZX_HANDLE_INVALID);
|
||||
auto h2 = helper.Get<zx_handle_t>("result_h2");
|
||||
EXPECT_NE(h2, ZX_HANDLE_INVALID);
|
||||
}
|
||||
|
||||
class TestolaImpl : public fidljstest::Testola {
|
||||
@ -130,6 +178,10 @@ class TestolaImpl : public fidljstest::Testola {
|
||||
various_stuff_ = stuff_as_vec;
|
||||
}
|
||||
|
||||
void WithResponse(int32_t a, int32_t b, WithResponseCallback sum) override {
|
||||
sum(a + b);
|
||||
}
|
||||
|
||||
bool was_do_something_called() const { return was_do_something_called_; }
|
||||
int32_t received_int() const { return received_int_; }
|
||||
const std::string& received_msg() const { return received_msg_; }
|
||||
@ -149,48 +201,6 @@ class TestolaImpl : public fidljstest::Testola {
|
||||
DISALLOW_COPY_AND_ASSIGN(TestolaImpl);
|
||||
};
|
||||
|
||||
void LoadAndSource(gin::ShellRunner* runner, const base::FilePath& filename) {
|
||||
std::string contents;
|
||||
ASSERT_TRUE(base::ReadFileToString(filename, &contents));
|
||||
|
||||
runner->Run(contents, filename.MaybeAsASCII());
|
||||
}
|
||||
|
||||
class BindingsSetupHelper {
|
||||
public:
|
||||
explicit BindingsSetupHelper(v8::Isolate* isolate)
|
||||
: handle_scope_(isolate), delegate_(), runner_(&delegate_, isolate) {
|
||||
gin::Runner::Scope scope(&runner_);
|
||||
|
||||
fidljs::InjectZxBindings(isolate, runner_.global());
|
||||
|
||||
// TODO(crbug.com/883496): Figure out how to set up v8 import hooking and
|
||||
// make fidl_Xyz into $fidl.Xyz. Manually inject the runtime support js
|
||||
// files for now.
|
||||
LoadAndSource(&runner_, base::FilePath(kRuntimeFile));
|
||||
LoadAndSource(&runner_, base::FilePath(kTestBindingFile));
|
||||
|
||||
zx_status_t status = zx::channel::create(0, &server_, &client_);
|
||||
EXPECT_EQ(status, ZX_OK);
|
||||
|
||||
runner_.global()->Set(gin::StringToSymbol(isolate, "testHandle"),
|
||||
gin::ConvertToV8(isolate, client_.get()));
|
||||
}
|
||||
|
||||
zx::channel& server() { return server_; }
|
||||
zx::channel& client() { return client_; }
|
||||
gin::ShellRunner& runner() { return runner_; }
|
||||
|
||||
private:
|
||||
v8::HandleScope handle_scope_;
|
||||
FidlGenJsTestShellRunnerDelegate delegate_;
|
||||
gin::ShellRunner runner_;
|
||||
zx::channel server_;
|
||||
zx::channel client_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BindingsSetupHelper);
|
||||
};
|
||||
|
||||
TEST_F(FidlGenJsTest, RawReceiveFidlMessage) {
|
||||
v8::Isolate* isolate = instance_->isolate();
|
||||
BindingsSetupHelper helper(isolate);
|
||||
@ -323,6 +333,83 @@ TEST_F(FidlGenJsTest, RawReceiveFidlMessageWithMultipleArgs) {
|
||||
EXPECT_EQ(testola_impl.various_stuff()[2], 123456u);
|
||||
}
|
||||
|
||||
TEST_F(FidlGenJsTest, RawWithResponse) {
|
||||
base::AsyncDispatcher dispatcher;
|
||||
|
||||
v8::Isolate* isolate = instance_->isolate();
|
||||
BindingsSetupHelper helper(isolate);
|
||||
|
||||
TestolaImpl testola_impl;
|
||||
fidl::Binding<fidljstest::Testola> binding(&testola_impl);
|
||||
binding.Bind(std::move(helper.server()), &dispatcher);
|
||||
|
||||
// Send the data from the JS side into the channel.
|
||||
std::string source = R"(
|
||||
var proxy = new TestolaProxy();
|
||||
proxy.$bind(testHandle);
|
||||
this.sum_result = -1;
|
||||
proxy.WithResponse(72, 99)
|
||||
.then(sum => {
|
||||
this.sum_result = sum;
|
||||
})
|
||||
.catch((e) => log('HOT GARBAGE: ' + e));
|
||||
)";
|
||||
helper.runner().Run(source, "test.js");
|
||||
|
||||
// Run the dispatcher until the response or timeout.
|
||||
while (helper.Get<int>("sum_result") == -1) {
|
||||
ASSERT_EQ(dispatcher.DispatchOrWaitUntil(zx_deadline_after(
|
||||
ZX_MSEC(TestTimeouts::action_timeout().InMilliseconds()))),
|
||||
ZX_OK);
|
||||
}
|
||||
|
||||
// Confirm that the response was received with the correct value.
|
||||
auto sum_result = helper.Get<int>("sum_result");
|
||||
EXPECT_EQ(sum_result, 72 + 99);
|
||||
}
|
||||
|
||||
TEST_F(FidlGenJsTest, NoResponseBeforeTearDown) {
|
||||
base::AsyncDispatcher dispatcher;
|
||||
|
||||
v8::Isolate* isolate = instance_->isolate();
|
||||
|
||||
BindingsSetupHelper helper(isolate);
|
||||
|
||||
TestolaImpl testola_impl;
|
||||
fidl::Binding<fidljstest::Testola> binding(&testola_impl);
|
||||
binding.Bind(std::move(helper.server()), &dispatcher);
|
||||
|
||||
// Send the data from the JS side into the channel.
|
||||
std::string source = R"(
|
||||
var proxy = new TestolaProxy();
|
||||
proxy.$bind(testHandle);
|
||||
this.resolved = false;
|
||||
this.rejected = false;
|
||||
this.excepted = false;
|
||||
proxy.WithResponse(1, 2)
|
||||
.then(sum => {
|
||||
this.resolved = true;
|
||||
}, () => {
|
||||
this.rejected = true;
|
||||
})
|
||||
.catch((e) => {
|
||||
log('HOT GARBAGE: ' + e);
|
||||
this.excepted = true;
|
||||
})
|
||||
)";
|
||||
helper.runner().Run(source, "test.js");
|
||||
|
||||
// Run the dispatcher, to read and queue the response.
|
||||
EXPECT_EQ(dispatcher.DispatchOrWaitUntil(ZX_TIME_INFINITE), ZX_OK);
|
||||
|
||||
// This causes outstanding waits to be canceled.
|
||||
helper.DestroyBindingsForTesting();
|
||||
|
||||
EXPECT_FALSE(helper.Get<bool>("resolved"));
|
||||
EXPECT_TRUE(helper.Get<bool>("rejected"));
|
||||
EXPECT_FALSE(helper.Get<bool>("excepted"));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
base::TestSuite test_suite(argc, argv);
|
||||
|
||||
|
@ -18,4 +18,6 @@ interface Testola {
|
||||
3: PrintMsg(string msg);
|
||||
|
||||
4: VariousArgs(Blorp blorp, string:32 msg, vector<uint32> stuff);
|
||||
|
||||
5: WithResponse(int32 a, int32 b) -> (int32 sum);
|
||||
};
|
||||
|
Reference in New Issue
Block a user