// Copyright 2019 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_BACK_FORWARD_CACHE_H_
#define CONTENT_PUBLIC_BROWSER_BACK_FORWARD_CACHE_H_

#include <cstdint>
#include <map>
#include <optional>
#include <set>

#include "arkweb/build/features/features.h"
#include "content/common/content_export.h"
#include "content/public/browser/global_routing_id.h"
#include "services/metrics/public/cpp/ukm_source_id.h"

#if BUILDFLAG(ARKWEB_BFCACHE)
#include "base/time/time.h"
#endif

namespace content {

class Page;
class RenderFrameHost;

// Public API for the BackForwardCache.
//
// After the user navigates away from a document, the old one might go into the
// frozen state and will be kept in the cache. It can potentially be reused
// at a later time if the user navigates back.
//
// Not all documents can or will be cached. You should not assume a document
// will be cached.
//
// All methods of this class should be called from the UI thread.
class CONTENT_EXPORT BackForwardCache {
 public:
  // LINT.IfChange(NotRestoredReason)
  enum class NotRestoredReason : uint8_t {
    kMinValue = 0,
    kNotPrimaryMainFrame = 0,
    // BackForwardCache is disabled due to low memory device, base::Feature or
    // command line. Note that the more specific NotRestoredReasons
    // kBackForwardCacheDisabledByLowMemory and
    // kBackForwardCacheDisabledByCommandLine will also be set as other reasons
    // along with this when appropriate.
    kBackForwardCacheDisabled = 1,
    kRelatedActiveContentsExist = 2,
    kHTTPStatusNotOK = 3,
    kSchemeNotHTTPOrHTTPS = 4,
    // DOMContentLoaded event has not yet fired. This means that deferred
    // scripts have not run yet and pagehide/pageshow event handlers may not be
    // installed yet.
    kLoading = 5,
    // 6: WasGrantedMediaAccess is no longer blocking.
    kBlocklistedFeatures = 7,
    kDisableForRenderFrameHostCalled = 8,
    kDomainNotAllowed = 9,
    kHTTPMethodNotGET = 10,
    kSubframeIsNavigating = 11,
    kTimeout = 12,
    kCacheLimit = 13,
    kJavaScriptExecution = 14,
    kRendererProcessKilled = 15,
    kRendererProcessCrashed = 16,
    // 17: Dialogs are no longer a reason to exclude from BackForwardCache
    // 18: GrantedMediaStreamAccess is no longer blocking.
    // 19: kSchedulerTrackedFeatureUsed is no longer used.
    kConflictingBrowsingInstance = 20,
    kCacheFlushed = 21,
    kServiceWorkerVersionActivation = 22,
    kSessionRestored = 23,
    kUnknown = 24,
    kServiceWorkerPostMessage = 25,
    kEnteredBackForwardCacheBeforeServiceWorkerHostAdded = 26,
    // 27: kRenderFrameHostReused_SameSite was removed.
    // 28: kRenderFrameHostReused_CrossSite was removed.
    // 29: kNotMostRecentNavigationEntry was removed.
    kServiceWorkerClaim = 30,
    kIgnoreEventAndEvict = 31,
    kHaveInnerContents = 32,
    kTimeoutPuttingInCache = 33,
    // BackForwardCache is disabled due to low memory device.
    kBackForwardCacheDisabledByLowMemory = 34,
    // BackForwardCache is disabled due to command-line switch (may include
    // cases where the embedder disabled it due to, e.g., enterprise policy).
    kBackForwardCacheDisabledByCommandLine = 35,
    // 36: kFrameTreeNodeStateReset was removed.
    // 37: kNetworkRequestDatapipeDrained = 37 was removed and broken into 43
    // and 44.
    kNetworkRequestRedirected = 38,
    kNetworkRequestTimeout = 39,
    kNetworkExceedsBufferLimit = 40,
    kNavigationCancelledWhileRestoring = 41,
    // 42: kBackForwardCacheDisabledForPrerender was removed and merged into 0.
    kUserAgentOverrideDiffers = 43,
    // 44: kNetworkRequestDatapipeDrainedAsDatapipe was removed now that
    // ScriptStreamer is supported.
    kNetworkRequestDatapipeDrainedAsBytesConsumer = 45,
    kForegroundCacheLimit = 46,
    kBrowsingInstanceNotSwapped = 47,
    kBackForwardCacheDisabledForDelegate = 48,
    // 49: kOptInUnloadHeaderNotPresent was removed as the experiments ended.
    kUnloadHandlerExistsInMainFrame = 50,
    kUnloadHandlerExistsInSubFrame = 51,
    kServiceWorkerUnregistration = 52,
    kCacheControlNoStore = 53,
    kCacheControlNoStoreCookieModified = 54,
    kCacheControlNoStoreHTTPOnlyCookieModified = 55,
    // 56: kNoResponseHead was fixed.
    // 57: kActivationNavigationsDisallowedForBug1234857 was fixed.
    kErrorDocument = 58,
    // 59: kFencedFramesEmbedder was removed.
    kCookieDisabled = 60,
    kHTTPAuthRequired = 61,
    kCookieFlushed = 62,
    kBroadcastChannelOnMessage = 63,
    kWebViewSettingsChanged = 64,
    kWebViewJavaScriptObjectChanged = 65,
    kWebViewMessageListenerInjected = 66,
    kWebViewSafeBrowsingAllowlistChanged = 67,
    kWebViewDocumentStartJavascriptChanged = 68,
    kCacheControlNoStoreDeviceBoundSessionTerminated = 69,
    kCacheLimitPrunedOnModerateMemoryPressure = 70,
    kCacheLimitPrunedOnCriticalMemoryPressure = 71,
    kSharedWorkerMessage = 72,
    kSharedWorkerWithNoActiveClient = 73,
    kMaxValue = kSharedWorkerWithNoActiveClient,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/navigation/enums.xml:BackForwardCacheNotRestoredReason)

