chromium/chrome/browser/offline_pages/android/auto_fetch_page_load_watcher.h

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

#ifndef CHROME_BROWSER_OFFLINE_PAGES_ANDROID_AUTO_FETCH_PAGE_LOAD_WATCHER_H_
#define CHROME_BROWSER_OFFLINE_PAGES_ANDROID_AUTO_FETCH_PAGE_LOAD_WATCHER_H_

#include <map>
#include <memory>
#include <set>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/offline_pages/core/auto_fetch.h"
#include "components/offline_pages/core/background/request_coordinator.h"
#include "components/offline_pages/core/client_id.h"
#include "url/gurl.h"

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

namespace offline_pages {
class SavePageRequest;
class RequestCoordinator;

// Manages showing the in-progress notification.
class AutoFetchNotifier {
 public:
  virtual ~AutoFetchNotifier() {}
  // Ensures that the in-progress notification is showing with the appropriate
  // request count.
  virtual void NotifyInProgress(int in_flight_count) {}
  // Update the request count if the in-progress notification is already
  // showing. This won't trigger showing the notification if it's not already
  // shown. If |in_flight_count| is 0, the notification will be hidden.
  virtual void InProgressCountChanged(int in_flight_count) {}
};

// Types and functions internal to AutoFetchPageLoadWatcher. Included in the
// header for testing.
namespace auto_fetch_internal {

// Information about an Android browser tab.
struct TabInfo {
  int android_tab_id = 0;
  GURL current_url;
};

// Interface to Android tabs used by |AutoFetchPageLoadWatcher|. This is the
// real implementation, methods are virtual for testing only.
class AndroidTabFinder {
 public:
  virtual ~AndroidTabFinder();
  // Returns a mapping of Android tab ID to TabInfo.
  virtual std::map<int, TabInfo> FindAndroidTabs(
      std::vector<int> android_tab_ids);
  virtual std::optional<TabInfo> FindNavigationTab(
      content::WebContents* web_contents);
};

// Information about an auto-fetch |SavePageRequest|.
struct RequestInfo {
  int64_t request_id;
  GURL url;
  SavePageRequest::AutoFetchNotificationState notification_state;
  auto_fetch::ClientIdMetadata metadata;
};

std::optional<RequestInfo> MakeRequestInfo(const SavePageRequest& request);

// |AutoFetchPageLoadWatcher|'s more unit-testable internal implementation.
// This class was designed to have few dependencies to make testing more
// tractable. Events are communicated to |InternalImpl| through its public
// member functions, and functions are injected through |AutoFetchNotifier|,
// |Delegate|, and |AndroidTabFinder|.
class InternalImpl {
 public:
  // Injected functions, implemented by |AutoFetchPageLoadWatcher|.
  // We need this because we can't call these functions directly.
  class Delegate {
   public:
    virtual ~Delegate() {}
    // Sets the notification state of a request to
    // |SavePageRequest::AutoFetchNotificationState::kShown|. Results in a call
    // to |SetNotificationStateComplete|.
    virtual void SetNotificationStateToShown(int64_t request_id) {}
    // Removes all |SavePageRequest|s with the given IDs.
    virtual void RemoveRequests(const std::vector<int64_t>& request_ids) {}
  };

  InternalImpl(AutoFetchNotifier* notifier,
               Delegate* delegate,
               std::unique_ptr<AndroidTabFinder> tab_finder);
  ~InternalImpl();

  // Request state change methods.
  void RequestListInitialized(std::vector<RequestInfo> requests);
  void RequestAdded(RequestInfo info);
  void RequestRemoved(RequestInfo info);
  void SetNotificationStateComplete(int64_t request_id, bool success);

  // Navigation methods.
  void SuccessfulPageNavigation(const GURL& url);
  void NavigationFrom(const GURL& previous_url,
                      content::WebContents* web_contents);

  // Tab event methods.
  void TabClosed(int android_tab_id);
  void TabModelReady();

