// 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_PAGE_USER_DATA_H_
#define CONTENT_PUBLIC_BROWSER_PAGE_USER_DATA_H_

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ref.h"
#include "base/supports_user_data.h"
#include "content/public/browser/page.h"

namespace content {

// A base class for classes attached to, and scoped to, the lifetime of a
// content::Page.

// PageUserData is created when a user of an API inherits this class and calls
// CreateForPage.
//
// PageUserData is similar to DocumentUserData, but is attached to the
// page (1:1 with main document) instead of any document. Prefer using
// PageUserData for main-document-only data.
//
// Example usage of PageUserData:
//
// --- in foo_page_helper.h ---
// class FooPageHelper : public content::PageUserData<FooPageHelper> {
//  public:
//   ~FooPageHelper() override;
//
//   // ... more public stuff here ...
//
//  private:
//   explicit FooPageHelper(content::Page& page);
//
//   friend PageUserData;
//   PAGE_USER_DATA_KEY_DECL();
//
//   // ... more private stuff here ...
// };
//
// --- in foo_page_helper.cc ---
// PAGE_USER_DATA_KEY_IMPL(FooPageHelper);
//
// FooPageHelper::FooPageHelper(content::Page& page)
//     : PageUserData(page) {}
//
// FooPageHelper::~FooPageHelper() {}
//
template <typename T>
class PageUserData : public base::SupportsUserData::Data {
 public:
  template <typename... Args>
  static void CreateForPage(Page& page, Args&&... args) {
    if (!GetForPage(page)) {
      T* data = new T(page, std::forward<Args>(args)...);
      page.SetUserData(UserDataKey(), base::WrapUnique(data));
    }
  }

  // TODO(crbug.com/1335845): Due to a unresolved bug, this can return nullptr
  // even after CreateForPage() or GetOrCreateForPage() has been called.
  static T* GetForPage(Page& page) {
    return static_cast<T*>(page.GetUserData(UserDataKey()));
  }

  static T* GetOrCreateForPage(Page& page) {
    if (auto* data = GetForPage(page)) {
      return data;
    }

    CreateForPage(page);
    return GetForPage(page);
  }

  static void DeleteForPage(Page& page) {
    DCHECK(GetForPage(page));
    page.RemoveUserData(UserDataKey());
  }

  Page& page() const { return *page_; }

  static const void* UserDataKey() { return &T::kUserDataKey; }

 protected:
  explicit PageUserData(Page& page) : page_(page) {}

 private:
  // Page associated with subclass which inherits this PageUserData.
  const raw_ref<Page> page_;
};

// Users won't be able to instantiate the template if they miss declaring the
// user data key.
// This macro declares a static variable inside the class that inherits from
// PageUserData. The address of this static variable is used as
// the key to store/retrieve an instance of the class.
#define PAGE_USER_DATA_KEY_DECL() static const int kUserDataKey = 0

// This macro instantiates the static variable declared by the previous macro.
// It must live in a .cc file to ensure that there is only one instantiation
// of the static variable.
#define PAGE_USER_DATA_KEY_IMPL(Type) const int Type::kUserDataKey

}  // namespace content

#endif  // CONTENT_PUBLIC_BROWSER_PAGE_USER_DATA_H_