chromium/ash/webui/boca_ui/provider/tab_info_collector.cc

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

#include "ash/webui/boca_ui/provider/tab_info_collector.h"

#include <memory>

#include "ash/public/cpp/tab_strip_delegate.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/webui/boca_ui/mojom/boca.mojom-forward.h"
#include "ash/wm/mru_window_tracker.h"
#include "base/base64.h"
#include "base/check_is_test.h"
#include "base/strings/utf_string_conversions.h"
#include "components/app_constants/constants.h"
#include "components/app_restore/full_restore_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "third_party/skia/include/encode/SkPngEncoder.h"
#include "ui/gfx/image/buffer_w_stream.h"
#include "ui/gfx/image/image_skia_rep_default.h"
#include "ui/wm/core/window_util.h"

namespace {
// Web UI image helper copied from //chrome/browser/ui/webui/util/image_util.cc
// due to dependency constraint.
std::string MakeDataURIForImage(base::span<const uint8_t> image_data,
                                std::string_view mime_subtype) {
  std::string result = "data:image/";
  result.append(mime_subtype.begin(), mime_subtype.end());
  result += ";base64,";
  result += base::Base64Encode(image_data);
  return result;
}

std::string EncodePNGAndMakeDataURI(gfx::ImageSkia image, float scale_factor) {
  const SkBitmap& bitmap = image.GetRepresentation(scale_factor).GetBitmap();
  gfx::BufferWStream stream;
  const bool encoding_succeeded =
      SkPngEncoder::Encode(&stream, bitmap.pixmap(), {});
  DCHECK(encoding_succeeded);
  return MakeDataURIForImage(
      base::as_bytes(base::make_span(stream.TakeBuffer())), "png");
}
// End of image lib

}  // namespace

namespace ash::boca {
TabInfoCollector::ImageGenerator::ImageGenerator(content::WebUI* web_ui)
    : web_ui_(web_ui) {}
TabInfoCollector::ImageGenerator::ImageGenerator() = default;
TabInfoCollector::ImageGenerator::~ImageGenerator() = default;

std::string TabInfoCollector::ImageGenerator::StringifyImage(
    ui::ImageModel image) {
  // For test only.
  if (!web_ui_) {
    CHECK_IS_TEST();
    return "";
  }
  const ui::ColorProvider& provider =
      web_ui_->GetWebContents()->GetColorProvider();
  gfx::ImageSkia raster_favicon = image.Rasterize(&provider);
  return EncodePNGAndMakeDataURI(raster_favicon,
                                 web_ui_->GetDeviceScaleFactor());
}

TabInfoCollector::TabInfoCollector(content::WebUI* web_ui) {
  image_generator_ = std::make_unique<ImageGenerator>(web_ui);
}

TabInfoCollector::TabInfoCollector(
    std::unique_ptr<TabInfoCollector::ImageGenerator> image_generator)
    : image_generator_(std::move(image_generator)) {}
TabInfoCollector::~TabInfoCollector() = default;

void TabInfoCollector::GetWindowTabInfo(GetWindowsTabsListCallback callback) {
  auto* const shell = Shell::Get();
  auto mru_windows =
      shell->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
  auto* delegate = shell->tab_strip_delegate();

  std::vector<std::vector<ash::TabInfo>> windows;
  for (aura::Window* window : mru_windows) {
    // skip transient windows.
    if (wm::GetTransientParent(window)) {
      continue;
    }
    // Only load browser window.
    if (full_restore::GetAppId(window) != app_constants::kChromeAppId) {
      continue;
    }
    // TODO(b/355508827):Set user selected window name.
    windows.push_back(delegate->GetTabsListForWindow(window));
  }
  SortWindowList(windows);
  std::move(callback).Run(AshToPageWindows(windows));
}

mojom::TabInfoPtr TabInfoCollector::AshToPageTabInfo(ash::TabInfo tab) {
  mojom::TabInfoPtr tab_info = mojom::TabInfo::New();
  tab_info->title = base::UTF16ToUTF8(tab.title);
  tab_info->url = tab.url;
  tab_info->favicon = image_generator_->StringifyImage(tab.favicon);
  return tab_info;
}

void TabInfoCollector::SortWindowList(
    std::vector<std::vector<ash::TabInfo>>& windows_list) {
  for (std::vector<ash::TabInfo>& window : windows_list) {
    // Sort tab on non-ascending order of last access time.
    base::ranges::sort(window, [](const ash::TabInfo a, const ash::TabInfo b) {
      return a.last_access_timetick > b.last_access_timetick;
    });
  }

  // Sort window on non-ascending order of last access time.
  base::ranges::sort(windows_list, [](const std::vector<ash::TabInfo> a,
                                      const std::vector<ash::TabInfo> b) {
    return a[0].last_access_timetick > b[0].last_access_timetick;
  });
}

std::vector<mojom::WindowPtr> TabInfoCollector::AshToPageWindows(
    std::vector<std::vector<ash::TabInfo>> windows) {
  std::vector<mojom::WindowPtr> out;
  for (auto window : windows) {
    mojom::WindowPtr window_out = mojom::Window::New();
    for (auto tab : window) {
      window_out->tab_list.push_back(AshToPageTabInfo(tab));
    }
    out.push_back(std::move(window_out));
  }
  return out;
}

}  // namespace ash::boca