// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXTENSIONS_BROWSER_EXTENSION_API_FRAME_ID_MAP_H_
#define EXTENSIONS_BROWSER_EXTENSION_API_FRAME_ID_MAP_H_

#include <list>
#include <map>
#include <memory>

#include "base/callback.h"
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/synchronization/lock.h"

namespace content {
class NavigationHandle;
class RenderFrameHost;
class WebContents;
}  // namespace content

namespace extensions {

class ExtensionApiFrameIdMapHelper {
 public:
  virtual void GetTabAndWindowId(content::RenderFrameHost* rfh,
                                 int* tab_id_out,
                                 int* window_id_out) = 0;
  virtual ~ExtensionApiFrameIdMapHelper() {}
};

// Extension frame IDs are exposed through the chrome.* APIs and have the
// following characteristics:
// - The top-level frame has ID 0.
// - Any child frame has a positive ID.
// - A non-existant frame has ID -1.
// - They are only guaranteed to be unique within a tab.
// - The ID does not change during the frame's lifetime and is not re-used after
//   the frame is removed. The frame may change its current RenderFrameHost over
//   time, so multiple RenderFrameHosts may map to the same extension frame ID.
//
// This class provides a mapping from a (render_process_id, frame_routing_id)
// pair to a FrameData struct, which includes the extension's frame id (as
// described above), the parent frame id, and the tab id (the latter can be
// invalid if it's not in a tab).
//
// Unless stated otherwise, the methods can only be called on the UI thread.
//
// The non-static methods of this class use an internal cache. This cache is
// used to minimize IO->UI->IO round-trips of GetFrameIdOnIO. If the cost of
// attaching FrameTreeNode IDs to requests is negligible (crbug.com/524228),
// then we can remove all key caching and remove the cache from this class.
// TODO(robwu): Keep an eye on crbug.com/524228 and act upon the outcome.
// TODO(devlin): Also keep an eye on FrameIOData to see if that could help.
class ExtensionApiFrameIdMap {
 public:
  // The data for a RenderFrame. Every RenderFrameIdKey maps to a FrameData.
  struct FrameData {
    FrameData();
    FrameData(int frame_id, int parent_frame_id, int tab_id, int window_id);

    // The extension API frame ID of the frame.
    int frame_id;

    // The extension API frame ID of the parent of the frame.
    int parent_frame_id;

    // The id of the tab that the frame is in, or -1 if the frame isn't in a
    // tab.
    int tab_id;

    // The id of the window that the frame is in, or -1 if the frame isn't in a
    // window.
    int window_id;
  };

  using FrameDataCallback = base::Callback<void(const FrameData&)>;

  // An invalid extension API frame ID.
  static const int kInvalidFrameId;

  // Extension API frame ID of the top-level frame.
  static const int kTopFrameId;

  static ExtensionApiFrameIdMap* Get();

  // Get the extension API frame ID for |rfh|.
  static int GetFrameId(content::RenderFrameHost* rfh);

  // Get the extension API frame ID for |navigation_handle|.
  static int GetFrameId(content::NavigationHandle* navigation_handle);

  // Get the extension API frame ID for the parent of |rfh|.
  static int GetParentFrameId(content::RenderFrameHost* rfh);

  // Get the extension API frame ID for the parent of |navigation_handle|.
  static int GetParentFrameId(content::NavigationHandle* navigation_handle);

  // Find the current RenderFrameHost for a given WebContents and extension
  // frame ID.
  // Returns nullptr if not found.
  static content::RenderFrameHost* GetRenderFrameHostById(
      content::WebContents* web_contents,
      int frame_id);

  // Runs |callback| with a result that is equivalent to calling GetFrameData()
  // on the UI thread. Thread hopping is minimized if possible. Callbacks for
  // the same |render_process_id| and |frame_routing_id| are guaranteed to be
  // run in order. The order of other callbacks is undefined.
  void GetFrameDataOnIO(int render_process_id,
                        int frame_routing_id,
                        const FrameDataCallback& callback);

