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

#include "ui/base/resource/resource_bundle.h"

#include <stdint.h>

#include <algorithm>
#include <cstdint>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/debug/crash_logging.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted_memory.h"
#include "base/notreached.h"
#include "base/numerics/byte_conversions.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/trace_event/typed_macros.h"
#include "build/build_config.h"
#include "net/filter/gzip_header.h"
#include "skia/ext/image_operations.h"
#include "third_party/brotli/include/brotli/decode.h"
#include "third_party/skia/include/codec/SkPngRustDecoder.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/zlib/google/compression_utils.h"
#include "ui/base/buildflags.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/data_pack.h"
#include "ui/base/resource/lottie_resource.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/base/ui_base_paths.h"
#include "ui/base/ui_base_switches.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/image/image_skia_source.h"
#include "ui/strings/grit/app_locale_settings.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_ANDROID)
#include "ui/base/resource/resource_bundle_android.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "ui/gfx/platform_font_skia.h"
#endif

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include "base/threading/scoped_blocking_call.h"
#include "ui/display/win/dpi.h"

// To avoid conflicts with the macro from the Windows SDK...
#undef LoadBitmap
#endif

#if BUILDFLAG(ARKWEB_HAP_DECOMPRESSED)
#include "arkweb/chromium_ext/ui/base/resource/resource_bundle_for_include.cc"
#endif

namespace ui {

namespace {

// PNG-related constants.
const uint8_t kPngMagic[8] = {0x89, 'P', 'N', 'G', 13, 10, 26, 10};
const size_t kPngChunkMetadataSize = 12;  // length, type, crc32
const unsigned char kPngScaleChunkType[4] = { 'c', 's', 'C', 'l' };
const unsigned char kPngDataChunkType[4] = { 'I', 'D', 'A', 'T' };

#if !BUILDFLAG(IS_APPLE)
constexpr std::string_view kPakFileExtension = ".pak";
#endif

ResourceBundle* g_shared_instance_ = nullptr;

base::FilePath GetResourcesPakFilePath(const std::string& pak_name) {
  base::FilePath path;
  if (base::PathService::Get(base::DIR_ASSETS, &path))
    return path.AppendASCII(pak_name);

  // Return just the name of the pak file.
#if BUILDFLAG(IS_WIN)
  return base::FilePath(base::ASCIIToWide(pak_name));
#else
  return base::FilePath(pak_name);
#endif  // BUILDFLAG(IS_WIN)
}

SkBitmap CreateEmptyBitmap() {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(32, 32);
  bitmap.eraseARGB(255, 255, 255, 0);
  return bitmap;
}

// Helper function for determining whether a resource is brotli compressed.
bool HasBrotliHeader(std::string_view data) {
  // Check that the data is brotli decoded by checking for kBrotliConst in
  // header. Header added during compression at tools/grit/grit/node/base.py.
  base::span<const uint8_t> data_bytes = base::as_byte_span(data);
  static_assert(std::size(ResourceBundle::kBrotliConst) == 2,
                "Magic number should be 2 bytes long");
  return data.size() >= ResourceBundle::kBrotliHeaderSize &&
         data_bytes[0] == ResourceBundle::kBrotliConst[0] &&
         data_bytes[1] == ResourceBundle::kBrotliConst[1];
}

// Returns the uncompressed size of Brotli compressed |input| from header.
size_t GetBrotliDecompressSize(std::string_view input) {
  CHECK(input.data());
  CHECK(HasBrotliHeader(input));
  base::span<const uint8_t> raw_input = base::as_byte_span(input);
  raw_input = raw_input.subspan(std::size(ResourceBundle::kBrotliConst));
  // Get size of uncompressed resource from header.
  uint64_t uncompress_size = 0;
  size_t bytes_size = ResourceBundle::kBrotliHeaderSize -
                      std::size(ResourceBundle::kBrotliConst);
  for (size_t i = 0; i < bytes_size; i++) {
    uncompress_size |= static_cast<uint64_t>(raw_input[i]) << (i * 8);
  }
  return static_cast<size_t>(uncompress_size);
}

using OutputBufferType = std::variant<std::string*, std::vector<uint8_t>*>;

// Returns a span of the given length that writes into `out_buf`.
base::span<uint8_t> GetBufferForWriting(OutputBufferType out_buf, size_t len) {
  if (std::holds_alternative<std::string*>(out_buf)) {
    std::string* str = std::get<std::string*>(out_buf);
    str->resize(len);
    return UNSAFE_TODO(
        base::span<uint8_t>(reinterpret_cast<uint8_t*>(str->data()), len));
  }

  std::vector<uint8_t>* vec = std::get<std::vector<uint8_t>*>(out_buf);
  vec->resize(len);
  return UNSAFE_TODO(base::span<uint8_t>(vec->data(), len));
}

// Decompresses data in |input| using brotli, storing
// the result in |output|, which is resized as necessary. Returns true for
// success. To be used for grit compressed resources only.
bool BrotliDecompress(std::string_view input, OutputBufferType output) {
  size_t decompress_size = GetBrotliDecompressSize(input);
  base::span<const uint8_t> raw_input = base::as_byte_span(input);
  raw_input = raw_input.subspan(ResourceBundle::kBrotliHeaderSize);

  return BrotliDecoderDecompress(
             raw_input.size(), raw_input.data(), &decompress_size,
             GetBufferForWriting(output, decompress_size).data()) ==
         BROTLI_DECODER_RESULT_SUCCESS;
}

// Helper function for decompressing resource.
void DecompressIfNeeded(std::string_view data, OutputBufferType output) {
  if (!data.empty() &&
      net::GZipHeader::HasGZipHeader(base::as_byte_span(data))) {
    TRACE_EVENT0("ui", "DecompressIfNeeded::GzipUncompress");
    const uint32_t uncompressed_size = compression::GetUncompressedSize(data);
    bool success = compression::GzipUncompress(
        base::as_byte_span(data),
        GetBufferForWriting(output, uncompressed_size));
    DCHECK(success);
  } else if (!data.empty() && HasBrotliHeader(data)) {
    TRACE_EVENT0("ui", "DecompressIfNeeded::BrotliDecompress");
    bool success = BrotliDecompress(data, output);
    DCHECK(success);
  } else {
    base::span<uint8_t> dest = GetBufferForWriting(output, data.size());
    std::ranges::copy(data, dest.data());
  }
}

}  // namespace

// A descendant of |gfx::ImageSkiaSource| that loads a bitmap image for the
// requested scale factor from |ResourceBundle| on demand for a given
// |resource_id|. If the bitmap for the requested scale factor does not exist,
// it will return the 1x bitmap scaled by the scale factor. This may lead to
// broken UI if the correct size of the scaled image is not exactly
// |scale_factor| * the size of the 1x bitmap. When
// --highlight-missing-scaled-resources flag is specified, scaled 1x bitmaps are
// highlighted by blending them with red.
class ResourceBundle::BitmapImageSource : public gfx::ImageSkiaSource {
 public:
  BitmapImageSource(ResourceBundle* rb, int resource_id)
      : rb_(rb), resource_id_(resource_id) {}

