910e62b5创建于 1月15日历史提交
// 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. If the
  // transfer fails synchronously, falls back to a non-direct receiver by
  // returning `pipe` unchanged. (Note that if the transfer fails
  // *asynchronously*, the new pipe handle will be returned but black-hole any
  // messages sent to it.)
  ScopedMessagePipeHandle AdoptPipe(ScopedMessagePipeHandle pipe);

  // Overwrites the portal used to transfer `pipe` to the thread-local node
  // with a dummy handle, to test how AdoptPipe() handles failures.
  void ReplacePortalForTesting(ScopedHandle dummy_portal);

 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_