blob: 919f9c09adbffaf13287f1735fd24cb0e265ec13 [file] [log] [blame]
// Copyright 2014 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.
#ifndef MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_
#define MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/connection_error_callback.h"
#include "mojo/public/cpp/bindings/interface_ptr.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/bindings/message.h"
namespace mojo {
template <typename BindingType>
struct BindingSetTraits;
template <typename Interface, typename ImplRefTraits>
struct BindingSetTraits<Binding<Interface, ImplRefTraits>> {
using ProxyType = InterfacePtr<Interface>;
using RequestType = InterfaceRequest<Interface>;
using BindingType = Binding<Interface, ImplRefTraits>;
using ImplPointerType = typename BindingType::ImplPointerType;
static RequestType MakeRequest(ProxyType* proxy) {
return mojo::MakeRequest(proxy);
}
};
using BindingId = size_t;
template <typename ContextType>
struct BindingSetContextTraits {
using Type = ContextType;
static constexpr bool SupportsContext() { return true; }
};
template <>
struct BindingSetContextTraits<void> {
// NOTE: This choice of Type only matters insofar as it affects the size of
// the |context_| field of a BindingSetBase::Entry with void context. The
// context value is never used in this case.
using Type = bool;
static constexpr bool SupportsContext() { return false; }
};
// Generic definition used for BindingSet and AssociatedBindingSet to own a
// collection of bindings which point to the same implementation.
//
// If |ContextType| is non-void, then every added binding must include a context
// value of that type, and |dispatch_context()| will return that value during
// the extent of any message dispatch targeting that specific binding.
template <typename Interface, typename BindingType, typename ContextType>
class BindingSetBase {
public:
using ContextTraits = BindingSetContextTraits<ContextType>;
using Context = typename ContextTraits::Type;
using PreDispatchCallback = base::Callback<void(const Context&)>;
using Traits = BindingSetTraits<BindingType>;
using ProxyType = typename Traits::ProxyType;
using RequestType = typename Traits::RequestType;
using ImplPointerType = typename Traits::ImplPointerType;
BindingSetBase() {}
void set_connection_error_handler(const base::Closure& error_handler) {
error_handler_ = error_handler;
error_with_reason_handler_.Reset();
}
void set_connection_error_with_reason_handler(
const ConnectionErrorWithReasonCallback& error_handler) {
error_with_reason_handler_ = error_handler;
error_handler_.Reset();
}
// Sets a callback to be invoked immediately before dispatching any message or
// error received by any of the bindings in the set. This may only be used
// with a non-void |ContextType|.
void set_pre_dispatch_handler(const PreDispatchCallback& handler) {
static_assert(ContextTraits::SupportsContext(),
"Pre-dispatch handler usage requires non-void context type.");
pre_dispatch_handler_ = handler;
}
// Adds a new binding to the set which binds |request| to |impl| with no
// additional context.
BindingId AddBinding(ImplPointerType impl, RequestType request) {
static_assert(!ContextTraits::SupportsContext(),
"Context value required for non-void context type.");
return AddBindingImpl(std::move(impl), std::move(request), false);
}
// Adds a new binding associated with |context|.
BindingId AddBinding(ImplPointerType impl,
RequestType request,
Context context) {
static_assert(ContextTraits::SupportsContext(),
"Context value unsupported for void context type.");
return AddBindingImpl(std::move(impl), std::move(request),
std::move(context));
}
// Removes a binding from the set. Note that this is safe to call even if the
// binding corresponding to |id| has already been removed.
//
// Returns |true| if the binding was removed and |false| if it didn't exist.
bool RemoveBinding(BindingId id) {
auto it = bindings_.find(id);
if (it == bindings_.end())
return false;
bindings_.erase(it);
return true;
}
// Returns a proxy bound to one end of a pipe whose other end is bound to
// |this|. If |id_storage| is not null, |*id_storage| will be set to the ID
// of the added binding.
ProxyType CreateInterfacePtrAndBind(ImplPointerType impl,
BindingId* id_storage = nullptr) {
ProxyType proxy;
BindingId id = AddBinding(std::move(impl), Traits::MakeRequest(&proxy));
if (id_storage)
*id_storage = id;
return proxy;
}
void CloseAllBindings() { bindings_.clear(); }
bool empty() const { return bindings_.empty(); }
// Implementations may call this when processing a dispatched message or
// error. During the extent of message or error dispatch, this will return the
// context associated with the specific binding which received the message or
// error. Use AddBinding() to associated a context with a specific binding.
const Context& dispatch_context() const {
static_assert(ContextTraits::SupportsContext(),
"dispatch_context() requires non-void context type.");
DCHECK(dispatch_context_);
return *dispatch_context_;
}
void FlushForTesting() {
for (auto& binding : bindings_)
binding.second->FlushForTesting();
}
private:
friend class Entry;
class Entry {
public:
Entry(ImplPointerType impl,
RequestType request,
BindingSetBase* binding_set,
BindingId binding_id,
Context context)
: binding_(std::move(impl), std::move(request)),
binding_set_(binding_set),
binding_id_(binding_id),
context_(std::move(context)) {
if (ContextTraits::SupportsContext())
binding_.AddFilter(base::MakeUnique<DispatchFilter>(this));
binding_.set_connection_error_with_reason_handler(
base::Bind(&Entry::OnConnectionError, base::Unretained(this)));
}
void FlushForTesting() { binding_.FlushForTesting(); }
private:
class DispatchFilter : public MessageReceiver {
public:
explicit DispatchFilter(Entry* entry) : entry_(entry) {}
~DispatchFilter() override {}
private:
// MessageReceiver:
bool Accept(Message* message) override {
entry_->WillDispatch();
return true;
}
Entry* entry_;
DISALLOW_COPY_AND_ASSIGN(DispatchFilter);
};
void WillDispatch() {
DCHECK(ContextTraits::SupportsContext());
binding_set_->SetDispatchContext(&context_);
}
void OnConnectionError(uint32_t custom_reason,
const std::string& description) {
if (ContextTraits::SupportsContext())
WillDispatch();
binding_set_->OnConnectionError(binding_id_, custom_reason, description);
}
BindingType binding_;
BindingSetBase* const binding_set_;
const BindingId binding_id_;
Context const context_;
DISALLOW_COPY_AND_ASSIGN(Entry);
};
void SetDispatchContext(const Context* context) {
DCHECK(ContextTraits::SupportsContext());
dispatch_context_ = context;
if (!pre_dispatch_handler_.is_null())
pre_dispatch_handler_.Run(*context);
}
BindingId AddBindingImpl(ImplPointerType impl,
RequestType request,
Context context) {
BindingId id = next_binding_id_++;
DCHECK_GE(next_binding_id_, 0u);
auto entry = base::MakeUnique<Entry>(std::move(impl), std::move(request),
this, id, std::move(context));
bindings_.insert(std::make_pair(id, std::move(entry)));
return id;
}
void OnConnectionError(BindingId id,
uint32_t custom_reason,
const std::string& description) {
auto it = bindings_.find(id);
DCHECK(it != bindings_.end());
// We keep the Entry alive throughout error dispatch.
std::unique_ptr<Entry> entry = std::move(it->second);
bindings_.erase(it);
if (!error_handler_.is_null())
error_handler_.Run();
else if (!error_with_reason_handler_.is_null())
error_with_reason_handler_.Run(custom_reason, description);
}
base::Closure error_handler_;
ConnectionErrorWithReasonCallback error_with_reason_handler_;
PreDispatchCallback pre_dispatch_handler_;
BindingId next_binding_id_ = 0;
std::map<BindingId, std::unique_ptr<Entry>> bindings_;
const Context* dispatch_context_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(BindingSetBase);
};
template <typename Interface, typename ContextType = void>
using BindingSet = BindingSetBase<Interface, Binding<Interface>, ContextType>;
} // namespace mojo
#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_