  // Back/forward cache can be disabled from within content and also from
  // embedders. This means we cannot have a unified enum that covers reasons
  // from different layers. Instead we namespace the reasons and allow each
  // source to manage its own enum. The previous approach was to use a hash of
  // the string for logging but this made it hard identify the reasons in the
  // logged data and also meant there was no control over new uses of the API.
  //
  // The logged value is |enum_value + source_type << 16|.
  enum class DisabledSource {
    // We reserve 0 because because the previous approach just used the strings
    // hashed to uint16.
    kLegacy = 0,
    kTesting = 1,
    kContent = 2,
    kEmbedder = 3,
  };
  typedef uint16_t DisabledReasonType;
  static const uint16_t kDisabledReasonTypeBits = 16;

  // Represents a reason to disable back-forward cache, given by a |source|.
  // |context| is arbitrary context that will be preserved and passed through,
  // e.g. an extension ID responsible for disabling BFCache that can be shown in
  // passed devtools. It preserves the |description| and |context| that
  // accompany it, however they are ignored for <, == and !=.
  struct CONTENT_EXPORT DisabledReason {
    DisabledReason(BackForwardCache::DisabledSource source,
                   BackForwardCache::DisabledReasonType id,
                   std::string description,
                   std::string context,
                   std::string report_string);
    DisabledReason(const DisabledReason&);

    const BackForwardCache::DisabledSource source;
    const BackForwardCache::DisabledReasonType id;
    const std::string description;
    const std::string context;
    // Report string used for NotRestoredReasons API. This will be brief and
    // will mask extension related reasons as "Extensions".
    const std::string report_string;

    std::weak_ordering operator<=>(const DisabledReason&) const;
    bool operator==(const DisabledReason&) const;
  };

  // Prevents the `render_frame_host` from entering the BackForwardCache. A
  // RenderFrameHost can only enter the BackForwardCache if the main one and all
  // its children can. This action can not be undone. Any document that is
  // assigned to this same RenderFrameHost in the future will not be cached
  // either. In practice this is not a big deal as only navigations that use a
  // new frame can be cached.
  //
  // This might be needed for example by components that listen to events via a
  // WebContentsObserver and keep some sort of per frame state, as this state
  // might be lost and not be recreated when navigating back.
  //
  // If the page is already in the cache an eviction is triggered.
  //
  // `render_frame_host`: non-null.
  // `reason`: Describes who is disabling this and why.
  // `source_id`: see
  // `BackForwardCacheCanStoreDocumentResult::DisabledReasonsMap` for what it
  // means and when it's set.
  static void DisableForRenderFrameHost(
      RenderFrameHost* render_frame_host,
      DisabledReason reason,
      std::optional<ukm::SourceId> source_id = std::nullopt);

  // Helper function to be used when it is not always possible to guarantee the
  // `render_frame_host` to be still alive when this is called. In this case,
  // its `id` can be used.
  // For what `source_id` means and when it's set, see
  // `BackForwardCacheCanStoreDocumentResult::DisabledReasonsMap`.
  static void DisableForRenderFrameHost(
      GlobalRenderFrameHostId id,
      DisabledReason reason,
      std::optional<ukm::SourceId> source_id = std::nullopt);

