blob: f0bb70a002f794fa749513ed18d356e13ea22056 [file] [log] [blame]
// Copyright 2017 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_DOCUMENT_SERVICE_H_
#define CONTENT_PUBLIC_BROWSER_DOCUMENT_SERVICE_H_
#include <cstdint>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/threading/thread_checker.h"
#include "content/public/browser/document_service_internal.h"
#include "content/public/browser/render_frame_host.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "url/origin.h"
namespace content {
enum class DocumentServiceDestructionReason : int {
// The mojo connection terminated.
kConnectionTerminated,
// The document pointed to by `render_frame_host()` is being destroyed.
kEndOfDocumentLifetime,
};
// Provides a safe alternative to mojo::MakeSelfOwnedReceiver<T>(...) for
// document-scoped Mojo interface implementations. Use of this helper prevents
// logic bugs when Mojo IPCs for `Interface` race against Mojo IPCs for
// navigation. One example of a past bug caused by this IPC race is
// https://crbug.com/769189, where an interface implementation performed a
// permission check using the wrong origin.
//
// Like C++ implementations owned by mojo::MakeSelfOwnedReceiver<T>(...), a
// subclass of DocumentService<Interface> will delete itself when the
// corresponding message pipe is disconnected by setting a disconnect handler on
// the mojo::Receiver<T>.
//
// In addition, a subclass of DocumentService<Interface> will also track
// the lifetime of the current document of the supplied RenderFrameHost and
// delete itself:
//
// - if the RenderFrameHost is deleted (for example, the <iframe> element the
// RenderFrameHost represents is removed from the DOM) or
// - if the RenderFrameHost commits a cross-document navigation. Specifically,
// DocumentService instances (and DocumentUserData instances)
// are deleted with the same timing, before the last committed origin and
// URL have been updated.
//
// When to use:
// Any Mojo interface implementation that references a RenderFrameHost, whether
// directly via a RenderFrameHost pointer, or indirectly, via the
// RenderFrameHost routing ID, should strongly consider:
//
// - `DocumentService` when there may be multiple instances per
// RenderFrameHost.
// - `DocumentUserData` when there should only be a single instance
// per RenderFrameHost.
//
// There are very few circumstances where a Mojo interface needs to be reused
// after a cross-document navigation.
template <typename Interface>
class DocumentService : public Interface, public internal::DocumentServiceBase {
public:
DocumentService(RenderFrameHost& render_frame_host,
mojo::PendingReceiver<Interface> pending_receiver)
: DocumentServiceBase(render_frame_host),
receiver_(this, std::move(pending_receiver)) {
// This is a developer error; it does not make sense to bind a
// DocumentService with a null PendingReceiver.
DUMP_WILL_BE_CHECK(receiver_.is_bound());
// |this| owns |receiver_|, so base::Unretained is safe.
receiver_.set_disconnect_handler(base::BindOnce(
[](DocumentService* document_service) {
document_service->WillBeDestroyed(
DocumentServiceDestructionReason::kConnectionTerminated);
document_service->ResetAndDeleteThis();
},
base::Unretained(this)));
}
~DocumentService() override {
// To avoid potential destruction order issues, implementations must use one
// of the *AndDeleteThis() methods below instead of writing `delete this`.
DUMP_WILL_BE_CHECK(!receiver_.is_bound());
}
// Subclasses may end their lifetime early by calling this method; `delete
// this` is not permitted for a `DocumentService` and will trigger the
// `DCHECK` in the destructor above.
//
// If there is a specific reason for self-deletion, one of the following may
// be more appropriate instead:
//
// - To report a failure when validating inputs received over IPC (e.g. the
// sender is malicious or buggy), use `ReportBadMessageAndDeleteThis()`.
//
// - Otherwise, to attach a specific numeric code to the `mojo::Receiver`
// reset, which will be passed to the other endpoint's disconnect with
// reason handler (if any), use `ResetWithReasonAndDeleteThis()`.
//
// The ordering of events is important: by resetting the mojo::Receiver before
// invoking the destructor, any pending Mojo reply callbacks can simply be
// dropped by an interface implementation, without forcing the implementation
// to (pointlessly) first run those reply callbacks.
//
// Marked final because there should be no real reason for a subclass to
// customize this behavior, and it allows for most `ResetAndDeleteThis()`
// calls to be devirtualized.
void ResetAndDeleteThis() final {
receiver_.reset();
delete this;
}
protected:
// `this` is promptly deleted if `render_frame_host_` commits a cross-document
// navigation, so it is always safe to simply call `GetLastCommittedOrigin()`
// and `GetLastCommittedURL()` directly.
const url::Origin& origin() const {
return render_frame_host().GetLastCommittedOrigin();
}
// Reports a bad message and deletes `this`.
//
// Prefer over `mojo::ReportBadMessage()`, since using this method avoids the
// need to run any pending reply callbacks with placeholder arguments.
NOT_TAIL_CALLED void ReportBadMessageAndDeleteThis(std::string_view error) {
receiver_.ReportBadMessage(error);
delete this;
}
// Resets the `mojo::Receiver` with a `reason` and `description` and deletes
// `this`.
void ResetWithReasonAndDeleteThis(uint32_t reason,
std::string_view description) {
receiver_.ResetWithReason(reason, description);
delete this;
}
// Returns a reference to the RenderFrameHost tracked by this object.
using DocumentServiceBase::render_frame_host;
// Subclasses can use this to check thread safety.
// For example: DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
THREAD_CHECKER(thread_checker_);
private:
// Note: `receiver_` is intentionally not exposed to implementations, since it
// is otherwise easy to write bugs that leak `this` by resetting the receiver
// without deleting `this`.
mojo::Receiver<Interface> receiver_;
};
} // namespace content
#endif // CONTENT_PUBLIC_BROWSER_DOCUMENT_SERVICE_H_