  base::WeakPtr<InternalImpl> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

  std::vector<GURL>& pages_loaded_before_observer_ready_for_testing() {
    return pages_loaded_before_observer_ready_;
  }

 private:
  void SetNotificationStateToShown(int64_t request_id);
  void UpdateNotificationStateForAllRequests();

  raw_ptr<AutoFetchNotifier> notifier_;
  raw_ptr<Delegate> delegate_;
  std::unique_ptr<AndroidTabFinder> tab_finder_;
  std::vector<RequestInfo> requests_;
  // Tracks whether |RequestListInitialized| has been called. If false,
  // |RequestAdded| and |RequestRemoved| should be ignored, as per the
  // documentation in |RequestCoordinator::Observer|.
  bool requests_initialized_ = false;
  bool tab_model_ready_ = false;
  std::vector<GURL> pages_loaded_before_observer_ready_;

  base::WeakPtrFactory<InternalImpl> weak_ptr_factory_{this};
};

}  // namespace auto_fetch_internal

// Watches for page loads and |RequestCoordinator| requests.
// Given an active auto-fetch request for <tab, URL>:
// - If URL is loaded successfully on tab, cancel the auto-fetch request.
// - If a different URL is loaded successfully on tab, trigger the in-progress
//   notification.
// - If tab is closed, trigger the in-progress notification.
// - If an auto-fetch request is removed, update the in-progress notification's
//   displayed request count.
//
// Implementation note:
// This class simply observes events and passes them down to |InternalImpl|
// for processing. All code here is run on the UI thread.
class AutoFetchPageLoadWatcher
    : public RequestCoordinator::Observer,
      public auto_fetch_internal::InternalImpl::Delegate {
 public:
  using AndroidTabFinder = auto_fetch_internal::AndroidTabFinder;

  static void CreateForWebContents(content::WebContents* web_contents);

  AutoFetchPageLoadWatcher(AutoFetchNotifier* notifier,
                           RequestCoordinator* request_coordinator,
                           std::unique_ptr<AndroidTabFinder> tab_finder);

  AutoFetchPageLoadWatcher(const AutoFetchPageLoadWatcher&) = delete;
  AutoFetchPageLoadWatcher& operator=(const AutoFetchPageLoadWatcher&) = delete;

  ~AutoFetchPageLoadWatcher() override;

  // Called when navigation completes, even on errors. This is only called
  // once per navigation.
  void HandleNavigation(content::NavigationHandle* navigation_handle);

  std::vector<GURL>& loaded_pages_for_testing() {
    return impl_.pages_loaded_before_observer_ready_for_testing();
  }

 private:
  class NavigationObserver;
  class TabWatcher;
  base::WeakPtr<AutoFetchPageLoadWatcher> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

  void InitializeRequestList(
      std::vector<std::unique_ptr<SavePageRequest>> requests);

  // InternalImpl::Delegate.
  void SetNotificationStateToShown(int64_t request_id) override;
  void RemoveRequests(const std::vector<int64_t>& request_ids) override;

  // RequestCoordinator::Observer.
  void OnAdded(const SavePageRequest& request) override;
  void OnCompleted(const SavePageRequest& request,
                   RequestNotifier::BackgroundSavePageResult status) override;
  void OnChanged(const SavePageRequest& request) override {}
  void OnNetworkProgress(const SavePageRequest& request,
                         int64_t received_bytes) override {}

  raw_ptr<RequestCoordinator> request_coordinator_;  // Not owned.
  auto_fetch_internal::InternalImpl impl_;
  std::unique_ptr<TabWatcher> tab_watcher_;
  base::WeakPtrFactory<AutoFetchPageLoadWatcher> weak_ptr_factory_{this};
};

}  // namespace offline_pages

#endif  // CHROME_BROWSER_OFFLINE_PAGES_ANDROID_AUTO_FETCH_PAGE_LOAD_WATCHER_H_