  // Helper function to be used when the input |page| has seen any form data
  // associated. This state will be set on the BackForwardCacheMetrics
  // associated with the main frame, is not persisted across session restores,
  // and only set in Android Custom tabs for now.
  // TODO(crbug.com/40251494): Set this boolean for all platforms.
  static void SetHadFormDataAssociated(Page& page);

  // List of reasons the BackForwardCache was disabled for a specific test. If a
  // test needs to be disabled for a reason not covered below, please add to
  // this enum.
  enum DisableForTestingReason {
    // The test has expectations that won't make sense if caching is enabled.
    //
    // One alternative to disabling BackForwardCache is to make the test's logic
    // conditional, based on whether or not BackForwardCache is enabled.
    //
    // You should also consider whether it would make sense to instead split
    // into two tests, one using a cacheable page, and one using an uncacheable
    // page.
    //
    // Even though BackForwardCache is already enabled by default, it is not
    // guaranteed to preserve and cache the previous document on every
    // navigation, and even if it does, it is still possible for a cached
    // document to get discarded without it ever getting restored, so not every
    // history navigation will restore a document from the back/forward cache.
    // Thus, testing cases where a document does not get preserved and cached
    // on navigation or not restored on history navigation is completely valid.
    TEST_REQUIRES_NO_CACHING,

    // Unload events never fire for documents that are put into the
    // BackForwardCache. This is by design, as there is never an appropriate
    // moment to fire unload if the document is cached.
    // In short, this is because:
    //
    // * We can't fire unload when going into the cache, because it may be
    // destructive, and put the document into an unknown/bad state. Pages can
    // also be cached and restored multiple times, and we don't want to invoke
    // unload more than once.
    //
    // * We can't fire unload when the document is evicted from the cache,
    // because at that point we don't want to run javascript for privacy and
    // security reasons.
    //
    // An alternative to disabling the BackForwardCache, is to have the test
    // load a page that is ineligible for caching (e.g. due to an unsupported
    // feature).
    TEST_USES_UNLOAD_EVENT,

    // This test expects that same-site navigations won't result in a
    // RenderFrameHost / RenderFrame / `blink::WebView` / RenderWidget change.
    // But when same-site BackForwardCache is enabled, the change usually does
    // happen. Even so, there will still be valid navigations that don't result
    // in those objects changing, so we should keep the test as is, just with
    // BackForwardCache disabled.
    TEST_ASSUMES_NO_RENDER_FRAME_CHANGE,
  };

  // Returns true if BackForwardCache is enabled.
  static bool IsBackForwardCacheFeatureEnabled();

  // TODO(crbug.com/345117894): Add reasons to all the callsites of Flush().
  // Evict all entries from the BackForwardCache with reason kCacheFlushed.
  virtual void Flush() = 0;

  // Evict all entries from the BackForwardCache with specific reason.
  virtual void Flush(NotRestoredReason reason) = 0;

  // Evict back/forward cache entries from the least recently used ones until
  // the cache is within the given size limit.
  // Returns the total number of BFCache entries before the pruning,
  virtual size_t Prune(size_t limit, NotRestoredReason reason) = 0;

  // Sets limits on cache size and time to live, which will take precedent over
  // the default limits.
  virtual void SetEmbedderSuppliedCacheSize(size_t cache_size) = 0;
  virtual void SetEmbedderSuppliedTimeToLive(base::TimeDelta time_to_live) = 0;

  // Disables the BackForwardCache so that no documents will be stored/served.
  // This allows tests to "force" not using the BackForwardCache, this can be
  // useful when:
  // * Tests rely on a new document being loaded.
  // * Tests want to test this case specifically.
  // Callers should pass an accurate |reason| to make future triaging of
  // disabled tests easier.
  //
  // Note: It's preferable to make tests BackForwardCache compatible
  // when feasible, rather than using this method. Also please consider whether
  // you actually should have 2 tests, one with the document cached
  // (BackForwardCache enabled), and one without.
  virtual void DisableForTesting(DisableForTestingReason reason) = 0;

#if BUILDFLAG(ARKWEB_BFCACHE)
  virtual size_t GetStoredEntriesNumber() = 0;
  virtual void SetCacheSize(int size) = 0;
  virtual int ArkWebGetCacheSize() const = 0;
  virtual void SetTimeToLive(int timeToLive) = 0;
  virtual int ArkWebGetTimeToLive() const = 0;
  virtual base::TimeDelta ArkWebGetTimeToLiveInBackForwardCache() = 0;
#endif

 protected:
  BackForwardCache() = default;
};

}  // namespace content

#endif  // CONTENT_PUBLIC_BROWSER_BACK_FORWARD_CACHE_H_