  // Attempts to populate |frame_data_out| with the FrameData for the specified
  // frame, but only does so if the data is already cached. Returns true if
  // cached frame data was found.
  bool GetCachedFrameDataOnIO(int render_process_id,
                              int frame_routing_id,
                              FrameData* frame_data_out);

  // Retrieves the FrameData for a given |rfh|. The map may be updated with the
  // result if the map did not contain the FrameData before the lookup.
  // If |rfh| is nullptr, then the map is not modified.
  FrameData GetFrameData(content::RenderFrameHost* rfh) WARN_UNUSED_RESULT;

  // Looks up the FrameData and stores it in the map. This method should be
  // called as early as possible, e.g. in a
  // WebContentsObserver::RenderFrameCreated notification.
  void CacheFrameData(content::RenderFrameHost* rfh);

  // Removes the FrameData mapping for a given frame. This method can be called
  // at any time, but it is typically called when a frame is destroyed.
  // If this method is not called, the cached mapping for the frame is retained
  // forever.
  void RemoveFrameData(content::RenderFrameHost* rfh);

  // Updates the tab and window id for the given RenderFrameHost, if any exists.
  void UpdateTabAndWindowId(int tab_id,
                            int window_id,
                            content::RenderFrameHost* rfh);

 protected:
  friend struct base::DefaultLazyInstanceTraits<ExtensionApiFrameIdMap>;

  // A set of identifiers that uniquely identifies a RenderFrame.
  struct RenderFrameIdKey {
    RenderFrameIdKey();
    RenderFrameIdKey(int render_process_id, int frame_routing_id);

    // The process ID of the renderer that contains the RenderFrame.
    int render_process_id;

    // The routing ID of the RenderFrame.
    int frame_routing_id;

    bool operator<(const RenderFrameIdKey& other) const;
    bool operator==(const RenderFrameIdKey& other) const;
  };

  struct FrameDataCallbacks {
    FrameDataCallbacks();
    FrameDataCallbacks(const FrameDataCallbacks& other);
    ~FrameDataCallbacks();

    // This is a std::list so that iterators are not invalidated when the list
    // is modified during an iteration.
    std::list<FrameDataCallback> callbacks;

    // To avoid re-entrant processing of callbacks.
    bool is_iterating;
  };

  using FrameDataMap = std::map<RenderFrameIdKey, FrameData>;
  using FrameDataCallbacksMap = std::map<RenderFrameIdKey, FrameDataCallbacks>;

  ExtensionApiFrameIdMap();
  virtual ~ExtensionApiFrameIdMap();

  // Determines the value to be stored in |frame_data_map_| for a given key.
  // This method is only called when |key| is not in |frame_data_map_|.
  // virtual for testing.
  virtual FrameData KeyToValue(const RenderFrameIdKey& key) const;

  // Looks up the data for the given |key| and adds it to the |frame_data_map_|.
  // |is_from_io| indicates whether the lookup originated from the IO thread.
  FrameData LookupFrameDataOnUI(const RenderFrameIdKey& key, bool is_from_io);

  // Called as soon as the frame data is found for the given |key|, and runs all
  // queued callbacks with |cached_frame_data|.
  void ReceivedFrameDataOnIO(const RenderFrameIdKey& key,
                             const FrameData& cached_frame_data);

  // Implementation of CacheFrameData(RenderFrameHost), separated for testing.
  void CacheFrameData(const RenderFrameIdKey& key);

  // Implementation of RemoveFrameData(RenderFrameHost), separated for testing.
  void RemoveFrameData(const RenderFrameIdKey& key);

  std::unique_ptr<ExtensionApiFrameIdMapHelper> helper_;

  // Queued callbacks for use on the IO thread.
  FrameDataCallbacksMap callbacks_map_;

  // This map is only modified on the UI thread and is used to minimize the
  // number of thread hops on the IO thread.
  FrameDataMap frame_data_map_;

  // This lock protects |frame_data_map_| from being concurrently written on the
  // UI thread and read on the IO thread.
  base::Lock frame_data_map_lock_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionApiFrameIdMap);
};

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_EXTENSION_API_FRAME_ID_MAP_H_
