// 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 <utility>

#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/strings/string_piece.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| 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`.
    DCHECK(!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(base::StringPiece error) {
    receiver_.ReportBadMessage(error);
    delete this;
  }

  // Resets the `mojo::Receiver` with a `reason` and `description` and deletes
  // `this`.
  void ResetWithReasonAndDeleteThis(uint32_t reason,
                                    base::StringPiece 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_