  BitmapImageSource(const BitmapImageSource&) = delete;
  BitmapImageSource& operator=(const BitmapImageSource&) = delete;
  ~BitmapImageSource() override = default;

  // gfx::ImageSkiaSource overrides:
  gfx::ImageSkiaRep GetImageForScale(float scale) override {
    SkBitmap image;
    bool fell_back_to_1x = false;
    ResourceScaleFactor scale_factor = GetSupportedResourceScaleFactor(scale);
    bool found = rb_->LoadBitmap(resource_id_, &scale_factor,
                                 &image, &fell_back_to_1x);
    if (!found) {
#if BUILDFLAG(IS_ANDROID)
      // TODO(oshima): Android unit_tests runs at DSF=3 with 100P assets.
      return gfx::ImageSkiaRep();
#else
      DUMP_WILL_BE_NOTREACHED() << "Unable to load bitmap image with id "
                                << resource_id_ << ", scale=" << scale;
      return gfx::ImageSkiaRep(CreateEmptyBitmap(), scale);
#endif
    }

    // If the resource is in the package with kScaleFactorNone, it
    // can be used in any scale factor. The image is marked as "unscaled"
    // so that the ImageSkia do not automatically scale.
    if (scale_factor == ui::kScaleFactorNone)
      return gfx::ImageSkiaRep(image, 0.0f);

    if (fell_back_to_1x) {
      // GRIT fell back to the 100% image, so rescale it to the correct size.
      image = skia::ImageOperations::Resize(
          image, skia::ImageOperations::RESIZE_LANCZOS3,
          base::ClampCeil(image.width() * scale),
          base::ClampCeil(image.height() * scale));
    } else {
      scale = GetScaleForResourceScaleFactor(scale_factor);
    }
    return gfx::ImageSkiaRep(image, scale);
  }

 private:
  raw_ptr<ResourceBundle, AcrossTasksDanglingUntriaged> rb_;

