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

content / public / browser / document_service.h [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 "base/types/pass_key.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 {

class DocumentAssociatedData;

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, subclasses must use one of
    // the *AndDeleteThis() methods below instead of using `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.
  void ResetAndDeleteThis() {
    InternalUnregister(base::PassKey<DocumentService>());
    receiver_.reset();
    delete this;
  }

  // Internal implementation helper:
  void ResetAndDeleteThisInternal(base::PassKey<DocumentAssociatedData>) 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) {
    InternalUnregister(base::PassKey<DocumentService>());
    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_