1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183

content / public / browser / render_frame_host_receiver_set.h [blame]

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_PUBLIC_BROWSER_RENDER_FRAME_HOST_RECEIVER_SET_H_
#define CONTENT_PUBLIC_BROWSER_RENDER_FRAME_HOST_RECEIVER_SET_H_

#include <map>
#include <vector>

#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "content/common/content_export.h"
#include "content/public/browser/active_url_message_filter.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/associated_receiver_set.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"

namespace content {

class WebContents;

// Owns a set of Channel-associated interface receivers with frame context on
// message dispatch.
//
// When messages are dispatched to the implementation, the implementation can
// call GetCurrentTargetFrame() on this object (see below) to determine which
// frame sent the message.
//
// In order to expose the interface to all RenderFrames, a binder must be
// registered for the interface. Typically this is done in
// RegisterAssociatedInterfaceBindersForRenderFrameHost() in a
// ContentBrowserClient subclass.  Doing that will expose the interface to all
// remote RenderFrame objects. If the WebContents is destroyed at any point, the
// receivers will automatically reset and will cease to dispatch further
// incoming messages.
//
// Because this object uses Channel-associated interface receivers, all messages
// sent via these interfaces are ordered with respect to legacy Chrome IPC
// messages on the relevant IPC::Channel (i.e. the Channel between the browser
// and whatever render process hosts the sending frame.)
//
// Because this is a templated class, its complete implementation lives in the
// header file.
template <typename Interface>
class CONTENT_EXPORT RenderFrameHostReceiverSet : public WebContentsObserver {
 public:
  using ImplPointerType = Interface*;

  RenderFrameHostReceiverSet(WebContents* web_contents, Interface* impl)
      : WebContentsObserver(web_contents), impl_(impl) {}
  ~RenderFrameHostReceiverSet() override = default;

  RenderFrameHostReceiverSet(const RenderFrameHostReceiverSet&) = delete;
  RenderFrameHostReceiverSet& operator=(const RenderFrameHostReceiverSet&) =
      delete;

  void Bind(RenderFrameHost* render_frame_host,
            mojo::PendingAssociatedReceiver<Interface> pending_receiver) {
    // If the RenderFrameHost does not have a live RenderFrame:
    // 1. There is no point in binding receivers, as the renderer should not be
    //    doing anything with this RenderFrameHost.
    // 2. More problematic, `RenderFrameDeleted()` might not be called again
    //    for `render_frame_host`, potentially leaving dangling pointers to the
    //    RenderFrameHost (or other related objects) after the RenderFrameHost
    //    itself is later deleted.
    if (!render_frame_host->IsRenderFrameLive()) {
      return;
    }

    // Inject the ActiveUrlMessageFilter to improve crash reporting. This filter
    // sets the correct URL crash keys based on the target RFH that is
    // processing a message.
    mojo::ReceiverId id = receivers_.Add(
        impl_, std::move(pending_receiver), render_frame_host,
        std::make_unique<internal::ActiveUrlMessageFilter>(render_frame_host));
    frame_to_receivers_map_[render_frame_host].push_back(id);
  }

  // Implementations of `Interface` can call `GetCurrentTargetFrame()` to
  // determine which frame sent the message. `GetCurrentTargetFrame()` will
  // never return `nullptr`.
  //
  // Important: this method must only be called while the incoming message is
  // being dispatched on the stack.
  RETURNS_NONNULL RenderFrameHost* GetCurrentTargetFrame() {
    if (current_target_frame_for_testing_)
      return current_target_frame_for_testing_;
    return receivers_.current_context();
  }

  // Reports the currently dispatching Message as bad and closes+removes the
  // receiver which received the message. Prefer this over the global
  // `mojo::ReportBadMessage()` function, since calling this method promptly
  // disconnects the receiver, preventing further (potentially bad) messages
  // from being processed.
  //
  // Important: this method must only be called while the incoming message is
  // being dispatched on the stack. To report a bad message after asynchronous
  // processing (e.g. posting a task that then reports a the bad message), use
  // `GetMessageCallback()` and pass the returned callback to the async task
  // that needs to report the message as bad.
  NOT_TAIL_CALLED void ReportBadMessage(const std::string& message) {
    receivers_.ReportBadMessage(message);
  }

  // Creates a callback which, when run, reports the currently dispatching
  // Message as bad and closes+removes the receiver which received the message.
  // Prefer this over the global `mojo::GetBadMessageCallback()` function,
  // since running the callback promptly disconnects the receiver, preventing
  // further (potentially bad) messages from being processed.
  //
  // Important: like `ReportBadMessage()`, this method must only be called while
  // the incoming message is being dispatched on the stack. However, unlike
  // `ReportBadMessage()`, the returned callback may be called even if the
  // original message is no longer being dispatched on the stack.
  //
  // Sequence safety: the returned callback must be called on the sequence that
  // owns `this` (i.e. the UI thread).
  mojo::ReportBadMessageCallback GetBadMessageCallback() {
    return receivers_.GetBadMessageCallback();
  }

  void SetCurrentTargetFrameForTesting(RenderFrameHost* render_frame_host) {
    current_target_frame_for_testing_ = render_frame_host;
  }

  // Allows test code to swap the interface implementation.
  //
  // Returns the existing interface implementation to the caller.
  //
  // The caller needs to guarantee that `new_impl` will live longer than
  // `this` Receiver.  One way to achieve this is to store the returned
  // `old_impl` and swap it back in when `new_impl` is getting destroyed.
  // Test code should prefer using `mojo::test::ScopedSwapImplForTesting` if
  // possible.
  [[nodiscard]] ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {
    ImplPointerType old_impl = impl_;
    impl_ = new_impl;

    for (const auto& it : frame_to_receivers_map_) {
      const std::vector<mojo::ReceiverId>& receiver_ids = it.second;
      for (const mojo::ReceiverId& id : receiver_ids) {
        // RenderFrameHostReceiverSet only allows all-or=nothing swaps, so
        // all the old impls are expected to be equal to `this`'s old impl_.
        CHECK_EQ(old_impl, receivers_.SwapImplForTesting(id, new_impl));
      }
    }

    return old_impl;
  }

 private:
  // content::WebContentsObserver:
  void RenderFrameDeleted(RenderFrameHost* render_frame_host) override {
    auto it = frame_to_receivers_map_.find(render_frame_host);
    if (it == frame_to_receivers_map_.end())
      return;
    for (auto id : it->second)
      receivers_.Remove(id);
    frame_to_receivers_map_.erase(it);
  }

  // Receiver set for each frame in the page. Note, bindings are reused across
  // navigations that are same-site since the RenderFrameHost is reused in that
  // case.
  mojo::AssociatedReceiverSet<Interface, RenderFrameHost*> receivers_;

  // Track which RenderFrameHosts are in the |receivers_| set so they can
  // be removed them when a RenderFrameHost is removed.
  std::map<RenderFrameHost*, std::vector<mojo::ReceiverId>>
      frame_to_receivers_map_;

  raw_ptr<RenderFrameHost> current_target_frame_for_testing_ = nullptr;

  // Must outlive this class.
  raw_ptr<Interface> impl_;
};

}  // namespace content

#endif  // CONTENT_PUBLIC_BROWSER_RENDER_FRAME_HOST_RECEIVER_SET_H_