  const int resource_id_;
};

ResourceBundle::FontDetails::FontDetails(std::string typeface,
                                         int size_delta,
                                         gfx::Font::Weight weight)
    : typeface(typeface), size_delta(size_delta), weight(weight) {}

bool ResourceBundle::FontDetails::operator==(const FontDetails& rhs) const {
  return std::tie(typeface, size_delta, weight) ==
         std::tie(rhs.typeface, rhs.size_delta, rhs.weight);
}

bool ResourceBundle::FontDetails::operator<(const FontDetails& rhs) const {
  return std::tie(typeface, size_delta, weight) <
         std::tie(rhs.typeface, rhs.size_delta, rhs.weight);
}

ResourceBundle::SharedInstanceSwapperForTesting::
    SharedInstanceSwapperForTesting()  // IN-TEST
    : SharedInstanceSwapperForTesting(/*instance=*/nullptr) {}

ResourceBundle::SharedInstanceSwapperForTesting::
    SharedInstanceSwapperForTesting(ResourceBundle* instance) {
  instance_ = SwapSharedInstanceForTesting(instance  // IN-TEST
#if BUILDFLAG(IS_ANDROID)
                                           ,
                                           {}, &android_locale_packs_
#endif  // BUILDFLAG(IS_ANDROID)
  );
}

ResourceBundle::SharedInstanceSwapperForTesting::
    ~SharedInstanceSwapperForTesting() {
  SwapSharedInstanceForTesting(instance_  // IN-TEST
#if BUILDFLAG(IS_ANDROID)
                               ,
                               android_locale_packs_, nullptr
#endif  // BUILDFLAG(IS_ANDROID)
  );
}

// static
std::string ResourceBundle::InitSharedInstanceWithLocale(
    const std::string& pref_locale,
    Delegate* delegate,
    LoadResources load_resources) {
  InitSharedInstance(delegate);
  if (load_resources == LOAD_COMMON_RESOURCES)
    g_shared_instance_->LoadCommonResources();
  std::string result =
      g_shared_instance_->LoadLocaleResources(pref_locale,
                                              /*crash_on_failure=*/true);
  g_shared_instance_->InitDefaultFontList();
  return result;
}

// static
void ResourceBundle::InitSharedInstanceWithBuffer(
    base::span<const uint8_t> buffer,
    ResourceScaleFactor scale_factor) {
  InitSharedInstance(nullptr);

  auto data_pack = std::make_unique<DataPack>(scale_factor);
  if (data_pack->LoadFromBuffer(buffer)) {
    g_shared_instance_->locale_resources_data_.push_back(std::move(data_pack));
  } else {
    LOG(ERROR) << "Failed to load locale resource from buffer";
  }
  g_shared_instance_->InitDefaultFontList();
}

// static
void ResourceBundle::InitSharedInstanceWithPakFileRegion(
    base::File pak_file,
    const base::MemoryMappedFile::Region& region) {
  InitSharedInstance(nullptr);
  auto data_pack = std::make_unique<DataPack>(k100Percent);
  CHECK(data_pack->LoadFromFileRegion(std::move(pak_file), region))
      << "failed to load pak file";
  g_shared_instance_->locale_resources_data_.push_back(std::move(data_pack));
  g_shared_instance_->InitDefaultFontList();
}

// static
void ResourceBundle::InitSharedInstanceWithPakPath(const base::FilePath& path) {
  InitSharedInstance(nullptr);
  g_shared_instance_->LoadTestResources(path, path);

  g_shared_instance_->InitDefaultFontList();
}

// static
void ResourceBundle::CleanupSharedInstance() {
  delete g_shared_instance_;
  g_shared_instance_ = nullptr;

#if BUILDFLAG(IS_ANDROID)
  UnloadAndroidLocaleResources();
#endif  // BUILDFLAG(IS_ANDROID)
}

// static
ResourceBundle* ResourceBundle::SwapSharedInstanceForTesting(
    ResourceBundle* instance
#if BUILDFLAG(IS_ANDROID)
    ,
    const std::vector<ResourceBundle::FdAndRegion>& new_android_locale_packs,
    std::vector<ResourceBundle::FdAndRegion>* old_android_locale_packs
#endif  // BUILDFLAG(IS_ANDROID)
) {
#if BUILDFLAG(IS_ANDROID)
  const std::vector<ResourceBundle::FdAndRegion> tmp =
      SwapAndroidGlobalsForTesting(new_android_locale_packs);  // IN-TEST
  if (old_android_locale_packs != nullptr) {
    *old_android_locale_packs = tmp;
  }
#endif  // BUILDFLAG(IS_ANDROID)
  ResourceBundle* ret = g_shared_instance_;
  g_shared_instance_ = instance;
  return ret;
}

// static
bool ResourceBundle::HasSharedInstance() {
  return g_shared_instance_ != nullptr;
}

// static
ResourceBundle& ResourceBundle::GetSharedInstance() {
  // Must call InitSharedInstance before this function.
  CHECK(g_shared_instance_ != nullptr);
  return *g_shared_instance_;
}

void ResourceBundle::LoadAdditionalLocaleDataWithPakFileRegion(
    base::File pak_file,
    const base::MemoryMappedFile::Region& region) {
  auto data_pack = std::make_unique<DataPack>(k100Percent);
  CHECK(data_pack->LoadFromFileRegion(std::move(pak_file), region))
      << "failed to load additional pak file";
  locale_resources_data_.push_back(std::move(data_pack));
}

#if !BUILDFLAG(IS_ANDROID)
// static
bool ResourceBundle::LocaleDataPakExists(std::string_view locale,
                                         Gender gender) {
#if BUILDFLAG(ARKWEB_HAP_DECOMPRESSED)
  return LocaleDataPakExistsExt(locale);
#else
  // TODO: Support gender translations on non-Android platforms.
  const auto path = GetLocaleFilePath(locale);
  if (path.empty()) {
    return false;
  }
#if BUILDFLAG(IS_WIN)
  // https://crbug.com/40688225: Chrome sometimes fails to find standard .pak
  // files. One theory is that this happens shortly after an update because
  // scanners (e.g., A/V) are busy checking Chrome's files. Record the last
  // found and the last not found pak file in crash keys to reveal what was
  // searched for and/or found when there is a failure to load resources.
  DWORD attributes;
  {
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    attributes = ::GetFileAttributes(path.value().c_str());
  }
  if (attributes != INVALID_FILE_ATTRIBUTES) {
    static auto* const found_path_key = base::debug::AllocateCrashKeyString(
        "LocaleDataPakExists-found_path", base::debug::CrashKeySize::Size256);
    base::debug::SetCrashKeyString(found_path_key, path.AsUTF8Unsafe());
    static auto* const found_attrs_key = base::debug::AllocateCrashKeyString(
        "LocaleDataPakExists-found_attrs", base::debug::CrashKeySize::Size32);
    base::debug::SetCrashKeyString(found_attrs_key,
                                   base::NumberToString(attributes));
    // Report that the file exists as long as it isn't a directory.
    return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
  }

  // ERROR_FILE_NOT_FOUND means that path.BaseName() does not exist.
  // PATH_NOT_FOUND means that path.DirName() does not exist.
  // ERROR_ACCESS_DENIED could mean that the file has been marked for deletion.
  // ERROR_FILE_CORRUPT has been known to happen, and is surely unrecoverable.
  // Treat these and all other errors as if the file does not exist.
  const auto error = ::GetLastError();
  static auto* const not_found_path_key = base::debug::AllocateCrashKeyString(
      "LocaleDataPakExists-not_found_path", base::debug::CrashKeySize::Size256);
  base::debug::SetCrashKeyString(not_found_path_key, path.AsUTF8Unsafe());
  static auto* const not_found_error_key = base::debug::AllocateCrashKeyString(
      "LocaleDataPakExists-not_found_error", base::debug::CrashKeySize::Size32);
  base::debug::SetCrashKeyString(not_found_error_key,
                                 base::NumberToString(error));
  return false;
#else
  return base::PathExists(path);
#endif
#endif
}
#endif  // !BUILDFLAG(IS_ANDROID)

void ResourceBundle::AddDataPackFromPath(const base::FilePath& path,
                                         ResourceScaleFactor scale_factor) {
  AddDataPackFromPathInternal(path, scale_factor, false);
}

void ResourceBundle::AddOptionalDataPackFromPath(
    const base::FilePath& path,
    ResourceScaleFactor scale_factor) {
  AddDataPackFromPathInternal(path, scale_factor, true);
}

void ResourceBundle::AddDataPackFromBuffer(base::span<const uint8_t> buffer,
                                           ResourceScaleFactor scale_factor) {
  std::unique_ptr<DataPack> data_pack(new DataPack(scale_factor));
  if (data_pack->LoadFromBuffer(buffer)) {
    AddResourceHandle(std::move(data_pack));
  } else {
    LOG(ERROR) << "Failed to load data pack from buffer";
  }
}

void ResourceBundle::AddDataPackFromFileRegion(
    base::File file,
    const base::MemoryMappedFile::Region& region,
    ResourceScaleFactor scale_factor) {
  auto data_pack = std::make_unique<DataPack>(scale_factor);
  if (data_pack->LoadFromFileRegion(std::move(file), region)) {
    AddResourceHandle(std::move(data_pack));
  } else {
    LOG(ERROR) << "Failed to load data pack from file."
               << "\nSome features may not be available.";
  }
}

#if !BUILDFLAG(IS_APPLE)
// static
base::FilePath ResourceBundle::GetLocaleFilePath(std::string_view app_locale) {
  if (app_locale.empty())
    return base::FilePath();

  base::FilePath locale_file_path;

  #if !BUILDFLAG(IS_ARKWEB)
  if (base::PathService::Get(ui::DIR_LOCALES, &locale_file_path)) {
    locale_file_path = locale_file_path.AppendASCII(
        base::StrCat({app_locale, kPakFileExtension}));
  }
#else
  if (base::PathService::Get(base::DIR_ASSETS, &locale_file_path)) {
    std::string locale_path = "locales/";
    locale_path += app_locale;
    locale_path += kPakFileExtension;
    locale_file_path = locale_file_path.AppendASCII(locale_path);
  }
#endif

  // Don't try to load from paths that are not absolute.
  return locale_file_path.IsAbsolute() ? locale_file_path : base::FilePath();
}
#endif

#if !BUILDFLAG(IS_ANDROID)
std::string ResourceBundle::LoadLocaleResources(const std::string& pref_locale,
                                                bool crash_on_failure) {
  DCHECK_EQ(locale_resources_data_.size(), 0u) << "locale.pak already loaded";
  std::string app_locale = l10n_util::GetApplicationLocale(pref_locale);
  base::FilePath locale_file_path = GetOverriddenPakPath();
  if (locale_file_path.empty())
    locale_file_path = GetLocaleFilePath(app_locale);

  if (locale_file_path.empty()) {
    // locale.pak was provided by neither GetOverriddenPakPath() nor
    // GetLocaleFilePath().
    if (crash_on_failure) {
      // Store the locale strings in crash keys in case the caller subsequently
      // crashes the process; see https://crbug.com/40688225.
      static auto* const app_locale_key = base::debug::AllocateCrashKeyString(
          "LoadLocaleResourcesNoPath-app_locale",
          base::debug::CrashKeySize::Size32);
      base::debug::SetCrashKeyString(app_locale_key, app_locale);
      static auto* const pref_locale_key = base::debug::AllocateCrashKeyString(
          "LoadLocaleResourcesNoPath-pref_locale",
          base::debug::CrashKeySize::Size32);
      base::debug::SetCrashKeyString(pref_locale_key, pref_locale);
    }
    LOG(WARNING) << "locale_file_path.empty() for locale " << app_locale;
    return std::string();
  }

  auto data_pack = std::make_unique<DataPack>(k100Percent);
  if (auto result = data_pack->LoadFromPathWithError(locale_file_path);
      !result.has_value() && crash_on_failure) {
    DataPack::ErrorState& error = result.error();
    // https://crbug.com/40688225 and https://crbug.com/394631579: Chrome can't
    // start when the locale file cannot be loaded. Crash early and gather some
    // data.

    // The local contained in prefs; provided by the caller.
    SCOPED_CRASH_KEY_STRING32("LoadLocaleResources", "pref_locale",
                              pref_locale);
    // The app locale resolved from the pref value.
    SCOPED_CRASH_KEY_STRING32("LoadLocaleResources", "app_locale", app_locale);
    // The path to the (possibly overridden) file that could not be opened.
    SCOPED_CRASH_KEY_STRING1024("LoadLocaleResources", "locale_filepath",
                                locale_file_path.AsUTF8Unsafe());

    // A ui::DataPack::FailureReason indicating what step during the attempt to
    // load the file failed.
    SCOPED_CRASH_KEY_NUMBER("LoadLocaleResources", "reason",
                            static_cast<int>(error.reason));
    // A last-error code on Windows; otherwise, errno. Only relevant if `reason`
    // is `kOpenFile` (0) or `kMapFile` (1).
    SCOPED_CRASH_KEY_NUMBER("LoadLocaleResources", "error", error.error);
    // The base::File::Error from opening the file. Only relevant if `reason` is
    // `kOpenFile` (0). Most likely redundant given `error` above, but reporting
    // anyway just in case.
    SCOPED_CRASH_KEY_NUMBER("LoadLocaleResources", "file_error",
                            error.file_error);

    NOTREACHED();
  }

  locale_resources_data_.push_back(std::move(data_pack));
  loaded_locale_ = pref_locale;
  return app_locale;
}
#endif  // !BUILDFLAG(IS_ANDROID)

void ResourceBundle::LoadTestResources(const base::FilePath& path,
                                       const base::FilePath& locale_path) {
  is_test_resources_ = true;
  // Use the given resource pak for both common and localized resources.

  if (!path.empty()) {
    const ResourceScaleFactor scale_factor =
        ui::GetSupportedResourceScaleFactors()[0];
    auto data_pack = std::make_unique<DataPack>(scale_factor);
    CHECK(data_pack->LoadFromPath(path));
    AddResourceHandle(std::move(data_pack));
  }

  auto data_pack = std::make_unique<DataPack>(ui::kScaleFactorNone);
  if (!locale_path.empty() && data_pack->LoadFromPath(locale_path)) {
    locale_resources_data_.push_back(std::move(data_pack));
  } else {
    locale_resources_data_.push_back(
        std::make_unique<DataPack>(ui::kScaleFactorNone));
  }

  // This is necessary to initialize ICU since we won't be calling
  // LoadLocaleResources in this case.
  l10n_util::GetApplicationLocale(std::string());
}

void ResourceBundle::UnloadLocaleResources() {
  locale_resources_data_.clear();

#if BUILDFLAG(IS_ANDROID)
  UnloadAndroidLocaleResources();
#endif  // BUILDFLAG(IS_ANDROID)
}

void ResourceBundle::OverrideLocalePakForTest(const base::FilePath& pak_path) {
  overridden_pak_path_ = pak_path;
}

void ResourceBundle::OverrideLocaleStringResource(
    int resource_id,
    const std::u16string& string) {
  overridden_locale_strings_[resource_id] = string;
}

const base::FilePath& ResourceBundle::GetOverriddenPakPath() const {
  return overridden_pak_path_;
}

std::u16string ResourceBundle::MaybeMangleLocalizedString(
    const std::u16string& str) const {
  if (!mangle_localized_strings_)
    return str;

  // IDS_MINIMUM_FONT_SIZE and friends are localization "strings" that are
  // actually integral constants. These should not be mangled or they become
  // impossible to parse.
  int ignored;
  if (base::StringToInt(str, &ignored))
    return str;

  // IDS_WEBSTORE_URL and some other resources are localization "strings" that
  // are actually URLs, where the "localized" part is actually just the language
  // code embedded in the URL. Don't mangle any URL.
  if (GURL(str).is_valid())
    return str;

  // For a string S, produce [[ --- S --- ]], where the number of dashes is 1/4
  // of the number of characters in S. This makes S something around 50-75%
  // longer, except for extremely short strings, which get > 100% longer.
  std::u16string start_marker = u"[[";
  std::u16string end_marker = u"]]";
  std::u16string dashes = std::u16string(str.size() / 4, '-');
  return base::JoinString({start_marker, dashes, str, dashes, end_marker},
                          u" ");
}

std::string ResourceBundle::ReloadLocaleResources(
    const std::string& pref_locale) {
  base::AutoLock lock_scope(*locale_resources_data_lock_);

  // Remove all overriden strings, as they will not be valid for the new locale.
  overridden_locale_strings_.clear();

  UnloadLocaleResources();
  return LoadLocaleResources(pref_locale, /*crash_on_failure=*/false);
}

gfx::ImageSkia* ResourceBundle::GetImageSkiaNamed(int resource_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const gfx::ImageSkia* image = GetImageNamed(resource_id).ToImageSkia();
  return const_cast<gfx::ImageSkia*>(image);
}

gfx::Image& ResourceBundle::GetImageNamed(int resource_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Check to see if the image is already in the cache.
  auto found = images_.find(resource_id);
  if (found != images_.end())
    return found->second;

  gfx::Image image;
  if (delegate_)
    image = delegate_->GetImageNamed(resource_id);

  if (image.IsEmpty()) {
    gfx::ImageSkia image_skia = CreateImageSkia(resource_id);
    CHECK(!image_skia.isNull())
        << "Unable to load image with id " << resource_id;
    image_skia.SetReadOnly();
    image = gfx::Image(image_skia);
  }

  // The load was successful, so cache the image.
  auto inserted = images_.emplace(resource_id, image);
  DCHECK(inserted.second);
  return inserted.first->second;
}

std::optional<ResourceBundle::LottieData> ResourceBundle::GetLottieData(
    int resource_id) const {
  // The prefix that GRIT prepends to Lottie assets, after compression if any.
  // See: tools/grit/grit/node/structure.py
  constexpr std::string_view kLottiePrefix = "LOTTIE";

  const std::string_view potential_lottie = GetRawDataResource(resource_id);
  if (!potential_lottie.starts_with(kLottiePrefix)) {
    return std::nullopt;
  }

  LottieData result;
  DecompressIfNeeded(potential_lottie.substr(std::size(kLottiePrefix)),
                     &result);
  return result;
}

const ui::ImageModel& ResourceBundle::GetThemedLottieImageNamed(
    int resource_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Check to see if the image is already in the cache.
  auto found = image_models_.find(resource_id);
  if (found != image_models_.end())
    return found->second;

  std::optional<LottieData> data = GetLottieData(resource_id);
  CHECK(data) << "Unable to load themed Lottie image with id " << resource_id;

  // The bytes string was successfully loaded, so parse it and cache the
  // resulting image.
  auto inserted = image_models_.emplace(
      resource_id, ParseLottieAsThemedStillImage(std::move(*data)));
  DCHECK(inserted.second);
  return inserted.first->second;
}

constexpr uint8_t ResourceBundle::kBrotliConst[];

bool ResourceBundle::HasDataResource(int resource_id) const {
  if (delegate_ && delegate_->HasDataResource(resource_id)) {
    return true;
  }
  for (const auto& resource_handle : resource_handles_) {
    if (resource_handle->HasResource(static_cast<uint16_t>(resource_id))) {
      return true;
    }
  }
  return false;
}

base::RefCountedMemory* ResourceBundle::LoadDataResourceBytes(
    int resource_id) const {
  return LoadDataResourceBytesForScale(resource_id, ui::kScaleFactorNone);
}

base::RefCountedMemory* ResourceBundle::LoadDataResourceBytesForScale(
    int resource_id,
    ResourceScaleFactor scale_factor) const {
  TRACE_EVENT("ui", "ResourceBundle::LoadDataResourceBytesForScale",
              [&](perfetto::EventContext ctx) {
                auto* event =
                    ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
                auto* data = event->set_resource_bundle();
                data->set_resource_id(static_cast<uint32_t>(resource_id));
              });

  if (delegate_) {
    base::RefCountedMemory* bytes =
        delegate_->LoadDataResourceBytes(resource_id, scale_factor);
    if (bytes)
      return bytes;
  }

  std::string_view data = GetRawDataResourceForScale(resource_id, scale_factor);
  if (data.empty())
    return nullptr;

  if (net::GZipHeader::HasGZipHeader(base::as_byte_span(data)) ||
      HasBrotliHeader(data)) {
    base::RefCountedString* bytes_string = new base::RefCountedString();
    DecompressIfNeeded(data, &bytes_string->as_string());
    return bytes_string;
  }

  return new base::RefCountedStaticMemory(base::as_byte_span(data));
}

std::string_view ResourceBundle::GetRawDataResource(int resource_id) const {
  return GetRawDataResourceForScale(resource_id, ui::kScaleFactorNone);
}

std::string_view ResourceBundle::GetRawDataResourceForScale(
    int resource_id,
    ResourceScaleFactor scale_factor,
    ResourceScaleFactor* loaded_scale_factor) const {
  if (delegate_) {
    std::string_view data;
    if (delegate_->GetRawDataResource(resource_id, scale_factor, &data)) {
      if (loaded_scale_factor) {
        *loaded_scale_factor = scale_factor;
      }
      return data;
    }
  }

  if (scale_factor != ui::k100Percent) {
    for (const auto& resource_handle : resource_handles_) {
      if (resource_handle->GetResourceScaleFactor() == scale_factor) {
        if (auto data = resource_handle->GetStringView(
                static_cast<uint16_t>(resource_id));
            data.has_value()) {
          if (loaded_scale_factor) {
            *loaded_scale_factor = scale_factor;
          }
          return data.value();
        }
      }
    }
  }

  for (const auto& resource_handle : resource_handles_) {
    if ((resource_handle->GetResourceScaleFactor() == ui::k100Percent ||
         resource_handle->GetResourceScaleFactor() == ui::k200Percent ||
         resource_handle->GetResourceScaleFactor() == ui::k300Percent ||
         resource_handle->GetResourceScaleFactor() == ui::kScaleFactorNone)) {
      if (auto data = resource_handle->GetStringView(
              static_cast<uint16_t>(resource_id));
          data.has_value()) {
        if (loaded_scale_factor) {
          *loaded_scale_factor = resource_handle->GetResourceScaleFactor();
        }
        return data.value();
      }
    }
  }
  if (loaded_scale_factor)
    *loaded_scale_factor = ui::kScaleFactorNone;
  return std::string_view();
}

std::string ResourceBundle::LoadDataResourceString(int resource_id) const {
  if (delegate_) {
    std::optional<std::string> data =
        delegate_->LoadDataResourceString(resource_id);
    if (data)
      return data.value();
  }

  return LoadDataResourceStringForScale(resource_id, ui::kScaleFactorNone);
}

std::string ResourceBundle::LoadDataResourceStringForScale(
    int resource_id,
    ResourceScaleFactor scaling_factor) const {
  std::string output;
  DecompressIfNeeded(GetRawDataResourceForScale(resource_id, scaling_factor),
                     &output);
  return output;
}

std::string ResourceBundle::LoadLocalizedResourceString(int resource_id) const {
  base::AutoLock lock_scope(*locale_resources_data_lock_);
  std::string_view data;
  for (auto& locale_data : locale_resources_data_) {
    data = locale_data->GetStringView(static_cast<uint16_t>(resource_id))
               .value_or(std::string_view());
    if (!data.empty()) {
      break;
    }
  }
  if (data.empty()) {
    data = GetRawDataResource(resource_id);
  }
  std::string output;
  DecompressIfNeeded(data, &output);
  return output;
}

bool ResourceBundle::IsGzipped(int resource_id) const {
  std::string_view raw_data = GetRawDataResource(resource_id);
  if (!raw_data.data())
    return false;

  return net::GZipHeader::HasGZipHeader(base::as_byte_span(raw_data));
}

bool ResourceBundle::IsBrotli(int resource_id) const {
  std::string_view raw_data = GetRawDataResource(resource_id);
  if (!raw_data.data())
    return false;

  return HasBrotliHeader(raw_data);
}

std::u16string ResourceBundle::GetLocalizedString(int resource_id) {
#if DCHECK_IS_ON()
  {
    base::AutoLock lock_scope(*locale_resources_data_lock_);
    // Overriding locale strings isn't supported if the first string resource
    // has already been queried.
    can_override_locale_string_resources_ = false;
  }
#endif
  DCHECK(!IsGzipped(resource_id) && !IsBrotli(resource_id))
      << "Compressed string encountered, perhaps use "
         "ResourceBundle::LoadLocalizedResourceString instead";
  return GetLocalizedStringImpl(resource_id);
}

base::RefCountedMemory* ResourceBundle::LoadLocalizedResourceBytes(
    int resource_id) const {
  {
    base::AutoLock lock_scope(*locale_resources_data_lock_);

    for (auto& locale_data : locale_resources_data_) {
      auto data =
          locale_data->GetStringView(static_cast<uint16_t>(resource_id));
      if (data.has_value() && !data->empty()) {
        return new base::RefCountedStaticMemory(base::as_byte_span(*data));
      }
    }
  }
  // Release lock_scope and fall back to main data pack.
  return LoadDataResourceBytes(resource_id);
}

const gfx::FontList& ResourceBundle::GetFontListWithDelta(int size_delta) {
  return GetFontListForDetails(FontDetails(std::string(), size_delta));
}

const gfx::FontList& ResourceBundle::GetFontListForDetails(
    const FontDetails& details) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto found = font_cache_.find(details);
  if (found != font_cache_.end())
    return found->second;

