chromium/chrome/browser/ash/phonehub/browser_tabs_metadata_fetcher_impl.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ash/phonehub/browser_tabs_metadata_fetcher_impl.h"

#include "base/barrier_closure.h"
#include "base/time/time.h"
#include "chrome/browser/ash/sync/synced_session_client_ash.h"
#include "components/favicon/core/history_ui_favicon_request_handler.h"
#include "components/favicon_base/favicon_types.h"
#include "components/sync_sessions/synced_session.h"
#include "components/ukm/scheme_constants.h"
#include "ui/gfx/image/image_skia.h"

namespace ash {
namespace phonehub {
namespace {

std::vector<BrowserTabsModel::BrowserTabMetadata>
GetSortedMetadataWithoutFavicons(const sync_sessions::SyncedSession* session) {
  std::vector<BrowserTabsModel::BrowserTabMetadata> browser_tab_metadata;

  using WindowPair =
      std::pair<const SessionID,
                std::unique_ptr<sync_sessions::SyncedSessionWindow>>;
  for (const WindowPair& window_pair : session->windows) {
    const sessions::SessionWindow& window = window_pair.second->wrapped_window;
    for (const std::unique_ptr<sessions::SessionTab>& tab : window.tabs) {
      int selected_index = tab->normalized_navigation_index();

      if (selected_index + 1 > static_cast<int>(tab->navigations.size()) ||
          tab->navigations.empty()) {
        continue;
      }

      const sessions::SerializedNavigationEntry& current_navigation =
          tab->navigations.at(selected_index);

      GURL tab_url = current_navigation.virtual_url();

      // URLs whose schemes are not http:// or https:// should be ignored
      // because they may be platform specific (e.g., chrome:// URLs) or may
      // refer to local media on the phone (e.g., content:// URLs).
      if (!tab_url.SchemeIsHTTPOrHTTPS()) {
        continue;
      }

      // If the url is incorrectly formatted, is empty, or has a
      // scheme that should be omitted, do not proceed with storing its
      // metadata.
      if (!tab_url.is_valid()) {
        continue;
      }

      const std::u16string& title = current_navigation.title();
      const base::Time last_accessed_timestamp = tab->timestamp;
      browser_tab_metadata.emplace_back(tab_url, title, last_accessed_timestamp,
                                        gfx::Image());
    }
  }

  // Sorts the |browser_tab_metadata| from most recently visited to least
  // recently visited.
  std::sort(browser_tab_metadata.begin(), browser_tab_metadata.end());

  // At most |kMaxMostRecentTabs| tab metadata can be displayed.
  size_t num_tabs_to_display = std::min(browser_tab_metadata.size(),
                                        BrowserTabsModel::kMaxMostRecentTabs);
  return std::vector<BrowserTabsModel::BrowserTabMetadata>(
      browser_tab_metadata.begin(),
      browser_tab_metadata.begin() + num_tabs_to_display);
}

std::vector<BrowserTabsModel::BrowserTabMetadata>
GetSortedMetadataWithoutFaviconsFromForeignSyncedSession(
    const ForeignSyncedSessionAsh& session) {
  std::vector<BrowserTabsModel::BrowserTabMetadata> browser_tab_metadata;

  for (const ForeignSyncedSessionWindowAsh& window : session.windows) {
    for (const ForeignSyncedSessionTabAsh& tab : window.tabs) {
      GURL tab_url = tab.current_navigation_url;

      const std::u16string title = tab.current_navigation_title;
      const base::Time last_accessed_timestamp = tab.last_modified_timestamp;
      browser_tab_metadata.emplace_back(tab_url, title, last_accessed_timestamp,
                                        gfx::Image());
    }
  }

  // Sorts the |browser_tab_metadata| from most recently visited to least
  // recently visited.
  std::sort(browser_tab_metadata.begin(), browser_tab_metadata.end());

  // At most |kMaxMostRecentTabs| tab metadata can be displayed.
  size_t num_tabs_to_display = std::min(browser_tab_metadata.size(),
                                        BrowserTabsModel::kMaxMostRecentTabs);
  return std::vector<BrowserTabsModel::BrowserTabMetadata>(
      browser_tab_metadata.begin(),
      browser_tab_metadata.begin() + num_tabs_to_display);
}

}  // namespace

BrowserTabsMetadataFetcherImpl::BrowserTabsMetadataFetcherImpl(
    favicon::HistoryUiFaviconRequestHandler* favicon_request_handler)
    : favicon_request_handler_(favicon_request_handler) {}

BrowserTabsMetadataFetcherImpl::~BrowserTabsMetadataFetcherImpl() = default;

void BrowserTabsMetadataFetcherImpl::Fetch(
    const sync_sessions::SyncedSession* session,
    base::OnceCallback<void(BrowserTabsMetadataResponse)> callback) {
  // A new fetch was made, return a std::nullopt to the previous |callback_|.
  if (!callback_.is_null()) {
    weak_ptr_factory_.InvalidateWeakPtrs();
    std::move(callback_).Run(std::nullopt);
  }

  results_ = GetSortedMetadataWithoutFavicons(session);
  callback_ = std::move(callback);

  // When |barrier| is run |num_tabs_to_display| times, it will run
  // |OnAllFaviconsFetched|.
  base::RepeatingClosure barrier = base::BarrierClosure(
      results_.size(),
      base::BindOnce(&BrowserTabsMetadataFetcherImpl::OnAllFaviconsFetched,
                     weak_ptr_factory_.GetWeakPtr()));

  for (size_t i = 0; i < results_.size(); ++i) {
    favicon_request_handler_->GetFaviconImageForPageURL(
        results_[i].url,
        base::BindOnce(&BrowserTabsMetadataFetcherImpl::OnFaviconReady,
                       weak_ptr_factory_.GetWeakPtr(), i, barrier),
        favicon::HistoryUiFaviconRequestOrigin::kRecentTabs);
  }
}

void BrowserTabsMetadataFetcherImpl::FetchForeignSyncedPhoneSessionMetadata(
    const ForeignSyncedSessionAsh& session,
    SyncedSessionClientAsh* synced_session_client_ash,
    base::OnceCallback<void(BrowserTabsMetadataResponse)> callback) {
  // A new fetch was made, return a std::nullopt to the previous |callback_|.
  if (!callback_.is_null()) {
    weak_ptr_factory_.InvalidateWeakPtrs();
    std::move(callback_).Run(std::nullopt);
  }

  std::vector<BrowserTabsModel::BrowserTabMetadata> results =
      GetSortedMetadataWithoutFaviconsFromForeignSyncedSession(session);
  BrowserTabsModel::BrowserTabMetadata* results_buffer = results.data();
  size_t results_size = results.size();

  // When |barrier| is run |num_tabs_to_display| times, it will run
  // |OnAllFaviconsFetched|.
  base::RepeatingClosure barrier = base::BarrierClosure(
      results_size, base::BindOnce(&BrowserTabsMetadataFetcherImpl::
                                       OnAllForeignSyncedSessionFaviconsFetched,
                                   weak_ptr_factory_.GetWeakPtr(),
                                   std::move(results), std::move(callback)));

  for (size_t i = 0; i < results_size; ++i) {
    synced_session_client_ash->GetFaviconImageForPageURL(
        results_buffer[i].url,
        base::BindOnce(
            &BrowserTabsMetadataFetcherImpl::OnForeignSyncedSessionFaviconReady,
            weak_ptr_factory_.GetWeakPtr(), results_buffer + i, barrier));
  }
}

void BrowserTabsMetadataFetcherImpl::OnAllFaviconsFetched() {
  std::move(callback_).Run(std::move(results_));
}

void BrowserTabsMetadataFetcherImpl::OnAllForeignSyncedSessionFaviconsFetched(
    std::vector<BrowserTabsModel::BrowserTabMetadata> results,
    base::OnceCallback<void(BrowserTabsMetadataResponse)> callback) {
  std::move(callback).Run(std::move(results));
}

void BrowserTabsMetadataFetcherImpl::OnFaviconReady(
    size_t index_in_results,
    base::OnceClosure done_closure,
    const favicon_base::FaviconImageResult& favicon_image_result) {
  DCHECK(index_in_results < results_.size());

  results_[index_in_results].favicon = std::move(favicon_image_result.image);
  std::move(done_closure).Run();
}

void BrowserTabsMetadataFetcherImpl::OnForeignSyncedSessionFaviconReady(
    BrowserTabsModel::BrowserTabMetadata* tab,
    base::OnceClosure done_closure,
    const gfx::ImageSkia& favicon) {
  tab->favicon = gfx::Image(favicon);
  std::move(done_closure).Run();
}

}  // namespace phonehub
}  // namespace ash