blob: 6d77cc4833a79a2b60d6878bdaf4e185730ccb07 [file] [log] [blame]
// Copyright 2023 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_DIRECT_RECEIVER_H_
#define MOJO_PUBLIC_CPP_BINDINGS_DIRECT_RECEIVER_H_
#include <cstdint>
#include <map>
#include <memory>
#include <string_view>
#include <utility>
#include "base/component_export.h"
#include "base/containers/flat_map.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/types/pass_key.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/system/handle.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "third_party/ipcz/include/ipcz/ipcz.h"
namespace blink {
class WidgetInputHandlerImpl;
}
namespace cc::mojo_embedder {
class AsyncLayerTreeFrameSink;
}
namespace cc::slim {
class FrameSinkImpl;
}
namespace viz {
class CompositorFrameSinkImpl;
class FrameSinkManagerImpl;
}
namespace mojo {
namespace internal {
// Encapsulates a thread-local ipcz node which is brought up lazily by any
// DirectReceiver when binding a pipe on a specific thread. The underlying node
// is ref-counted such that a thread's node is automatically torn down when its
// last DirectReceiver goes away. (Except in sandboxed processes on Windows,
// which only allow a fixed number of ThreadLocalNodes, so once created they're
// never deleted.)
// TODO(crbug.com/446199357): Remove the refcounting completely. It's unneeded
// complexity.
class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) ThreadLocalNode
: public base::RefCounted<ThreadLocalNode> {
public:
// Construction requires a PassKey private to this class. In practice,
// constructed only within the static `Get()` method as needed.
explicit ThreadLocalNode(base::PassKey<ThreadLocalNode>);
// Gets the current thread's ThreadLocalNode instance, initializing a new one
// if one doesn't already exist.
static scoped_refptr<ThreadLocalNode> Get();
// Indicates whether a ThreadLocalNode instance exists for the current thread.
static bool CurrentThreadHasInstance();
IpczHandle node() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return node_->value();
}
// Transfers `pipe` from the process's global node to the thread-local ipcz
// node owned by this object and returns a new pipe handle for it.
ScopedMessagePipeHandle AdoptPipe(ScopedMessagePipeHandle pipe);
private:
friend class base::RefCounted<ThreadLocalNode>;
~ThreadLocalNode();
void WatchForIncomingTransfers();
static void OnTrapEvent(const IpczTrapEvent* event);
void OnTransferredPortalAvailable();
THREAD_CHECKER(thread_checker_);
// A dedicated node created for this object.
ScopedHandle node_ GUARDED_BY_CONTEXT(thread_checker_);
// A portal on the local node which is connected to `global_portal_`. Used to
// receive pipes from the global node.
ScopedHandle local_portal_ GUARDED_BY_CONTEXT(thread_checker_);
// A portal on the global node which is connected to `local_portal_`. Used to
// transfer pipes from the global node to the local one.
ScopedHandle global_portal_ GUARDED_BY_CONTEXT(thread_checker_);
// Tracks pending portal merges. See AdoptPortal() implementation for gritty
// details.
uint64_t next_merge_id_ GUARDED_BY_CONTEXT(thread_checker_) = 0;
std::map<uint64_t, ScopedHandle> pending_merges_
GUARDED_BY_CONTEXT(thread_checker_);
base::WeakPtrFactory<ThreadLocalNode> weak_ptr_factory_
GUARDED_BY_CONTEXT(thread_checker_){this};
};
} // namespace internal
namespace test::direct_receiver_unittest {
class ServiceImpl;
} // namespace test::direct_receiver_unittest
// Key object that must be provided to construct a DirectReceiver instance.
// See notes on DirectReceiver below to understand why this is guarded.
class DirectReceiverKey {
private:
DirectReceiverKey() = default;
// Update this list and get a mojo/OWNERS approval in order to gain access to
// DirectReceiver construction.
friend class cc::mojo_embedder::AsyncLayerTreeFrameSink;
friend class cc::slim::FrameSinkImpl;
friend class mojo::test::direct_receiver_unittest::ServiceImpl;
friend class blink::WidgetInputHandlerImpl;
friend class viz::CompositorFrameSinkImpl;
friend class viz::FrameSinkManagerImpl;
};
// DirectReceiver is a wrapper around the standard Receiver<T> type that always
// receives its messages directly from the sender without an IO-thread hop. To
// enable this safely DirectReceiver is constrained in a few ways:
//
// - It cannot be unbound and moved once bound
// - It's always bound on the current default task runner
// - It must be bound on a thread whose MessagePump exposes an IOWatcher
//
// As long as any DirectReceiver exists on a thread, there is a thread-local
// ThreadLocalNode instance which lives on that thread to receive IPC directly
// from out-of-process peers. When one of these DirectReceivers is bound to a
// pipe, it indicates that the pipe will be receiving messages on the thread.
// For that to happen the pipe is 'transferred' to the ThreadLocalNode.
//
// TODO(crbug.com/40266729): Find a way to transfer without creating another
// ipcz pipe.
//
// SUBTLE: DirectReceiver internally allocates a LIMITED SYSTEM RESOURCE on many
// systems (including Android and Chrome OS) and must therefore be used
// sparingly. All usage must be approved by Mojo OWNERS, with access controlled
// by the friend list in DirectReceiverKey above.
//
// EVEN MORE SUBTLE: Any Mojo interface endpoints received in messages to a
// DirectReceiver will also permanently receive I/O on the DirectReceiver's
// thread. While they may be bound on any thread and otherwise behave like any
// other Receiver, their incoming messages will hop through the DirectReceiver's
// thread just as messages to other Receivers normally hop through the global IO
// thread. Unless you're going to bind them all to the same thread as the
// DirectReceiver, passing pipes to your DirectReceiver is likely a BAD IDEA.
template <typename T>
class DirectReceiver {
static_assert(
T::kSupportsDirectReceiver,
"This interface must be marked with the [DirectReceiver] attribute.");
public:
// Creates a DirectReceiver bound to the current thread.
DirectReceiver(DirectReceiverKey, T* impl) : receiver_(impl) {}
~DirectReceiver() = default;
void set_disconnect_handler(base::OnceClosure handler) {
receiver_.set_disconnect_handler(std::move(handler));
}
bool is_bound() const { return receiver_.is_bound(); }
void ReportBadMessage(std::string_view error) {
receiver_.ReportBadMessage(error);
}
// Binds this object to `receiver` to receive IPC directly on the calling
// thread, which must be the same thread the DirectReceiver was created on.
void Bind(PendingReceiver<T> receiver) {
receiver_.Bind(receiver.is_valid() ? PendingReceiver<T>(node_->AdoptPipe(
receiver.PassPipe()))
: std::move(receiver));
}
void ResetWithReason(uint32_t custom_reason_code,
std::string_view description) {
receiver_.ResetWithReason(custom_reason_code, description);
}
internal::ThreadLocalNode& node_for_testing() { return *node_; }
Receiver<T>& receiver_for_testing() { return receiver_; }
private:
const scoped_refptr<internal::ThreadLocalNode> node_{
internal::ThreadLocalNode::Get()};
Receiver<T> receiver_;
};
// Indicates whether DirectReceiver can be supported in the calling process.
COMPONENT_EXPORT(MOJO_CPP_BINDINGS) bool IsDirectReceiverSupported();
#if BUILDFLAG(IS_WIN)
// The Windows sandbox blocks named pipe creation, so in a sandboxed process
// this must be called before the sandbox is locked down. This is safe to call
// in an unsandboxed process but is not required.
//
// This restricts DirectReceiver to a single thread, although the number of
// threads could be increased by creating more transports if needed.
COMPONENT_EXPORT(MOJO_CPP_BINDINGS)
void CreateDirectReceiverTransportBeforeSandbox();
#endif // BUILDFLAG(IS_WIN)
} // namespace mojo
#endif // MOJO_PUBLIC_CPP_BINDINGS_DIRECT_RECEIVER_H_