  const FontDetails base_details(details.typeface);
  gfx::FontList default_font_list = gfx::FontList();
  gfx::FontList base_font_list =
      details.typeface.empty()
          ? default_font_list
          : gfx::FontList({details.typeface}, default_font_list.GetFontStyle(),
                          default_font_list.GetFontSize(),
                          default_font_list.GetFontWeight());
  font_cache_.emplace(base_details, base_font_list);
  gfx::FontList& base = font_cache_.find(base_details)->second;
  if (details == base_details)
    return base;

  // Fonts of a given style are derived from the unstyled font of the same size.
  // Cache the unstyled font by first inserting a default-constructed font list.
  // Then, derive it for the initial insertion, or use the iterator that points
  // to the existing entry that the insertion collided with.
  const FontDetails sized_details(details.typeface, details.size_delta);
  auto sized = font_cache_.emplace(sized_details, base_font_list);
  if (sized.second)
    sized.first->second = base.DeriveWithSizeDelta(details.size_delta);
  if (details == sized_details) {
    return sized.first->second;
  }

  auto styled = font_cache_.emplace(details, base_font_list);
  DCHECK(styled.second);  // Otherwise font_cache_.find(..) would have found it.
  styled.first->second = sized.first->second.Derive(
      0, sized.first->second.GetFontStyle(), details.weight);

