blob: b5cd19191c738e333949e5f9202d13bdb82fabc9 [file] [log] [blame]
// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "ui/base/resource/resource_bundle.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "base/base_paths.h"
#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted_memory.h"
#include "base/numerics/byte_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "skia/buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/data_pack.h"
#include "ui/base/resource/data_pack_literal.h"
#include "ui/base/resource/mock_resource_bundle_delegate.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"
#if BUILDFLAG(IS_WIN)
#include "ui/display/win/dpi.h"
#endif
using ::testing::_;
using ::testing::Between;
using ::testing::DoAll;
using ::testing::Property;
using ::testing::Return;
using ::testing::ReturnArg;
using ::testing::SetArgPointee;
namespace ui {
namespace {
const unsigned char kPngMagic[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
const size_t kPngChunkMetadataSize = 12;
const unsigned char kPngIHDRChunkType[4] = { 'I', 'H', 'D', 'R' };
// Custom chunk that GRIT adds to PNG to indicate that it could not find a
// bitmap at the requested scale factor and fell back to 1x.
const unsigned char kPngScaleChunk[12] = { 0x00, 0x00, 0x00, 0x00,
'c', 's', 'C', 'l',
0xc1, 0x30, 0x60, 0x4d };
#if BUILDFLAG(SKIA_SUPPORT_SKOTTIE) && BUILDFLAG(USE_BLINK)
// The width and height attributes values in the lottie asset.
constexpr int kLottieWidth = 200;
constexpr int kLottieHeight = 200;
// A string with the "LOTTIE" prefix that GRIT adds to Lottie assets.
constexpr std::string_view kLottieData =
R"(LOTTIE{
"v": "5.5.2",
"fr": 1,
"ip": 0,
"op": 1,
"w": 200,
"h": 200,
"ddd": 0,
"assets": [],
"layers": [
{
"ty": 1,
"ip": 0,
"op": 1,
"st": 0,
"ks": {},
"sc": "#ff0000",
"sh": 200,
"sw": 200
}
]
})";
// The contents after the prefix has been removed.
constexpr std::string_view kLottieExpected =
R"({
"v": "5.5.2",
"fr": 1,
"ip": 0,
"op": 1,
"w": 200,
"h": 200,
"ddd": 0,
"assets": [],
"layers": [
{
"ty": 1,
"ip": 0,
"op": 1,
"st": 0,
"ks": {},
"sc": "#ff0000",
"sh": 200,
"sw": 200
}
]
})";
#endif // BUILDFLAG(SKIA_SUPPORT_SKOTTIE) && BUILDFLAG(USE_BLINK)
// Returns |bitmap_data| with |custom_chunk| inserted after the IHDR chunk.
void AddCustomChunk(std::string_view custom_chunk,
std::vector<unsigned char>* bitmap_data) {
size_t chunk_offset = 0u;
// Expect the magic signature first.
auto magic_span =
base::as_byte_span(*bitmap_data).first<std::size(kPngMagic)>();
EXPECT_TRUE(magic_span == kPngMagic);
chunk_offset += magic_span.size();
// Expect an IHDR chunk next. It starts with a length.
auto ihdr_chunk = base::as_byte_span(*bitmap_data).subspan(chunk_offset);
uint32_t ihdr_chunk_length =
base::U32FromBigEndian(ihdr_chunk.first<sizeof(uint32_t)>());
auto ihdr_type =
ihdr_chunk.subspan<sizeof(uint32_t), std::size(kPngIHDRChunkType)>();
EXPECT_TRUE(ihdr_type == kPngIHDRChunkType);
chunk_offset += ihdr_chunk_length;
// Expect a PNG Metadata chunk next.
chunk_offset += kPngChunkMetadataSize;
// Then insert custom chunk.
ASSERT_LE(chunk_offset, bitmap_data->size());
bitmap_data->insert(bitmap_data->begin() + chunk_offset, custom_chunk.begin(),
custom_chunk.end());
}
// Creates datapack at |path| with a single bitmap at resource ID 3
// which is |edge_size|x|edge_size| pixels.
// If |custom_chunk| is non empty, adds it after the IHDR chunk
// in the encoded bitmap data.
void CreateDataPackWithSingleBitmap(const base::FilePath& path,
int edge_size,
std::string_view custom_chunk) {
SkBitmap bitmap;
bitmap.allocN32Pixels(edge_size, edge_size);
bitmap.eraseColor(SK_ColorWHITE);
std::optional<std::vector<uint8_t>> bitmap_data =
gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, /*discard_transparency=*/false);
ASSERT_TRUE(bitmap_data);
if (custom_chunk.size() > 0) {
AddCustomChunk(custom_chunk, &bitmap_data.value());
}
std::map<uint16_t, std::string_view> resources;
resources[3u] = base::as_string_view(bitmap_data.value());
DataPack::WritePack(path, resources, ui::DataPack::BINARY);
}
} // namespace
class ResourceBundleTest : public testing::Test {
public:
ResourceBundleTest() : resource_bundle_(nullptr) {}
ResourceBundleTest(const ResourceBundleTest&) = delete;
ResourceBundleTest& operator=(const ResourceBundleTest&) = delete;
~ResourceBundleTest() override {}
// Overridden from testing::Test:
void TearDown() override {
resource_bundle_.reset();
if (temp_dir_.IsValid())
ASSERT_TRUE(temp_dir_.Delete());
}
// Returns new ResoureBundle with the specified |delegate|. The
// ResourceBundleTest class manages the lifetime of the returned
// ResourceBundle.
ResourceBundle* CreateResourceBundle(ResourceBundle::Delegate* delegate) {
DCHECK(!resource_bundle_);
resource_bundle_ = std::make_unique<ResourceBundle>(delegate);
return resource_bundle_.get();
}
protected:
base::ScopedTempDir temp_dir_;
MockResourceBundleDelegate delegate_;
std::unique_ptr<ResourceBundle> resource_bundle_;
};
TEST_F(ResourceBundleTest, DelegateGetPathForResourcePack) {
ResourceBundle* resource_bundle = CreateResourceBundle(&delegate_);
base::FilePath pack_path(FILE_PATH_LITERAL("/path/to/test_path.pak"));
ResourceScaleFactor pack_scale_factor = ui::k200Percent;
EXPECT_CALL(delegate_, GetPathForResourcePack(Property(&base::FilePath::value,
pack_path.value()),
pack_scale_factor))
.Times(1)
.WillOnce(Return(pack_path));
resource_bundle->AddDataPackFromPath(pack_path, pack_scale_factor);
}
TEST_F(ResourceBundleTest, DelegateGetPathForLocalePack) {
ResourceBundle::SharedInstanceSwapperForTesting resource_bundle_swapper;
ResourceBundle::InitSharedInstance(&delegate_);
std::string locale = "en-US";
// Cancel the load.
EXPECT_CALL(delegate_, GetPathForLocalePack(_, _))
.WillRepeatedly(Return(base::FilePath()))
.RetiresOnSaturation();
EXPECT_FALSE(ResourceBundle::LocaleDataPakExists(locale));
EXPECT_EQ("", ResourceBundle::GetSharedInstance().LoadLocaleResources(
locale, /*crash_on_failure=*/false));
// Allow the load to proceed.
EXPECT_CALL(delegate_, GetPathForLocalePack(_, _))
.WillRepeatedly(ReturnArg<0>());
EXPECT_TRUE(ResourceBundle::LocaleDataPakExists(locale));
EXPECT_EQ(locale, ResourceBundle::GetSharedInstance().LoadLocaleResources(
locale, /*crash_on_failure=*/false));
ResourceBundle::CleanupSharedInstance();
}
TEST_F(ResourceBundleTest, DelegateGetImageNamed) {
ResourceBundle* resource_bundle = CreateResourceBundle(&delegate_);
gfx::Image empty_image = resource_bundle->GetEmptyImage();
int resource_id = 5;
EXPECT_CALL(delegate_, GetImageNamed(resource_id))
.Times(1)
.WillOnce(Return(empty_image));
gfx::Image result = resource_bundle->GetImageNamed(resource_id);
EXPECT_EQ(empty_image.ToSkBitmap(), result.ToSkBitmap());
}
TEST_F(ResourceBundleTest, DelegateGetNativeImageNamed) {
ResourceBundle* resource_bundle = CreateResourceBundle(&delegate_);
gfx::Image empty_image = resource_bundle->GetEmptyImage();
int resource_id = 5;
// Some platforms delegate GetNativeImageNamed calls to GetImageNamed.
EXPECT_CALL(delegate_, GetImageNamed(resource_id))
.Times(Between(0, 1))
.WillOnce(Return(empty_image));
EXPECT_CALL(delegate_, GetNativeImageNamed(resource_id))
.Times(Between(0, 1))
.WillOnce(Return(empty_image));
gfx::Image result = resource_bundle->GetNativeImageNamed(resource_id);
EXPECT_EQ(empty_image.ToSkBitmap(), result.ToSkBitmap());
}
TEST_F(ResourceBundleTest, DelegateHasDataResource) {
ResourceBundle* resource_bundle = CreateResourceBundle(&delegate_);
int resource_id = 5;
EXPECT_CALL(delegate_, HasDataResource(resource_id))
.Times(1)
.WillOnce(Return(true));
bool result = resource_bundle->HasDataResource(resource_id);
EXPECT_EQ(result, true);
}
TEST_F(ResourceBundleTest, DelegateLoadDataResourceBytes) {
ResourceBundle* resource_bundle = CreateResourceBundle(&delegate_);
// Create the data resource for testing purposes.
const unsigned char data[] = "My test data";
scoped_refptr<base::RefCountedStaticMemory> static_memory(
new base::RefCountedStaticMemory(data));
int resource_id = 5;
ResourceScaleFactor scale_factor = ui::kScaleFactorNone;
EXPECT_CALL(delegate_, LoadDataResourceBytes(resource_id, scale_factor))
.Times(1)
.WillOnce(Return(static_memory.get()));
scoped_refptr<base::RefCountedMemory> result =
resource_bundle->LoadDataResourceBytesForScale(resource_id, scale_factor);
EXPECT_EQ(static_memory, result);
}
TEST_F(ResourceBundleTest, DelegateGetRawDataResource) {
ResourceBundle* resource_bundle = CreateResourceBundle(&delegate_);
// Create the string piece for testing purposes.
char data[] = "My test data";
std::string_view string_piece(data);
int resource_id = 5;
EXPECT_CALL(delegate_,
GetRawDataResource(resource_id, ui::kScaleFactorNone, _))
.Times(1)
.WillOnce(DoAll(SetArgPointee<2>(string_piece), Return(true)));
std::string_view result = resource_bundle->GetRawDataResource(resource_id);
EXPECT_EQ(string_piece.data(), result.data());
}
TEST_F(ResourceBundleTest, IsGzipped) {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
base::FilePath data_path =
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("sample.pak"));
// Dump contents into a pak file and load it.
ASSERT_TRUE(base::WriteFile(
data_path, {kSampleCompressPakContentsV5, kSampleCompressPakSizeV5}));
ResourceBundle* resource_bundle = CreateResourceBundle(nullptr);
resource_bundle->AddDataPackFromPath(data_path, k100Percent);
ASSERT_FALSE(resource_bundle->IsGzipped(1));
ASSERT_FALSE(resource_bundle->IsGzipped(4));
ASSERT_FALSE(resource_bundle->IsGzipped(6));
ASSERT_TRUE(resource_bundle->IsGzipped(8));
// Ask for a non-existent resource ID.
ASSERT_FALSE(resource_bundle->IsGzipped(200));
}
TEST_F(ResourceBundleTest, IsBrotli) {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
base::FilePath data_path =
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("sample.pak"));
// Dump contents into a pak file and load it.
ASSERT_TRUE(base::WriteFile(
data_path, {kSampleCompressPakContentsV5, kSampleCompressPakSizeV5}));
ResourceBundle* resource_bundle = CreateResourceBundle(nullptr);
resource_bundle->AddDataPackFromPath(data_path, k100Percent);
ASSERT_FALSE(resource_bundle->IsBrotli(1));
ASSERT_FALSE(resource_bundle->IsBrotli(4));
ASSERT_TRUE(resource_bundle->IsBrotli(6));
ASSERT_FALSE(resource_bundle->IsGzipped(6));
ASSERT_FALSE(resource_bundle->IsBrotli(8));
// Ask for non-existent resource ID.
ASSERT_FALSE(resource_bundle->IsBrotli(200));
}
TEST_F(ResourceBundleTest, DelegateGetLocalizedString) {
ResourceBundle* resource_bundle = CreateResourceBundle(&delegate_);
std::u16string data = u"My test data";
int resource_id = 5;
EXPECT_CALL(delegate_, GetLocalizedString(resource_id, _))
.Times(1)
.WillOnce(DoAll(SetArgPointee<1>(data), Return(true)));
std::u16string result = resource_bundle->GetLocalizedString(resource_id);
EXPECT_EQ(data, result);
}
TEST_F(ResourceBundleTest, OverrideStringResource) {
ResourceBundle* resource_bundle = CreateResourceBundle(nullptr);
std::u16string data = u"My test data";
int resource_id = 5;
std::u16string result = resource_bundle->GetLocalizedString(resource_id);
EXPECT_EQ(std::u16string(), result);
resource_bundle->OverrideLocaleStringResource(resource_id, data);
result = resource_bundle->GetLocalizedString(resource_id);
EXPECT_EQ(data, result);
}
#if DCHECK_IS_ON()
TEST_F(ResourceBundleTest, CanOverrideStringResources) {
ResourceBundle* resource_bundle = CreateResourceBundle(nullptr);
std::u16string data = u"My test data";
int resource_id = 5;
EXPECT_TRUE(
resource_bundle->get_can_override_locale_string_resources_for_test());
resource_bundle->GetLocalizedString(resource_id);
EXPECT_FALSE(
resource_bundle->get_can_override_locale_string_resources_for_test());
}
#endif
TEST_F(ResourceBundleTest, DelegateGetLocalizedStringWithOverride) {
ResourceBundle* resource_bundle = CreateResourceBundle(&delegate_);
std::u16string delegate_data = u"My delegate data";
int resource_id = 5;
EXPECT_CALL(delegate_, GetLocalizedString(resource_id, _))
.Times(1)
.WillOnce(DoAll(SetArgPointee<1>(delegate_data), Return(true)));
std::u16string override_data = u"My override data";
std::u16string result = resource_bundle->GetLocalizedString(resource_id);
EXPECT_EQ(delegate_data, result);
}
TEST_F(ResourceBundleTest, LocaleDataPakExists) {
// Check that ResourceBundle::LocaleDataPakExists returns the correct results.
EXPECT_TRUE(ResourceBundle::LocaleDataPakExists("en-US"));
EXPECT_FALSE(ResourceBundle::LocaleDataPakExists("not_a_real_locale"));
}
class ResourceBundleImageTest : public ResourceBundleTest {
public:
ResourceBundleImageTest() {}
ResourceBundleImageTest(const ResourceBundleImageTest&) = delete;
ResourceBundleImageTest& operator=(const ResourceBundleImageTest&) = delete;
~ResourceBundleImageTest() override {}
void SetUp() override {
ResourceBundleTest::SetUp();
// Create a temporary directory to write test resource bundles to.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
}
// Returns resource bundle which uses an empty data pak for locale data.
ResourceBundle* CreateResourceBundleWithEmptyLocalePak() {
// Write an empty data pak for locale data.
const base::FilePath& locale_path = dir_path().Append(
FILE_PATH_LITERAL("locale.pak"));
EXPECT_TRUE(
base::WriteFile(locale_path, {kEmptyPakContents, kEmptyPakSize}));
ui::ResourceBundle* resource_bundle = CreateResourceBundle(nullptr);
// Load the empty locale data pak.
resource_bundle->LoadTestResources(base::FilePath(), locale_path);
return resource_bundle;
}
// Returns the path of temporary directory to write test data packs into.
const base::FilePath& dir_path() { return temp_dir_.GetPath(); }
// Returns the number of DataPacks managed by |resource_bundle|.
size_t NumDataPacksInResourceBundle(ResourceBundle* resource_bundle) {
DCHECK(resource_bundle);
return resource_bundle->resource_handles_.size();
}
private:
std::unique_ptr<DataPack> locale_pack_;
};
TEST_F(ResourceBundleImageTest, HasDataResource) {
base::FilePath data_path = dir_path().Append(FILE_PATH_LITERAL("sample.pak"));
// Dump content into pak file.
ASSERT_TRUE(base::WriteFile(
data_path, {kSampleCompressPakContentsV5, kSampleCompressPakSizeV5}));
// Load pak file.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_path, kScaleFactorNone);
EXPECT_FALSE(resource_bundle->HasDataResource(1));
EXPECT_TRUE(resource_bundle->HasDataResource(4));
EXPECT_TRUE(resource_bundle->HasDataResource(6));
EXPECT_TRUE(resource_bundle->HasDataResource(8));
EXPECT_FALSE(resource_bundle->HasDataResource(200));
}
TEST_F(ResourceBundleImageTest, LoadDataResourceBytes) {
base::FilePath data_path = dir_path().Append(FILE_PATH_LITERAL("sample.pak"));
// Dump contents into the pak files.
ASSERT_TRUE(base::WriteFile(
data_path, {kSampleCompressPakContentsV5, kSampleCompressPakSizeV5}));
// Load pak file.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_path, kScaleFactorNone);
// Test normal uncompressed data.
scoped_refptr<base::RefCountedMemory> resource =
resource_bundle->LoadDataResourceBytes(4);
EXPECT_EQ("this is id 4", base::as_string_view(*resource));
// Test the brotli data.
scoped_refptr<base::RefCountedMemory> brotli_resource =
resource_bundle->LoadDataResourceBytes(6);
EXPECT_EQ("this is id 6", base::as_string_view(*brotli_resource));
// Test the gzipped data.
scoped_refptr<base::RefCountedMemory> gzip_resource =
resource_bundle->LoadDataResourceBytes(8);
EXPECT_EQ("this is id 8", base::as_string_view(*gzip_resource));
}
// Verify that we don't crash when trying to load a resource that is not found.
// In some cases, we fail to mmap resources.pak, but try to keep going anyway.
TEST_F(ResourceBundleImageTest, LoadDataResourceBytesNotFound) {
base::FilePath data_path = dir_path().Append(FILE_PATH_LITERAL("sample.pak"));
// Dump contents into the pak files.
ASSERT_TRUE(base::WriteFile(data_path, {kEmptyPakContents, kEmptyPakSize}));
// Create a resource bundle from the file.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_path, k100Percent);
const int kUnfoundResourceId = 10000;
EXPECT_EQ(nullptr,
resource_bundle->LoadDataResourceBytes(kUnfoundResourceId));
// Give a .pak file that doesn't exist so we will fail to load it.
resource_bundle->AddDataPackFromPath(
base::FilePath(FILE_PATH_LITERAL("non-existant-file.pak")),
ui::kScaleFactorNone);
EXPECT_EQ(nullptr,
resource_bundle->LoadDataResourceBytes(kUnfoundResourceId));
}
TEST_F(ResourceBundleImageTest, LoadDataResourceStringForScale) {
base::FilePath data_path = dir_path().Append(FILE_PATH_LITERAL("sample.pak"));
base::FilePath data_2x_path =
dir_path().Append(FILE_PATH_LITERAL("sample_2x.pak"));
// Dump content into pak files.
ASSERT_TRUE(base::WriteFile(
data_path, {kSampleCompressPakContentsV5, kSampleCompressPakSizeV5}));
ASSERT_TRUE(base::WriteFile(data_2x_path, {kSampleCompressScaledPakContents,
kSampleCompressScaledPakSize}));
// Load pak files.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_path, k100Percent);
resource_bundle->AddDataPackFromPath(data_2x_path, k200Percent);
// Resource ID 6 is brotlied and exists in both 1x and 2x paks, so we expect a
// different result when requesting the 2x scale.
EXPECT_EQ("this is id 6",
resource_bundle->LoadDataResourceStringForScale(6, k100Percent));
EXPECT_EQ("this is id 6 x2",
resource_bundle->LoadDataResourceStringForScale(6, k200Percent));
}
TEST_F(ResourceBundleImageTest, LoadLocalizedResourceString) {
base::FilePath data_path = dir_path().Append(FILE_PATH_LITERAL("sample.pak"));
// Dump content into pak file.
ASSERT_TRUE(base::WriteFile(
data_path, {kSampleCompressPakContentsV5, kSampleCompressPakSizeV5}));
// Load pak file.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_path, kScaleFactorNone);
resource_bundle->OverrideLocalePakForTest(data_path);
EXPECT_EQ("this is id 6", resource_bundle->LoadLocalizedResourceString(6));
EXPECT_EQ("this is id 8", resource_bundle->LoadLocalizedResourceString(8));
}
TEST_F(ResourceBundleImageTest, LoadDataResourceString) {
base::FilePath data_path = dir_path().Append(FILE_PATH_LITERAL("sample.pak"));
// Dump content into pak file.
ASSERT_TRUE(base::WriteFile(
data_path, {kSampleCompressPakContentsV5, kSampleCompressPakSizeV5}));
// Load pak file.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_path, kScaleFactorNone);
// Resource ID 6 is Brotli compressed, expect it to be uncompressed.
EXPECT_EQ("this is id 6", resource_bundle->LoadDataResourceString(6));
// Resource ID 8 is Gzip compressed, expect it to be uncompressed.
EXPECT_EQ("this is id 8", resource_bundle->LoadDataResourceString(8));
// Resource ID 4 is plain text (not compressed), expect to return as-is.
EXPECT_EQ("this is id 4", resource_bundle->LoadDataResourceString(4));
}
TEST_F(ResourceBundleImageTest, GetRawDataResource) {
base::FilePath data_path = dir_path().Append(FILE_PATH_LITERAL("sample.pak"));
base::FilePath data_2x_path =
dir_path().Append(FILE_PATH_LITERAL("sample_2x.pak"));
// Dump contents into the pak files.
ASSERT_TRUE(
base::WriteFile(data_path, {kSamplePakContentsV4, kSamplePakSizeV4}));
ASSERT_TRUE(
base::WriteFile(data_2x_path, {kSamplePakContents2x, kSamplePakSize2x}));
// Load the regular and 2x pak files.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_path, k100Percent);
resource_bundle->AddDataPackFromPath(data_2x_path, k200Percent);
// Resource ID 4 exists in both 1x and 2x paks, so we expect a different
// result when requesting the 2x scale.
EXPECT_EQ("this is id 4",
resource_bundle->GetRawDataResourceForScale(4, k100Percent));
EXPECT_EQ("this is id 4 2x",
resource_bundle->GetRawDataResourceForScale(4, k200Percent));
// Resource ID 6 only exists in the 1x pak so we expect the same resource
// for both scale factor requests.
EXPECT_EQ("this is id 6",
resource_bundle->GetRawDataResourceForScale(6, k100Percent));
EXPECT_EQ("this is id 6",
resource_bundle->GetRawDataResourceForScale(6, k200Percent));
}
// Test requesting image reps at various scale factors from the image returned
// via ResourceBundle::GetImageNamed().
TEST_F(ResourceBundleImageTest, GetImageNamed) {
#if BUILDFLAG(IS_WIN)
display::win::SetDefaultDeviceScaleFactor(2.0);
#endif
test::ScopedSetSupportedResourceScaleFactors scoped_supported(
{k100Percent, k200Percent});
base::FilePath data_1x_path = dir_path().AppendASCII("sample_1x.pak");
base::FilePath data_2x_path = dir_path().AppendASCII("sample_2x.pak");
// Create the pak files.
CreateDataPackWithSingleBitmap(data_1x_path, 10, std::string_view());
CreateDataPackWithSingleBitmap(data_2x_path, 20, std::string_view());
// Load the regular and 2x pak files.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_1x_path, k100Percent);
resource_bundle->AddDataPackFromPath(data_2x_path, k200Percent);
EXPECT_EQ(k200Percent, resource_bundle->GetMaxResourceScaleFactor());
gfx::ImageSkia* image_skia = resource_bundle->GetImageSkiaNamed(3);
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
// ChromeOS/Windows load highest scale factor first.
EXPECT_EQ(ui::k200Percent, GetSupportedResourceScaleFactor(
image_skia->image_reps()[0].scale()));
#else
EXPECT_EQ(ui::k100Percent, GetSupportedResourceScaleFactor(
image_skia->image_reps()[0].scale()));
#endif
// Resource ID 3 exists in both 1x and 2x paks. Image reps should be
// available for both scale factors in |image_skia|.
gfx::ImageSkiaRep image_rep = image_skia->GetRepresentation(
GetScaleForResourceScaleFactor(ui::k100Percent));
EXPECT_EQ(ui::k100Percent,
GetSupportedResourceScaleFactor(image_rep.scale()));
image_rep = image_skia->GetRepresentation(
GetScaleForResourceScaleFactor(ui::k200Percent));
EXPECT_EQ(ui::k200Percent,
GetSupportedResourceScaleFactor(image_rep.scale()));
// Requesting the 1.4x resource should return either the 1x or the 2x
// resource.
image_rep = image_skia->GetRepresentation(1.4f);
ResourceScaleFactor scale_factor =
GetSupportedResourceScaleFactor(image_rep.scale());
EXPECT_TRUE(scale_factor == ui::k100Percent ||
scale_factor == ui::k200Percent);
// ImageSkia scales image if the one for the requested scale factor is not
// available.
EXPECT_EQ(1.4f, image_skia->GetRepresentation(1.4f).scale());
}
// Test that GetImageNamed() behaves properly for images which GRIT has
// annotated as having fallen back to 1x.
TEST_F(ResourceBundleImageTest, GetImageNamedFallback1x) {
test::ScopedSetSupportedResourceScaleFactors scoped_supported(
{k100Percent, k200Percent});
base::FilePath data_path = dir_path().AppendASCII("sample.pak");
base::FilePath data_2x_path = dir_path().AppendASCII("sample_2x.pak");
// Create the pak files.
CreateDataPackWithSingleBitmap(data_path, 10, std::string_view());
// 2x data pack bitmap has custom chunk to indicate that the 2x bitmap is not
// available and that GRIT fell back to 1x.
CreateDataPackWithSingleBitmap(
data_2x_path, 10,
std::string_view(reinterpret_cast<const char*>(kPngScaleChunk),
std::size(kPngScaleChunk)));
// Load the regular and 2x pak files.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_path, k100Percent);
resource_bundle->AddDataPackFromPath(data_2x_path, k200Percent);
gfx::ImageSkia* image_skia = resource_bundle->GetImageSkiaNamed(3);
// The image rep for 2x should be available. It should be resized to the
// proper 2x size.
gfx::ImageSkiaRep image_rep = image_skia->GetRepresentation(
GetScaleForResourceScaleFactor(ui::k200Percent));
EXPECT_EQ(ui::k200Percent,
GetSupportedResourceScaleFactor(image_rep.scale()));
EXPECT_EQ(20, image_rep.pixel_width());
EXPECT_EQ(20, image_rep.pixel_height());
}
TEST_F(ResourceBundleImageTest, FallbackToNone) {
// Presents a consistent set of supported scale factors for all platforms.
// iOS does not include k100Percent, which breaks the test below.
test::ScopedSetSupportedResourceScaleFactors scoped_supported(
{k100Percent, k200Percent, k300Percent});
base::FilePath data_default_path = dir_path().AppendASCII("sample.pak");
// Create the pak files.
CreateDataPackWithSingleBitmap(data_default_path, 10, std::string_view());
// Load the regular pak files only.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_default_path, kScaleFactorNone);
gfx::ImageSkia* image_skia = resource_bundle->GetImageSkiaNamed(3);
EXPECT_EQ(1u, image_skia->image_reps().size());
EXPECT_TRUE(image_skia->image_reps()[0].unscaled());
EXPECT_EQ(ui::k100Percent, GetSupportedResourceScaleFactor(
image_skia->image_reps()[0].scale()));
}
#if BUILDFLAG(SKIA_SUPPORT_SKOTTIE) && BUILDFLAG(USE_BLINK)
TEST_F(ResourceBundleImageTest, Lottie) {
// Create the pak files.
const base::FilePath data_unscaled_path =
dir_path().AppendASCII("sample.pak");
const std::map<uint16_t, std::string_view> resources = {
std::make_pair(3u, kLottieData)};
DataPack::WritePack(data_unscaled_path, resources, ui::DataPack::BINARY);
// Load the unscaled pack file.
ResourceBundle* resource_bundle = CreateResourceBundleWithEmptyLocalePak();
resource_bundle->AddDataPackFromPath(data_unscaled_path, kScaleFactorNone);
std::optional<std::vector<uint8_t>> data = resource_bundle->GetLottieData(3);
ASSERT_TRUE(data.has_value());
EXPECT_TRUE(std::ranges::equal(*data, kLottieExpected));
test::ScopedSetSupportedResourceScaleFactors scoped_supported(
{k100Percent, k200Percent});
gfx::ImageSkia* image_skia = resource_bundle->GetImageSkiaNamed(3);
// Unscaled image should always return scale=1.
EXPECT_EQ(1.f, image_skia->GetRepresentation(2.f).scale());
EXPECT_EQ(1.f, image_skia->GetRepresentation(1.f).scale());
EXPECT_EQ(1.f, image_skia->GetRepresentation(1.4f).scale());
EXPECT_EQ(kLottieWidth, image_skia->width());
EXPECT_EQ(kLottieHeight, image_skia->height());
// Lottie resource should be 'unscaled'.
EXPECT_TRUE(image_skia->image_reps()[0].unscaled());
}
#endif // BUILDFLAG(SKIA_SUPPORT_SKOTTIE) && BUILDFLAG(USE_BLINK)
} // namespace ui