| // Copyright 2019 The Chromium Authors |
| // 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_ASSOCIATED_REMOTE_H_ |
| #define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_REMOTE_H_ |
| |
| #include <cstdint> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" |
| #include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h" |
| #include "mojo/public/cpp/bindings/pending_associated_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_associated_remote.h" |
| #include "mojo/public/cpp/bindings/runtime_features.h" |
| #include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" |
| |
| namespace mojo { |
| |
| // An AssociatedRemote is similar to a Remote (see remote.h): it is used to |
| // issue mojom interface method calls that will be sent over a message pipe to |
| // be handled by the entangled AssociatedReceiver. |
| // |
| // An AssociatedRemote is needed when it is important to preserve the relative |
| // ordering of calls with another mojom interface. This is implemented by |
| // sharing the underlying message pipe between the mojom interfaces where |
| // ordering must be preserved. |
| // |
| // Because of this, an AssociatedRemote cannot be used to issue mojom interface |
| // method calls until one of its endpoints (either the AssociatedRemote itself |
| // or its entangled AssociatedReceiver) is sent over a Remote/Receiver pair |
| // or an already-established AssociatedRemote/AssociatedReceiver pair. |
| template <typename Interface> |
| class AssociatedRemote { |
| public: |
| using InterfaceType = Interface; |
| using PendingType = PendingAssociatedRemote<Interface>; |
| using Proxy = typename Interface::Proxy_; |
| |
| // Constructs an unbound AssociatedRemote. This object cannot issue Interface |
| // method calls and does not schedule any tasks. |
| AssociatedRemote() = default; |
| AssociatedRemote(AssociatedRemote&& other) noexcept { |
| *this = std::move(other); |
| } |
| |
| // Constructs a new AssociatedRemote which is bound from |pending_remote| and |
| // which schedules response callbacks and disconnection notifications on the |
| // default SequencedTaskRunner (i.e., |
| // base::SequencedTaskRunner::GetCurrentDefault() at construction time). |
| explicit AssociatedRemote(PendingAssociatedRemote<Interface> pending_remote) |
| : AssociatedRemote(std::move(pending_remote), nullptr) {} |
| |
| // Constructs a new AssociatedRemote which is bound from |pending_remote| and |
| // which schedules response callbacks and disconnection notifications on |
| // |task_runner|. |task_runner| must run tasks on the same sequence that owns |
| // this AssociatedRemote. |
| AssociatedRemote(PendingAssociatedRemote<Interface> pending_remote, |
| scoped_refptr<base::SequencedTaskRunner> task_runner) { |
| Bind(std::move(pending_remote), std::move(task_runner)); |
| } |
| |
| AssociatedRemote(const AssociatedRemote&) = delete; |
| AssociatedRemote& operator=(const AssociatedRemote&) = delete; |
| |
| ~AssociatedRemote() = default; |
| |
| AssociatedRemote& operator=(AssociatedRemote&& other) noexcept { |
| internal_state_.Swap(&other.internal_state_); |
| return *this; |
| } |
| |
| // Exposes access to callable Interface methods directed at this |
| // AssociatedRemote's receiver. Must only be called on a bound |
| // AssociatedRemote. |
| // |
| // Please also see comments of |is_bound()| about when it is safe to make |
| // calls using the returned pointer. |
| typename Interface::Proxy_* get() const { |
| DCHECK(is_bound()) |
| << "Cannot issue Interface method calls on an unbound AssociatedRemote"; |
| return internal_state_.instance(); |
| } |
| |
| // Shorthand form of |get()|. See above. |
| typename Interface::Proxy_* operator->() const { return get(); } |
| typename Interface::Proxy_& operator*() const { return *get(); } |
| |
| // Indicates whether this AssociatedRemote is bound. |
| // |
| // NOTE: |
| // 1) The state of being "bound" should not be confused with the state of |
| // being "connected" (see |is_connected()| below). An AssociatedRemote is |
| // NEVER passively unbound and the only way for it to become unbound is to |
| // explicitly call |reset()| or |Unbind()|. As such, unless you make explicit |
| // calls to those methods, it is always safe to assume that an |
| // AssociatedRemote you've bound will remain bound. |
| // |
| // 2) The state of being "bound" is a necessary but not sufficient condition |
| // for Interface methods to be callable. For them to be callable, the |
| // AssociatedRemote must also be "associated", which means either one of |
| // the following cases: |
| // 2-1) Either itself or its entangled AssociatedReceiver must be sent over |
| // a Remote/Receiver pair or an already-established |
| // AssociatedRemote/AssociatedReceiver pair. |
| // 2-2) It is bound with a dedicated message pipe. Please see comments of |
| // BindNewEndpointAndPassDedicatedReceiver(). |
| bool is_bound() const { return internal_state_.is_bound(); } |
| explicit operator bool() const { return is_bound(); } |
| |
| // Indicates whether this AssociatedRemote is connected to a receiver. Must |
| // only be called on a bound AssociatedRemote. If this returns |true|, method |
| // calls made by this AssociatedRemote may eventually end up at the connected |
| // receiver (though it's of course possible for this call to race with |
| // disconnection). If this returns |false| however, all future Interface |
| // method calls on this AssociatedRemote will be silently dropped. |
| // |
| // A bound AssociatedRemote becomes disconnected automatically either when its |
| // receiver is destroyed, or when it receives a malformed or otherwise |
| // unexpected response message from the receiver. |
| // |
| // NOTE: The state of being "bound" should not be confused with the state of |
| // being "connected". See |is_bound()| above. |
| bool is_connected() const { |
| DCHECK(is_bound()); |
| return !internal_state_.encountered_error(); |
| } |
| |
| // Sets a Closure to be invoked if this AssociatedRemote is cut off from its |
| // receiver. This can happen if the corresponding AssociatedReceiver (or |
| // unconsumed PendingAssociatedReceiver) is destroyed, or if the |
| // AssociatedReceiver sends a malformed or otherwise unexpected response |
| // message to this AssociatedRemote. Must only be called on a bound |
| // AssociatedRemote object, and only remains set as long as the |
| // AssociatedRemote is both bound and connected. |
| // |
| // If invoked at all, |handler| will be scheduled asynchronously using the |
| // AssociatedRemote's bound SequencedTaskRunner. |
| void set_disconnect_handler(base::OnceClosure handler) { |
| if (is_connected()) |
| internal_state_.set_connection_error_handler(std::move(handler)); |
| } |
| |
| // Similar to above but the handler receives additional metadata if provided |
| // by the receiving endpoint when closing itself. |
| void set_disconnect_with_reason_handler( |
| ConnectionErrorWithReasonCallback handler) { |
| internal_state_.set_connection_error_with_reason_handler( |
| std::move(handler)); |
| } |
| |
| // A convenient helper that resets this AssociatedRemote on disconnect. Note |
| // that this replaces any previously set disconnection handler. Must be called |
| // on a bound AssociatedRemote object. If the AssociatedRemote is connected, |
| // a callback is set to reset it after it is disconnected. If AssociatedRemote |
| // is bound but disconnected then reset is called immediately. |
| void reset_on_disconnect() { |
| if (!is_connected()) { |
| reset(); |
| return; |
| } |
| set_disconnect_handler( |
| base::BindOnce(&AssociatedRemote::reset, base::Unretained(this))); |
| } |
| |
| // Resets this AssociatedRemote to an unbound state. To reset the |
| // AssociatedRemote and recover an PendingAssociatedRemote that can be bound |
| // again later, use |Unbind()| instead. |
| void reset() { |
| State doomed_state; |
| internal_state_.Swap(&doomed_state); |
| } |
| |
| // Similar to the method above, but also specifies a disconnect reason. |
| void ResetWithReason(uint32_t custom_reason, const std::string& description) { |
| if (internal_state_.is_bound()) |
| internal_state_.CloseWithReason(custom_reason, description); |
| reset(); |
| } |
| |
| // Helpers for binding and unbinding the AssociatedRemote. Only an unbound |
| // AssociatedRemote (i.e. |is_bound()| is false) may be bound. Similarly, only |
| // a bound AssociatedRemote may be unbound. |
| |
| // Binds this AssociatedRemote with the returned PendingAssociatedReceiver. |
| // Mojom interface method calls made through |this| will be routed to the |
| // object that ends up binding the returned PendingAssociatedReceiver. |
| // |
| // Any response callbacks or disconnection notifications will be scheduled to |
| // run on |task_runner|. If |task_runner| is null, defaults to the current |
| // SequencedTaskRunner. |
| [[nodiscard]] PendingAssociatedReceiver<Interface> |
| BindNewEndpointAndPassReceiver( |
| scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) { |
| DCHECK(!is_bound()) << "AssociatedRemote for " << Interface::Name_ |
| << " is already bound"; |
| if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) { |
| return PendingAssociatedReceiver<Interface>(); |
| } |
| ScopedInterfaceEndpointHandle remote_handle; |
| ScopedInterfaceEndpointHandle receiver_handle; |
| ScopedInterfaceEndpointHandle::CreatePairPendingAssociation( |
| &remote_handle, &receiver_handle); |
| Bind(PendingAssociatedRemote<Interface>(std::move(remote_handle), 0), |
| std::move(task_runner)); |
| return PendingAssociatedReceiver<Interface>(std::move(receiver_handle)); |
| } |
| |
| // Binds this AssociatedRemote by consuming |pending_remote|. |
| // |
| // Any response callbacks or disconnection notifications will be scheduled to |
| // run on |task_runner|. If |task_runner| is null, defaults to the current |
| // SequencedTaskRunner. |
| void Bind(PendingAssociatedRemote<Interface> pending_remote, |
| scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) { |
| DCHECK(!is_bound()) << "AssociatedRemote for " << Interface::Name_ |
| << " is already bound"; |
| |
| if (!pending_remote) { |
| reset(); |
| return; |
| } |
| if (!internal::GetRuntimeFeature_ExpectEnabled<Interface>()) { |
| reset(); |
| return; |
| } |
| |
| internal_state_.Bind( |
| AssociatedInterfacePtrInfo<Interface>(pending_remote.PassHandle(), |
| pending_remote.version()), |
| std::move(task_runner)); |
| |
| // Force the internal state to configure its proxy. Unlike InterfacePtr we |
| // do not use AssociatedRemote in transit, so binding to a pipe handle can |
| // also imply binding to a SequencedTaskRunner and observing pipe handle |
| // state. |
| std::ignore = internal_state_.instance(); |
| } |
| |
| // Binds this AssociatedRemote with the returned PendingAssociatedReceiver |
| // using a dedicated message pipe. This allows the entangled |
| // AssociatedReceiver/AssociatedRemote endpoints to be used without ever being |
| // associated with any other mojom interfaces. |
| // |
| // Needless to say, messages sent between the two entangled endpoints will not |
| // be ordered with respect to any other mojom interfaces. This is generally |
| // useful for ignoring calls on an associated remote or for binding associated |
| // endpoints in tests. |
| [[nodiscard]] PendingAssociatedReceiver<Interface> |
| BindNewEndpointAndPassDedicatedReceiver() { |
| DCHECK(!is_bound()) << "AssociatedRemote for " << Interface::Name_ |
| << " is already bound"; |
| |
| PendingAssociatedReceiver<Interface> receiver = |
| BindNewEndpointAndPassReceiver(); |
| if (receiver) { |
| receiver.EnableUnassociatedUsage(); |
| } |
| return receiver; |
| } |
| |
| // Unbinds this AssociatedRemote, rendering it unable to issue further |
| // Interface method calls. Returns a PendingAssociatedRemote which may be |
| // passed across threads or processes and consumed by another AssociatedRemote |
| // elsewhere. |
| // |
| // Note that it is an error (the bad, crashy kind of error) to attempt to |
| // |Unbind()| an AssociatedRemote which is awaiting one or more responses to |
| // previously issued Interface method calls. Calling this method should only |
| // be considered in cases where satisfaction of that constraint can be proven. |
| // |
| // Must only be called on a bound AssociatedRemote. |
| [[nodiscard]] PendingAssociatedRemote<Interface> Unbind() { |
| DCHECK(is_bound()); |
| CHECK(!internal_state_.has_pending_callbacks()); |
| State state; |
| internal_state_.Swap(&state); |
| AssociatedInterfacePtrInfo<Interface> info = state.PassInterface(); |
| return PendingAssociatedRemote<Interface>(info.PassHandle(), |
| info.version()); |
| } |
| |
| // Sends a message on the underlying message pipe and runs the current |
| // message loop until its response is received. This can be used in tests to |
| // verify that no message was sent on a message pipe in response to some |
| // stimulus. |
| void FlushForTesting() { internal_state_.FlushForTesting(); } |
| |
| internal::AssociatedInterfacePtrState<Interface>* internal_state() { |
| return &internal_state_; |
| } |
| |
| private: |
| using State = internal::AssociatedInterfacePtrState<Interface>; |
| mutable State internal_state_; |
| }; |
| |
| } // namespace mojo |
| |
| #endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_REMOTE_H_ |