  return styled.first->second;
}

const gfx::FontList& ResourceBundle::GetFontList(FontStyle legacy_style) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  gfx::Font::Weight weight = gfx::Font::Weight::NORMAL;
  if (legacy_style == BoldFont || legacy_style == MediumBoldFont)
    weight = gfx::Font::Weight::BOLD;

  int size_delta = 0;
  switch (legacy_style) {
    case SmallFont:
      size_delta = kSmallFontDelta;
      break;
    case MediumFont:
    case MediumBoldFont:
      size_delta = kMediumFontDelta;
      break;
    case LargeFont:
      size_delta = kLargeFontDelta;
      break;
    case BaseFont:
    case BoldFont:
      break;
  }

  return GetFontListForDetails(FontDetails(std::string(), size_delta, weight));
}

const gfx::Font& ResourceBundle::GetFont(FontStyle style) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return GetFontList(style).GetPrimaryFont();
}

void ResourceBundle::ReloadFonts() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  InitDefaultFontList();
  font_cache_.clear();
}

ResourceScaleFactor ResourceBundle::GetMaxResourceScaleFactor() const {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  return max_scale_factor_;
#else
  return GetMaxSupportedResourceScaleFactor();
#endif
}

void ResourceBundle::CheckCanOverrideStringResources() {
#if DCHECK_IS_ON()
  base::AutoLock lock_scope(*locale_resources_data_lock_);
  DCHECK(can_override_locale_string_resources_);
#endif
}

