C++程序  |  326行  |  11.38 KB

// 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() : weak_ptr_factory_(this) {}

  void set_connection_error_handler(base::RepeatingClosure error_handler) {
    error_handler_ = std::move(error_handler);
    error_with_reason_handler_.Reset();
  }

  void set_connection_error_with_reason_handler(
      RepeatingConnectionErrorWithReasonCallback error_handler) {
    error_with_reason_handler_ = std::move(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;
  }

  // Swaps the interface implementation with a different one, to allow tests
  // to modify behavior.
  //
  // Returns the existing interface implementation to the caller.
  ImplPointerType SwapImplForTesting(BindingId id, ImplPointerType new_impl) {
    auto it = bindings_.find(id);
    if (it == bindings_.end())
      return nullptr;

    return it->second->SwapImplForTesting(new_impl);
  }

  void CloseAllBindings() { bindings_.clear(); }

  bool empty() const { return bindings_.empty(); }

  size_t size() const { return bindings_.size(); }

  // 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_;
  }

  // Implementations may call this when processing a dispatched message or
  // error. During the extent of message or error dispatch, this will return the
  // BindingId of the specific binding which received the message or error.
  BindingId dispatch_binding() const {
    DCHECK(dispatch_context_);
    return dispatch_binding_;
  }

  // Reports the currently dispatching Message as bad and closes the binding the
  // message was received from. Note that this is only legal to call from
  // directly within the stack frame of a message dispatch. If you need to do
  // asynchronous work before you can determine the legitimacy of a message, use
  // GetBadMessageCallback() and retain its result until you're ready to invoke
  // or discard it.
  void ReportBadMessage(const std::string& error) {
    GetBadMessageCallback().Run(error);
  }

  // Acquires a callback which may be run to report the currently dispatching
  // Message as bad and close the binding the message was received from. Note
  // that this is only legal to call from directly within the stack frame of a
  // message dispatch, but the returned callback may be called exactly once any
  // time thereafter as long as the binding set itself hasn't been destroyed yet
  // to report the message as bad. This may only be called once per message.
  // The returned callback must be called on the BindingSet's own sequence.
  ReportBadMessageCallback GetBadMessageCallback() {
    DCHECK(dispatch_context_);
    return base::BindOnce(
        [](ReportBadMessageCallback error_callback,
           base::WeakPtr<BindingSetBase> binding_set, BindingId binding_id,
           const std::string& error) {
          std::move(error_callback).Run(error);
          if (binding_set)
            binding_set->RemoveBinding(binding_id);
        },
        mojo::GetBadMessageCallback(), weak_ptr_factory_.GetWeakPtr(),
        dispatch_binding());
  }

  void FlushForTesting() {
    DCHECK(!is_flushing_);
    is_flushing_ = true;
    for (auto& binding : bindings_)
      if (binding.second)
        binding.second->FlushForTesting();
    is_flushing_ = false;
    // Clean up any bindings that were destroyed.
    for (auto it = bindings_.begin(); it != bindings_.end();) {
      if (!it->second)
        it = bindings_.erase(it);
      else
        ++it;
    }
  }

 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)) {
      binding_.AddFilter(std::make_unique<DispatchFilter>(this));
      binding_.set_connection_error_with_reason_handler(
          base::BindOnce(&Entry::OnConnectionError, base::Unretained(this)));
    }

    void FlushForTesting() { binding_.FlushForTesting(); }

    ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {
      return binding_.SwapImplForTesting(new_impl);
    }

   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() {
      binding_set_->SetDispatchContext(&context_, binding_id_);
    }

    void OnConnectionError(uint32_t custom_reason,
                           const std::string& description) {
      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, BindingId binding_id) {
    dispatch_context_ = context;
    dispatch_binding_ = binding_id;
    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 = std::make_unique<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);
    if (!is_flushing_)
      bindings_.erase(it);

    if (error_handler_) {
      error_handler_.Run();
    } else if (error_with_reason_handler_) {
      error_with_reason_handler_.Run(custom_reason, description);
    }
  }

  base::RepeatingClosure error_handler_;
  RepeatingConnectionErrorWithReasonCallback error_with_reason_handler_;
  PreDispatchCallback pre_dispatch_handler_;
  BindingId next_binding_id_ = 0;
  std::map<BindingId, std::unique_ptr<Entry>> bindings_;
  bool is_flushing_ = false;
  const Context* dispatch_context_ = nullptr;
  BindingId dispatch_binding_;
  base::WeakPtrFactory<BindingSetBase> weak_ptr_factory_;

  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_