// 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_BROWSER_FENCED_FRAME_FENCED_FRAME_URL_MAPPING_H_
#define CONTENT_BROWSER_FENCED_FRAME_FENCED_FRAME_URL_MAPPING_H_

#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "content/browser/fenced_frame/fenced_frame_config.h"
#include "content/browser/fenced_frame/fenced_frame_reporter.h"
#include "content/common/content_export.h"
#include "url/gurl.h"



namespace content {

class FencedFrameURLMappingTestPeer;

using SharedStorageReportingMap = base::flat_map<std::string, ::GURL>;

// Keeps a mapping of fenced frames URN:UUID and URL. Also keeps a set of
// pending mapped URN:UUIDs to support asynchronous mapping. See
// https://github.com/WICG/fenced-frame/blob/master/explainer/opaque_src.md
// TODO(crbug.com/40252330): Add methods for:
// 1. generating the pending config.
// 2. finalizing the pending config.
class CONTENT_EXPORT FencedFrameURLMapping {
 public:
  // The runURLSelectionOperation's url mapping result. It contains the mapped
  // url, the `SharedStorageBudgetMetadata`, and a FencedFrameReporter.
  struct CONTENT_EXPORT SharedStorageURNMappingResult {
    GURL mapped_url;
    SharedStorageBudgetMetadata budget_metadata;
    scoped_refptr<FencedFrameReporter> fenced_frame_reporter;
    SharedStorageURNMappingResult();
    SharedStorageURNMappingResult(
        GURL mapped_url,
        SharedStorageBudgetMetadata budget_metadata,
        scoped_refptr<FencedFrameReporter> fenced_frame_reporter);
    ~SharedStorageURNMappingResult();
  };

  class MappingResultObserver {
   public:
    virtual ~MappingResultObserver() = default;

    // Called as soon as the URN mapping decision is made.
    //
    // On success, `properties` will be populated with the properties bound to
    // the urn:uuid.
    virtual void OnFencedFrameURLMappingComplete(
        const std::optional<FencedFrameProperties>& properties) = 0;
  };

  FencedFrameURLMapping();
  ~FencedFrameURLMapping();
  FencedFrameURLMapping(FencedFrameURLMapping&) = delete;
  FencedFrameURLMapping& operator=(FencedFrameURLMapping&) = delete;

  // Imports URN to URL mappings from passed in mapping. Generally only called
  // once per PendingAdComponentsMap, on the mapping associated with a frame
  // being navigated to a URN. Calling this twice with the same
  // PendingAdComponentsMap on the same FencedFrameURLMapping will do nothing.
  void ImportPendingAdComponents(
      const std::vector<std::pair<GURL, FencedFrameConfig>>& components);

  // Generate a URN that is not yet mapped to a URL.
  // For Shared Storage, it will be returned by
  // `sharedStorage.selectURL()` before the URL selection decision
  // is made.
  //
  // This method will fail and return std::nullopt if number of
  // mappings has reached limit. `selectURL()` will be terminated
  // up front and an error will be reported.
  std::optional<GURL> GeneratePendingMappedURN();

  // Register an observer for `urn_uuid`. The observer will be notified with the
  // mapping result and will be auto unregistered. If `urn_uuid` already exists
  // in `urn_uuid_to_url_map_`, or if it is not recognized at all, the observer
  // will be notified synchronously; if the mapping is pending (i.e. `urn_uuid`
  // exists in `pending_urn_uuid_to_url_map_`), the observer will be notified
  // asynchronously as soon as when the mapping decision is made.
  void ConvertFencedFrameURNToURL(const GURL& urn_uuid,
                                  MappingResultObserver* observer);

  // Explicitly unregister the observer for `urn_uuid`. This is only needed if
  // the observer is going to become invalid and the mapping is still pending.
  void RemoveObserverForURN(const GURL& urn_uuid,
                            MappingResultObserver* observer);

  // Called when the shared storage mapping decision is made for `urn_uuid`.
  // Should only be invoked on a `urn_uuid` pending to be mapped. This method
  // will trigger the observers' OnFencedFrameURLMappingComplete() method
  // associated with the `urn_uuid`, unregister those observers, and move the
  // `urn_uuid` from `pending_urn_uuid_to_url_map_` to `urn_uuid_to_url_map_`.
  // If the resolved URL is fenced-frame-compatible, the return value is the
  // populated fenced frame config. It is used to notify the observers in shared
  // storage worklet host manager. Tests can then obtain the populated fenced
  // frame configs from the observers.
  // Otherwise this method returns an std::nullopt.
  std::optional<FencedFrameConfig> OnSharedStorageURNMappingResultDetermined(
      const GURL& urn_uuid,
      const SharedStorageURNMappingResult& mapping_result);

  // Adds a mapping for |url| to a URN:UUID that will be generated by this
  // function. Should only be invoked with a valid URL which is one of the
  // "potentially trustworthy URLs".
  // Mapping will not be added and return std::nullopt if number of mappings
  // has reached limit. Enforcing a limit on number of mappings prevents
  // excessive memory consumption.
  // `fenced_frame_reporter` will contain a `FencedFrameReporter` to associate
  // with the created URN. It may be nullptr.
  std::optional<GURL> AddFencedFrameURLForTesting(
      const GURL& url,
      scoped_refptr<FencedFrameReporter> fenced_frame_reporter = nullptr);

  // Erases the urn_uuid_to_url_map_ and the pending_urn_uuid_to_url_map_.
  void ClearMapForTesting();

  // Return the `SharedStorageBudgetMetadata` associated with `urn_uuid`, or
  // nullptr if there's no metadata associated (i.e. `urn_uuid` was not
  // originated from shared storage). Precondition: `urn_uuid` exists in
  // `urn_uuid_to_url_map_`.
  //
  // This method will be called during the lifetime of a `NavigationRequest`
  // object, to associate the budget metadata to each relevant committed
  // document. A non-null returned pointer will stay valid during the
  // `FencedFrameURLMapping`'s (thus the page's) lifetime, and a page will
  // outlive any `NavigationRequest` occurring in fenced frames in the page,
  // thus it's safe for a `NavigationRequest` to store a pointer to this.
  SharedStorageBudgetMetadata* GetSharedStorageBudgetMetadataForTesting(
      const GURL& urn_uuid);

 private:
  friend class FencedFrameURLMappingTestPeer;

  using UrnUuidToUrlMap = std::map<GURL, FencedFrameConfig>;

  // The maximum number of urn mappings.
  static constexpr size_t kMaxUrnMappingSize = 65536;

  // Adds an entry to `urn_uuid_to_url_map_` for `url`, generating a unique URN
  // as the key. Insertion fails if number of entries has reached the limit.
  std::optional<UrnUuidToUrlMap::iterator> AddMappingForUrl(const GURL& url);

  bool IsMapped(const GURL& urn_uuid) const;
  bool IsPendingMapped(const GURL& urn_uuid) const;
  // Return true if number of mappings in `urn_uuid_to_url_map_` and
  // `pending_urn_uuid_to_url_map_` has reached the limit specified as
  // `kMaxUrnMappingSize`.
  bool IsFull() const;

  // The URNs that are already mapped to URLs, along with their mapping info.
  UrnUuidToUrlMap urn_uuid_to_url_map_;

  // The URNs that are not yet mapped to URLs, along with the associated
  // observers to be notified when the mapping decision is made.
  std::map<GURL, std::set<raw_ptr<MappingResultObserver>>>
      pending_urn_uuid_to_url_map_;
};

}  // namespace content

#endif  // CONTENT_BROWSER_FENCED_FRAME_FENCED_FRAME_URL_MAPPING_H_