ResourceBundle::ResourceBundle(Delegate* delegate)
    : delegate_(delegate),
      locale_resources_data_lock_(new base::Lock),
      max_scale_factor_(k100Percent) {
  // With CEF's multi-threaded mode the ResourceBundle may be created on the
  // main thread and then accessed on the UI thread. Allow the SequenceChecker
  // to re-bind on the UI thread when CalledOnValidSequence() is called for the
  // first time.
  DETACH_FROM_SEQUENCE(sequence_checker_);

  mangle_localized_strings_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kMangleLocalizedStrings);
}

ResourceBundle::~ResourceBundle() {
  FreeImages();
  UnloadLocaleResources();
}

void ResourceBundle::CleanupOnUIThread() {
  FreeImages();
  font_cache_.clear();
}

// static
void ResourceBundle::InitSharedInstance(Delegate* delegate) {
  DCHECK(g_shared_instance_ == nullptr) << "ResourceBundle initialized twice";
  g_shared_instance_ = new ResourceBundle(delegate);
  std::vector<ResourceScaleFactor> supported_scale_factors;
#if BUILDFLAG(IS_IOS)
  float internal_display_device_scale_factor =
      display::GetInternalDisplayDeviceScaleFactor();
  if (internal_display_device_scale_factor > 2.0) {
    supported_scale_factors.push_back(k300Percent);
  } else if (internal_display_device_scale_factor > 1.0) {
    supported_scale_factors.push_back(k200Percent);
  } else {
    supported_scale_factors.push_back(k100Percent);
  }
#else
  // On platforms other than iOS, 100P is always a supported scale factor.
  supported_scale_factors.push_back(k100Percent);

#if BUILDFLAG(ENABLE_HIDPI)
  supported_scale_factors.push_back(k200Percent);
#endif
#endif
  ui::SetSupportedResourceScaleFactors(supported_scale_factors);

// Register Png Decoder for use by DataURIResourceProviderProxy for embedded
// images.
#if BUILDFLAG(IS_CHROMEOS)
  SkCodecs::Register(SkPngRustDecoder::Decoder());
#endif
}

