blob: fa3e0a0928dc340d579e53ddb7080df7a280ea56 [file] [log] [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"
#include "third_party/abseil-cpp/absl/base/attributes.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.
RenderFrameHost* GetCurrentTargetFrame() ABSL_ATTRIBUTE_RETURNS_NONNULL {
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_