void ResourceBundle::FreeImages() {
  images_.clear();
#if BUILDFLAG(IS_CHROMEOS)
  image_models_.clear();
#endif
}

void ResourceBundle::LoadChromeResources() {
  // Always load the 1x data pack first as the 2x data pack contains both 1x and
  // 2x images. The 1x data pack only has 1x images, thus passes in an accurate
  // scale factor to gfx::ImageSkia::AddRepresentation.
  if (IsScaleFactorSupported(k100Percent)) {
    AddDataPackFromPath(GetResourcesPakFilePath("chrome_100_percent.pak"),
                        k100Percent);
  }

  if (IsScaleFactorSupported(k200Percent)) {
    AddOptionalDataPackFromPath(
        GetResourcesPakFilePath("chrome_200_percent.pak"), k200Percent);
  }
}

void ResourceBundle::AddDataPackFromPathInternal(
    const base::FilePath& path,
    ResourceScaleFactor scale_factor,
    bool optional) {
  // Do not pass an empty |path| value to this method. If the absolute path is
  // unknown pass just the pack file name.
  DCHECK(!path.empty());

  base::FilePath pack_path = path;
  if (delegate_)
    pack_path = delegate_->GetPathForResourcePack(pack_path, scale_factor);

  // Don't try to load empty values or values that are not absolute paths.
  if (pack_path.empty() || !pack_path.IsAbsolute())
    return;

  auto data_pack = std::make_unique<DataPack>(scale_factor);
  if (data_pack->LoadFromPath(pack_path)) {
    AddResourceHandle(std::move(data_pack));
  } else if (!optional) {
    LOG(ERROR) << "Failed to load " << pack_path.value()
               << "\nSome features may not be available.";
  }
}

void ResourceBundle::AddResourceHandle(
    std::unique_ptr<ResourceHandle> resource_handle) {
#if DCHECK_IS_ON()
  resource_handle->CheckForDuplicateResources(resource_handles_);
#endif

  if (GetScaleForResourceScaleFactor(
          resource_handle->GetResourceScaleFactor()) >
      GetScaleForResourceScaleFactor(max_scale_factor_))
    max_scale_factor_ = resource_handle->GetResourceScaleFactor();

  resource_handles_.push_back(std::move(resource_handle));
}

void ResourceBundle::InitDefaultFontList() {
#if BUILDFLAG(IS_CHROMEOS)
  // InitDefaultFontList() is called earlier than overriding the locale strings.
  // So we call the |GetLocalizedStringImpl()| which doesn't set the flag
  // |can_override_locale_string_resources_| to false. This is okay, because the
  // font list doesn't need to be overridden by variations.
  std::string font_family =
      base::UTF16ToUTF8(GetLocalizedStringImpl(IDS_UI_FONT_FAMILY_CROS));
  gfx::FontList::SetDefaultFontDescription(font_family);

  // TODO(yukishiino): Remove SetDefaultFontDescription() once the migration to
  // the font list is done.  We will no longer need SetDefaultFontDescription()
  // after every client gets started using a FontList instead of a Font.
  gfx::PlatformFontSkia::SetDefaultFontDescription(font_family);
#else
  // Use a single default font as the default font list.
  gfx::FontList::SetDefaultFontDescription(std::string());
#endif
}

gfx::ImageSkia ResourceBundle::CreateImageSkia(int resource_id) {
  DCHECK(!resource_handles_.empty()) << "Missing call to SetResourcesDataDLL?";

  std::optional<LottieData> data = GetLottieData(resource_id);
  if (data) {
    return ParseLottieAsStillImage(std::move(*data));
  }

#if BUILDFLAG(IS_CHROMEOS)
  const ResourceScaleFactor scale_factor_to_load = GetMaxResourceScaleFactor();
#elif BUILDFLAG(IS_WIN)
  const ResourceScaleFactor scale_factor_to_load =
      display::win::GetDPIScale() > 1.25 ? GetMaxResourceScaleFactor()
                                         : ui::k100Percent;
#else
  const ResourceScaleFactor scale_factor_to_load = ui::k100Percent;
#endif

  // TODO(oshima): Consider reading the image size from png IHDR chunk and
  // skip decoding here and remove #ifdef below.
  // |ResourceBundle::GetSharedInstance()| is destroyed after the
  // |BrowserMainLoop| has finished running. The |gfx::ImageSkia| is guaranteed
  // to be destroyed before the resource bundle is destroyed.
  return gfx::ImageSkia(std::make_unique<BitmapImageSource>(this, resource_id),
                        GetScaleForResourceScaleFactor(scale_factor_to_load));
}

bool ResourceBundle::LoadBitmap(const ResourceHandle& data_handle,
                                int resource_id,
                                SkBitmap* bitmap,
                                bool* fell_back_to_1x) const {
  DCHECK(fell_back_to_1x);
  scoped_refptr<base::RefCountedMemory> memory(
      data_handle.GetStaticMemory(static_cast<uint16_t>(resource_id)));
  if (!memory.get())
    return false;

  if (DecodePNG(*memory, bitmap, fell_back_to_1x)) {
    return true;
  }

#if !BUILDFLAG(IS_IOS)
  // iOS does not compile or use the JPEG codec.  On other platforms,
  // 99% of our assets are PNGs, however fallback to JPEG.
  SkBitmap jpeg_bitmap = gfx::JPEGCodec::Decode(*memory);
  if (!jpeg_bitmap.isNull()) {
    bitmap->swap(jpeg_bitmap);
    *fell_back_to_1x = false;
    return true;
  }
#endif

  NOTREACHED() << "Unable to decode theme image resource " << resource_id;
}

bool ResourceBundle::LoadBitmap(int resource_id,
                                ResourceScaleFactor* scale_factor,
                                SkBitmap* bitmap,
                                bool* fell_back_to_1x) const {
  DCHECK(fell_back_to_1x);
  for (const auto& pack : resource_handles_) {
    if (pack->GetResourceScaleFactor() == ui::kScaleFactorNone &&
        LoadBitmap(*pack, resource_id, bitmap, fell_back_to_1x)) {
      DCHECK(!*fell_back_to_1x);
      *scale_factor = ui::kScaleFactorNone;
      return true;
    }

    if (pack->GetResourceScaleFactor() == *scale_factor &&
        LoadBitmap(*pack, resource_id, bitmap, fell_back_to_1x)) {
      return true;
    }
  }

  // Unit tests may only have 1x data pack. Allow them to fallback to 1x
  // resources.
  if (is_test_resources_ && *scale_factor != ui::k100Percent) {
    for (const auto& pack : resource_handles_) {
      if (pack->GetResourceScaleFactor() == ui::k100Percent &&
          LoadBitmap(*pack, resource_id, bitmap, fell_back_to_1x)) {
        *fell_back_to_1x = true;
        return true;
      }
    }
  }

  return false;
}

gfx::Image& ResourceBundle::GetEmptyImage() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (empty_image_.IsEmpty()) {
    // The placeholder bitmap is bright red so people notice the problem.
    SkBitmap bitmap = CreateEmptyBitmap();
    empty_image_ = gfx::Image::CreateFrom1xBitmap(bitmap);
  }
  return empty_image_;
}

#if BUILDFLAG(IS_CHROMEOS)
const ui::ImageModel& ResourceBundle::GetEmptyImageModel() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (empty_image_model_.IsEmpty())
    empty_image_model_ = ui::ImageModel::FromImage(GetEmptyImage());
  return empty_image_model_;
}
#endif

std::u16string ResourceBundle::GetLocalizedStringImpl(int resource_id) const {
  std::u16string string;
  if (delegate_ && delegate_->GetLocalizedString(resource_id, &string))
    return MaybeMangleLocalizedString(string);

  // Ensure that ReloadLocaleResources() doesn't drop the resources while
  // we're using them.
  base::AutoLock lock_scope(*locale_resources_data_lock_);

  IdToStringMap::const_iterator it =
      overridden_locale_strings_.find(resource_id);
  if (it != overridden_locale_strings_.end())
    return MaybeMangleLocalizedString(it->second);

  // If for some reason we were unable to load the resources , return an empty
  // string (better than crashing).
  if (locale_resources_data_.empty()) {
    LOG(WARNING) << "locale resources are not loaded";
    return std::u16string();
  }

  std::optional<std::string_view> data;
  ResourceHandle::TextEncodingType encoding =
      locale_resources_data_.at(0)->GetTextEncodingType();
  for (auto& locale_data : locale_resources_data_) {
    data = locale_data->GetStringView(static_cast<uint16_t>(resource_id));
    if (data.has_value()) {
      encoding = locale_data->GetTextEncodingType();
      break;
    }
  }
  if (!data.has_value()) {
    // Fall back on the main data pack (shouldn't be any strings here except
    // in unittests).
    data = GetRawDataResource(resource_id);
    CHECK(!data->empty())
        << "Unable to find resource: " << resource_id
        << ". If this happens in a browser test running on Windows, it may "
           "be that dead-code elimination stripped out the code that uses the"
           " resource, causing the resource to be stripped out because the "
           "resource is not used by chrome.dll. See "
           "https://crbug.com/1181150.";
  }

  // Strings should not be loaded from a data pack that contains binary data.
  DCHECK(encoding == ResourceHandle::UTF16 || encoding == ResourceHandle::UTF8)
      << "requested localized string from binary pack file";

  // Data pack encodes strings as either UTF8 or UTF16.
  std::u16string msg;
  if (encoding == ResourceHandle::UTF16) {
    msg.assign(UNSAFE_TODO(reinterpret_cast<const char16_t*>(data->data())),
               data->length() / 2);
  } else if (encoding == ResourceHandle::UTF8) {
    // Best-effort conversion.
    base::UTF8ToUTF16(data->data(), data->size(), &msg);
  }
  return MaybeMangleLocalizedString(msg);
}

// static
bool ResourceBundle::PNGContainsFallbackMarker(base::span<const uint8_t> buf) {
  if (buf.size() < std::size(kPngMagic) ||
      buf.first(std::size(kPngMagic)) != kPngMagic) {
    return false;  // Data invalid or a JPEG.
  }
  buf = buf.subspan(std::size(kPngMagic));

  // Scan for custom chunks until we find one, find the IDAT chunk, or run out
  // of chunks.
  for (;;) {
    if (buf.size() < kPngChunkMetadataSize) {
      break;
    }
    uint32_t length = base::U32FromBigEndian(buf.first<4u>());
    if (buf.size() - kPngChunkMetadataSize < length) {
      break;
    }
    if (length == 0u) {
      auto scale_chunk =
          buf.subspan(sizeof(uint32_t), std::size(kPngScaleChunkType));
      if (scale_chunk == kPngScaleChunkType) {
        return true;
      }
    }
    auto data_chunk =
        buf.subspan(sizeof(uint32_t), std::size(kPngDataChunkType));
    if (data_chunk == kPngDataChunkType) {
      // Stop looking for custom chunks, any custom chunks should be before an
      // IDAT chunk.
      break;
    }
    buf = buf.subspan(length + kPngChunkMetadataSize);
  }
  return false;
}

// static
bool ResourceBundle::DecodePNG(base::span<const uint8_t> buf,
                               SkBitmap* bitmap,
                               bool* fell_back_to_1x) {
  *fell_back_to_1x = PNGContainsFallbackMarker(buf);
  *bitmap = gfx::PNGCodec::Decode(buf);
  return !bitmap->isNull();
}

}  // namespace ui