1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/base/resource/resource_bundle.h"
6
7 #include <stdint.h>
8
9 #include <limits>
10 #include <utility>
11 #include <vector>
12
13 #include "base/big_endian.h"
14 #include "base/command_line.h"
15 #include "base/files/file.h"
16 #include "base/files/file_util.h"
17 #include "base/logging.h"
18 #include "base/memory/ref_counted_memory.h"
19 #include "base/path_service.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_piece.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/synchronization/lock.h"
26 #include "build/build_config.h"
27 #include "net/filter/gzip_header.h"
28 #include "skia/ext/image_operations.h"
29 #include "third_party/brotli/include/brotli/decode.h"
30 #include "third_party/skia/include/core/SkBitmap.h"
31 #include "third_party/skia/include/core/SkColor.h"
32 #include "third_party/zlib/google/compression_utils.h"
33 #include "ui/base/buildflags.h"
34 #include "ui/base/l10n/l10n_util.h"
35 #include "ui/base/layout.h"
36 #include "ui/base/resource/data_pack.h"
37 #include "ui/base/ui_base_paths.h"
38 #include "ui/base/ui_base_switches.h"
39 #include "ui/display/display.h"
40 #include "ui/display/screen.h"
41 #include "ui/gfx/codec/jpeg_codec.h"
42 #include "ui/gfx/codec/png_codec.h"
43 #include "ui/gfx/geometry/safe_integer_conversions.h"
44 #include "ui/gfx/geometry/size_conversions.h"
45 #include "ui/gfx/image/image_skia.h"
46 #include "ui/gfx/image/image_skia_source.h"
47 #include "ui/strings/grit/app_locale_settings.h"
48
49 #if defined(OS_ANDROID)
50 #include "base/android/build_info.h"
51 #include "ui/base/resource/resource_bundle_android.h"
52 #endif
53
54 #if defined(OS_CHROMEOS)
55 #include "ui/gfx/platform_font_skia.h"
56 #endif
57
58 #if defined(OS_WIN)
59 #include "ui/display/win/dpi.h"
60 #endif
61
62 namespace ui {
63
64 namespace {
65
66 // PNG-related constants.
67 const unsigned char kPngMagic[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
68 const size_t kPngChunkMetadataSize = 12; // length, type, crc32
69 const unsigned char kPngScaleChunkType[4] = { 'c', 's', 'C', 'l' };
70 const unsigned char kPngDataChunkType[4] = { 'I', 'D', 'A', 'T' };
71
72 #if !defined(OS_MACOSX) || defined(TOOLKIT_QT)
73 const char kPakFileExtension[] = ".pak";
74 #endif
75
76 ResourceBundle* g_shared_instance_ = nullptr;
77
GetResourcesPakFilePath(const std::string & pak_name)78 base::FilePath GetResourcesPakFilePath(const std::string& pak_name) {
79 base::FilePath path;
80 if (base::PathService::Get(base::DIR_MODULE, &path))
81 return path.AppendASCII(pak_name.c_str());
82
83 // Return just the name of the pak file.
84 #if defined(OS_WIN)
85 return base::FilePath(base::ASCIIToUTF16(pak_name));
86 #else
87 return base::FilePath(pak_name.c_str());
88 #endif // OS_WIN
89 }
90
CreateEmptyBitmap()91 SkBitmap CreateEmptyBitmap() {
92 SkBitmap bitmap;
93 bitmap.allocN32Pixels(32, 32);
94 bitmap.eraseARGB(255, 255, 255, 0);
95 return bitmap;
96 }
97
98 // Helper function for determining whether a resource is gzipped.
HasGzipHeader(base::StringPiece data)99 bool HasGzipHeader(base::StringPiece data) {
100 net::GZipHeader header;
101 const char* header_end = nullptr;
102 net::GZipHeader::Status header_status =
103 header.ReadMore(data.data(), data.length(), &header_end);
104 return header_status == net::GZipHeader::COMPLETE_HEADER;
105 }
106
107 // Helper function for determining whether a resource is brotli compressed.
HasBrotliHeader(base::StringPiece data)108 bool HasBrotliHeader(base::StringPiece data) {
109 // Check that the data is brotli decoded by checking for kBrotliConst in
110 // header. Header added during compression at tools/grit/grit/node/base.py.
111 const uint8_t* data_bytes = reinterpret_cast<const uint8_t*>(data.data());
112 static_assert(base::size(ResourceBundle::kBrotliConst) == 2,
113 "Magic number should be 2 bytes long");
114 return data.size() >= ResourceBundle::kBrotliHeaderSize &&
115 *data_bytes == ResourceBundle::kBrotliConst[0] &&
116 *(data_bytes + 1) == ResourceBundle::kBrotliConst[1];
117 }
118
119 // Returns the uncompressed size of Brotli compressed |input| from header.
GetBrotliDecompressSize(base::StringPiece input)120 size_t GetBrotliDecompressSize(base::StringPiece input) {
121 CHECK(input.data());
122 CHECK(HasBrotliHeader(input));
123 const uint8_t* raw_input = reinterpret_cast<const uint8_t*>(input.data());
124 raw_input = raw_input + base::size(ResourceBundle::kBrotliConst);
125 // Get size of uncompressed resource from header.
126 uint64_t uncompress_size = 0;
127 int bytes_size = static_cast<int>(ResourceBundle::kBrotliHeaderSize -
128 base::size(ResourceBundle::kBrotliConst));
129 for (int i = 0; i < bytes_size; i++) {
130 uncompress_size |= static_cast<uint64_t>(*(raw_input + i)) << (i * 8);
131 }
132 return static_cast<size_t>(uncompress_size);
133 }
134
135 // Decompresses data in |input| using brotli, storing
136 // the result in |output|, which is resized as necessary. Returns true for
137 // success. To be used for grit compressed resources only.
BrotliDecompress(base::StringPiece input,std::string * output)138 bool BrotliDecompress(base::StringPiece input, std::string* output) {
139 size_t decompress_size = GetBrotliDecompressSize(input);
140 const uint8_t* raw_input = reinterpret_cast<const uint8_t*>(input.data());
141 raw_input = raw_input + ResourceBundle::kBrotliHeaderSize;
142
143 output->resize(decompress_size);
144 uint8_t* output_bytes =
145 reinterpret_cast<uint8_t*>(const_cast<char*>(output->data()));
146 return BrotliDecoderDecompress(
147 input.size() - ResourceBundle::kBrotliHeaderSize, raw_input,
148 &decompress_size, output_bytes) == BROTLI_DECODER_RESULT_SUCCESS;
149 }
150
151 // Helper function for decompressing resource.
DecompressIfNeeded(base::StringPiece data,std::string * output)152 void DecompressIfNeeded(base::StringPiece data, std::string* output) {
153 if (!data.empty() && HasGzipHeader(data)) {
154 bool success = compression::GzipUncompress(data, output);
155 DCHECK(success);
156 } else if (!data.empty() && HasBrotliHeader(data)) {
157 bool success = BrotliDecompress(data, output);
158 DCHECK(success);
159 } else {
160 // Assume the raw data is not compressed.
161 output->assign(data.data(), data.size());
162 }
163 }
164
165 } // namespace
166
167 // An ImageSkiaSource that loads bitmaps for the requested scale factor from
168 // ResourceBundle on demand for a given |resource_id|. If the bitmap for the
169 // requested scale factor does not exist, it will return the 1x bitmap scaled
170 // by the scale factor. This may lead to broken UI if the correct size of the
171 // scaled image is not exactly |scale_factor| * the size of the 1x resource.
172 // When --highlight-missing-scaled-resources flag is specified, scaled 1x images
173 // are higlighted by blending them with red.
174 class ResourceBundle::ResourceBundleImageSource : public gfx::ImageSkiaSource {
175 public:
ResourceBundleImageSource(ResourceBundle * rb,int resource_id)176 ResourceBundleImageSource(ResourceBundle* rb, int resource_id)
177 : rb_(rb), resource_id_(resource_id) {}
~ResourceBundleImageSource()178 ~ResourceBundleImageSource() override {}
179
180 // gfx::ImageSkiaSource overrides:
GetImageForScale(float scale)181 gfx::ImageSkiaRep GetImageForScale(float scale) override {
182 SkBitmap image;
183 bool fell_back_to_1x = false;
184 ScaleFactor scale_factor = GetSupportedScaleFactor(scale);
185 bool found = rb_->LoadBitmap(resource_id_, &scale_factor,
186 &image, &fell_back_to_1x);
187 if (!found) {
188 #if defined(OS_ANDROID)
189 // TODO(oshima): Android unit_tests runs at DSF=3 with 100P assets.
190 return gfx::ImageSkiaRep();
191 #else
192 NOTREACHED() << "Unable to load image with id " << resource_id_
193 << ", scale=" << scale;
194 return gfx::ImageSkiaRep(CreateEmptyBitmap(), scale);
195 #endif
196 }
197
198 // If the resource is in the package with SCALE_FACTOR_NONE, it
199 // can be used in any scale factor. The image is maked as "unscaled"
200 // so that the ImageSkia do not automatically scale.
201 if (scale_factor == ui::SCALE_FACTOR_NONE)
202 return gfx::ImageSkiaRep(image, 0.0f);
203
204 if (fell_back_to_1x) {
205 // GRIT fell back to the 100% image, so rescale it to the correct size.
206 image = skia::ImageOperations::Resize(
207 image,
208 skia::ImageOperations::RESIZE_LANCZOS3,
209 gfx::ToCeiledInt(image.width() * scale),
210 gfx::ToCeiledInt(image.height() * scale));
211 } else {
212 scale = GetScaleForScaleFactor(scale_factor);
213 }
214 return gfx::ImageSkiaRep(image, scale);
215 }
216
217 private:
218 ResourceBundle* rb_;
219 const int resource_id_;
220
221 DISALLOW_COPY_AND_ASSIGN(ResourceBundleImageSource);
222 };
223
224 struct ResourceBundle::FontKey {
FontKeyui::ResourceBundle::FontKey225 FontKey(const std::string& typeface,
226 int in_size_delta,
227 gfx::Font::FontStyle in_style,
228 gfx::Font::Weight in_weight)
229 : typeface(typeface),
230 size_delta(in_size_delta),
231 style(in_style),
232 weight(in_weight) {}
233
~FontKeyui::ResourceBundle::FontKey234 ~FontKey() {}
235
operator ==ui::ResourceBundle::FontKey236 bool operator==(const FontKey& rhs) const {
237 return std::tie(typeface, size_delta, style, weight) ==
238 std::tie(rhs.typeface, rhs.size_delta, rhs.style, rhs.weight);
239 }
240
operator <ui::ResourceBundle::FontKey241 bool operator<(const FontKey& rhs) const {
242 return std::tie(typeface, size_delta, style, weight) <
243 std::tie(rhs.typeface, rhs.size_delta, rhs.style, rhs.weight);
244 }
245
246 std::string typeface;
247 int size_delta;
248 gfx::Font::FontStyle style;
249 gfx::Font::Weight weight;
250 };
251
252 // static
InitSharedInstanceWithLocale(const std::string & pref_locale,Delegate * delegate,LoadResources load_resources)253 std::string ResourceBundle::InitSharedInstanceWithLocale(
254 const std::string& pref_locale,
255 Delegate* delegate,
256 LoadResources load_resources) {
257 InitSharedInstance(delegate);
258 if (load_resources == LOAD_COMMON_RESOURCES)
259 g_shared_instance_->LoadCommonResources();
260 std::string result = g_shared_instance_->LoadLocaleResources(pref_locale);
261 g_shared_instance_->InitDefaultFontList();
262 return result;
263 }
264
265 // static
InitSharedInstanceWithPakFileRegion(base::File pak_file,const base::MemoryMappedFile::Region & region)266 void ResourceBundle::InitSharedInstanceWithPakFileRegion(
267 base::File pak_file,
268 const base::MemoryMappedFile::Region& region) {
269 InitSharedInstance(nullptr);
270 auto data_pack = std::make_unique<DataPack>(SCALE_FACTOR_100P);
271 if (!data_pack->LoadFromFileRegion(std::move(pak_file), region)) {
272 LOG(WARNING) << "failed to load pak file";
273 NOTREACHED();
274 return;
275 }
276 g_shared_instance_->locale_resources_data_ = std::move(data_pack);
277 g_shared_instance_->InitDefaultFontList();
278 }
279
280 // static
InitSharedInstanceWithPakPath(const base::FilePath & path)281 void ResourceBundle::InitSharedInstanceWithPakPath(const base::FilePath& path) {
282 InitSharedInstance(nullptr);
283 g_shared_instance_->LoadTestResources(path, path);
284
285 g_shared_instance_->InitDefaultFontList();
286 }
287
288 // static
CleanupSharedInstance()289 void ResourceBundle::CleanupSharedInstance() {
290 delete g_shared_instance_;
291 g_shared_instance_ = nullptr;
292 }
293
294 // static
SwapSharedInstanceForTesting(ResourceBundle * instance)295 ResourceBundle* ResourceBundle::SwapSharedInstanceForTesting(
296 ResourceBundle* instance) {
297 ResourceBundle* ret = g_shared_instance_;
298 g_shared_instance_ = instance;
299 return ret;
300 }
301
302 // static
HasSharedInstance()303 bool ResourceBundle::HasSharedInstance() {
304 return g_shared_instance_ != nullptr;
305 }
306
307 // static
GetSharedInstance()308 ResourceBundle& ResourceBundle::GetSharedInstance() {
309 // Must call InitSharedInstance before this function.
310 CHECK(g_shared_instance_ != nullptr);
311 return *g_shared_instance_;
312 }
313
LoadSecondaryLocaleDataWithPakFileRegion(base::File pak_file,const base::MemoryMappedFile::Region & region)314 void ResourceBundle::LoadSecondaryLocaleDataWithPakFileRegion(
315 base::File pak_file,
316 const base::MemoryMappedFile::Region& region) {
317 auto data_pack = std::make_unique<DataPack>(SCALE_FACTOR_100P);
318 if (!data_pack->LoadFromFileRegion(std::move(pak_file), region)) {
319 LOG(WARNING) << "failed to load secondary pak file";
320 NOTREACHED();
321 return;
322 }
323 secondary_locale_resources_data_ = std::move(data_pack);
324 }
325
326 #if !defined(OS_ANDROID) && !defined(TOOLKIT_QT)
327 // static
LocaleDataPakExists(const std::string & locale)328 bool ResourceBundle::LocaleDataPakExists(const std::string& locale) {
329 return !GetLocaleFilePath(locale).empty();
330 }
331 #endif // !defined(OS_ANDROID)
332
AddDataPackFromPath(const base::FilePath & path,ScaleFactor scale_factor)333 void ResourceBundle::AddDataPackFromPath(const base::FilePath& path,
334 ScaleFactor scale_factor) {
335 AddDataPackFromPathInternal(path, scale_factor, false);
336 }
337
AddOptionalDataPackFromPath(const base::FilePath & path,ScaleFactor scale_factor)338 void ResourceBundle::AddOptionalDataPackFromPath(const base::FilePath& path,
339 ScaleFactor scale_factor) {
340 AddDataPackFromPathInternal(path, scale_factor, true);
341 }
342
AddDataPackFromFile(base::File file,ScaleFactor scale_factor)343 void ResourceBundle::AddDataPackFromFile(base::File file,
344 ScaleFactor scale_factor) {
345 AddDataPackFromFileRegion(std::move(file),
346 base::MemoryMappedFile::Region::kWholeFile,
347 scale_factor);
348 }
349
AddDataPackFromBuffer(base::StringPiece buffer,ScaleFactor scale_factor)350 void ResourceBundle::AddDataPackFromBuffer(base::StringPiece buffer,
351 ScaleFactor scale_factor) {
352 std::unique_ptr<DataPack> data_pack(new DataPack(scale_factor));
353 if (data_pack->LoadFromBuffer(buffer)) {
354 AddDataPack(std::move(data_pack));
355 } else {
356 LOG(ERROR) << "Failed to load data pack from buffer";
357 }
358 }
359
AddDataPackFromFileRegion(base::File file,const base::MemoryMappedFile::Region & region,ScaleFactor scale_factor)360 void ResourceBundle::AddDataPackFromFileRegion(
361 base::File file,
362 const base::MemoryMappedFile::Region& region,
363 ScaleFactor scale_factor) {
364 std::unique_ptr<DataPack> data_pack(new DataPack(scale_factor));
365 if (data_pack->LoadFromFileRegion(std::move(file), region)) {
366 AddDataPack(std::move(data_pack));
367 } else {
368 LOG(ERROR) << "Failed to load data pack from file."
369 << "\nSome features may not be available.";
370 }
371 }
372
373 #if !defined(OS_MACOSX) || defined(TOOLKIT_QT)
374 // static
GetLocaleFilePath(const std::string & app_locale)375 base::FilePath ResourceBundle::GetLocaleFilePath(
376 const std::string& app_locale) {
377 if (app_locale.empty())
378 return base::FilePath();
379
380 base::FilePath locale_file_path;
381
382 base::PathService::Get(ui::DIR_LOCALES, &locale_file_path);
383
384 if (!locale_file_path.empty()) {
385 #if defined(OS_ANDROID)
386 if (locale_file_path.value().find("chromium_tests") == std::string::npos) {
387 std::string extracted_file_suffix =
388 base::android::BuildInfo::GetInstance()->extracted_file_suffix();
389 locale_file_path = locale_file_path.AppendASCII(
390 app_locale + kPakFileExtension + extracted_file_suffix);
391 } else {
392 // TODO(agrieve): Update tests to not side-load pak files and remove
393 // this special-case. https://crbug.com/691719
394 locale_file_path =
395 locale_file_path.AppendASCII(app_locale + kPakFileExtension);
396 }
397 #else
398 locale_file_path =
399 locale_file_path.AppendASCII(app_locale + kPakFileExtension);
400 #endif
401 }
402
403 // Note: The delegate GetPathForLocalePack() override is currently only used
404 // by CastResourceDelegate, which does not call this function prior to
405 // initializing the ResourceBundle. This called earlier than that by the
406 // variations code which also has a CHECK that an inconsistent value does not
407 // get returned via VariationsService::EnsureLocaleEquals().
408 if (HasSharedInstance() && GetSharedInstance().delegate_) {
409 locale_file_path = GetSharedInstance().delegate_->GetPathForLocalePack(
410 locale_file_path, app_locale);
411 }
412
413 // Don't try to load empty values or values that are not absolute paths.
414 if (locale_file_path.empty() || !locale_file_path.IsAbsolute())
415 return base::FilePath();
416
417 if (base::PathExists(locale_file_path))
418 return locale_file_path;
419
420 return base::FilePath();
421 }
422 #endif
423
424 #if !defined(OS_ANDROID) && !defined(TOOLKIT_QT)
LoadLocaleResources(const std::string & pref_locale)425 std::string ResourceBundle::LoadLocaleResources(
426 const std::string& pref_locale) {
427 DCHECK(!locale_resources_data_.get()) << "locale.pak already loaded";
428 std::string app_locale = l10n_util::GetApplicationLocale(pref_locale);
429 base::FilePath locale_file_path = GetOverriddenPakPath();
430 if (locale_file_path.empty())
431 locale_file_path = GetLocaleFilePath(app_locale);
432
433 if (locale_file_path.empty()) {
434 // It's possible that there is no locale.pak.
435 LOG(WARNING) << "locale_file_path.empty() for locale " << app_locale;
436 return std::string();
437 }
438
439 std::unique_ptr<DataPack> data_pack(new DataPack(SCALE_FACTOR_100P));
440 if (!data_pack->LoadFromPath(locale_file_path)) {
441 LOG(ERROR) << "failed to load locale file: " << locale_file_path;
442 NOTREACHED();
443 return std::string();
444 }
445
446 locale_resources_data_ = std::move(data_pack);
447 return app_locale;
448 }
449 #endif // defined(OS_ANDROID)
450
LoadTestResources(const base::FilePath & path,const base::FilePath & locale_path)451 void ResourceBundle::LoadTestResources(const base::FilePath& path,
452 const base::FilePath& locale_path) {
453 is_test_resources_ = true;
454 DCHECK(!ui::GetSupportedScaleFactors().empty());
455 const ScaleFactor scale_factor(ui::GetSupportedScaleFactors()[0]);
456 // Use the given resource pak for both common and localized resources.
457 std::unique_ptr<DataPack> data_pack(new DataPack(scale_factor));
458 if (!path.empty() && data_pack->LoadFromPath(path))
459 AddDataPack(std::move(data_pack));
460
461 data_pack = std::make_unique<DataPack>(ui::SCALE_FACTOR_NONE);
462 if (!locale_path.empty() && data_pack->LoadFromPath(locale_path)) {
463 locale_resources_data_ = std::move(data_pack);
464 } else {
465 locale_resources_data_ = std::make_unique<DataPack>(ui::SCALE_FACTOR_NONE);
466 }
467 // This is necessary to initialize ICU since we won't be calling
468 // LoadLocaleResources in this case.
469 l10n_util::GetApplicationLocale(std::string());
470 }
471
UnloadLocaleResources()472 void ResourceBundle::UnloadLocaleResources() {
473 locale_resources_data_.reset();
474 secondary_locale_resources_data_.reset();
475 }
476
OverrideLocalePakForTest(const base::FilePath & pak_path)477 void ResourceBundle::OverrideLocalePakForTest(const base::FilePath& pak_path) {
478 overridden_pak_path_ = pak_path;
479 }
480
OverrideLocaleStringResource(int resource_id,const base::string16 & string)481 void ResourceBundle::OverrideLocaleStringResource(
482 int resource_id,
483 const base::string16& string) {
484 overridden_locale_strings_[resource_id] = string;
485 }
486
GetOverriddenPakPath()487 const base::FilePath& ResourceBundle::GetOverriddenPakPath() {
488 return overridden_pak_path_;
489 }
490
MaybeMangleLocalizedString(const base::string16 & str)491 base::string16 ResourceBundle::MaybeMangleLocalizedString(
492 const base::string16& str) {
493 if (!mangle_localized_strings_)
494 return str;
495
496 // IDS_MINIMUM_FONT_SIZE and friends are localization "strings" that are
497 // actually integral constants. These should not be mangled or they become
498 // impossible to parse.
499 int ignored;
500 if (base::StringToInt(str, &ignored))
501 return str;
502
503 // For a string S, produce [[ --- S --- ]], where the number of dashes is 1/4
504 // of the number of characters in S. This makes S something around 50-75%
505 // longer, except for extremely short strings, which get > 100% longer.
506 base::string16 start_marker = base::UTF8ToUTF16("[[");
507 base::string16 end_marker = base::UTF8ToUTF16("]]");
508 base::string16 dashes = base::string16(str.size() / 4, '-');
509 return base::JoinString({start_marker, dashes, str, dashes, end_marker},
510 base::UTF8ToUTF16(" "));
511 }
512
ReloadLocaleResources(const std::string & pref_locale)513 std::string ResourceBundle::ReloadLocaleResources(
514 const std::string& pref_locale) {
515 base::AutoLock lock_scope(*locale_resources_data_lock_);
516
517 // Remove all overriden strings, as they will not be valid for the new locale.
518 overridden_locale_strings_.clear();
519
520 UnloadLocaleResources();
521 return LoadLocaleResources(pref_locale);
522 }
523
GetImageSkiaNamed(int resource_id)524 gfx::ImageSkia* ResourceBundle::GetImageSkiaNamed(int resource_id) {
525 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
526
527 const gfx::ImageSkia* image = GetImageNamed(resource_id).ToImageSkia();
528 return const_cast<gfx::ImageSkia*>(image);
529 }
530
GetImageNamed(int resource_id)531 gfx::Image& ResourceBundle::GetImageNamed(int resource_id) {
532 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
533
534 // Check to see if the image is already in the cache.
535 auto found = images_.find(resource_id);
536 if (found != images_.end())
537 return found->second;
538
539 gfx::Image image;
540 if (delegate_)
541 image = delegate_->GetImageNamed(resource_id);
542
543 if (image.IsEmpty()) {
544 DCHECK(!data_packs_.empty()) << "Missing call to SetResourcesDataDLL?";
545
546 #if defined(OS_CHROMEOS)
547 ui::ScaleFactor scale_factor_to_load = GetMaxScaleFactor();
548 #elif defined(OS_WIN)
549 ui::ScaleFactor scale_factor_to_load =
550 display::win::GetDPIScale() > 1.25
551 ? GetMaxScaleFactor()
552 : ui::SCALE_FACTOR_100P;
553 #else
554 ui::ScaleFactor scale_factor_to_load = ui::SCALE_FACTOR_100P;
555 #endif
556 // TODO(oshima): Consider reading the image size from png IHDR chunk and
557 // skip decoding here and remove #ifdef below.
558 // ResourceBundle::GetSharedInstance() is destroyed after the
559 // BrowserMainLoop has finished running. |image_skia| is guaranteed to be
560 // destroyed before the resource bundle is destroyed.
561 gfx::ImageSkia image_skia(
562 std::make_unique<ResourceBundleImageSource>(this, resource_id),
563 GetScaleForScaleFactor(scale_factor_to_load));
564 if (image_skia.isNull()) {
565 LOG(WARNING) << "Unable to load image with id " << resource_id;
566 NOTREACHED(); // Want to assert in debug mode.
567 // The load failed to retrieve the image; show a debugging red square.
568 return GetEmptyImage();
569 }
570 image_skia.SetReadOnly();
571 image = gfx::Image(image_skia);
572 }
573
574 // The load was successful, so cache the image.
575 auto inserted = images_.emplace(resource_id, image);
576 DCHECK(inserted.second);
577 return inserted.first->second;
578 }
579
580 constexpr uint8_t ResourceBundle::kBrotliConst[];
581
LoadDataResourceBytes(int resource_id) const582 base::RefCountedMemory* ResourceBundle::LoadDataResourceBytes(
583 int resource_id) const {
584 return LoadDataResourceBytesForScale(resource_id, ui::SCALE_FACTOR_NONE);
585 }
586
LoadDataResourceBytesForScale(int resource_id,ScaleFactor scale_factor) const587 base::RefCountedMemory* ResourceBundle::LoadDataResourceBytesForScale(
588 int resource_id,
589 ScaleFactor scale_factor) const {
590 if (delegate_) {
591 base::RefCountedMemory* bytes =
592 delegate_->LoadDataResourceBytes(resource_id, scale_factor);
593 if (bytes)
594 return bytes;
595 }
596
597 base::StringPiece data =
598 GetRawDataResourceForScale(resource_id, scale_factor);
599 if (data.empty())
600 return nullptr;
601
602 if (HasGzipHeader(data) || HasBrotliHeader(data)) {
603 base::RefCountedString* bytes_string = new base::RefCountedString();
604 DecompressIfNeeded(data, &(bytes_string->data()));
605 return bytes_string;
606 }
607
608 return new base::RefCountedStaticMemory(data.data(), data.length());
609 }
610
GetRawDataResource(int resource_id) const611 base::StringPiece ResourceBundle::GetRawDataResource(int resource_id) const {
612 return GetRawDataResourceForScale(resource_id, ui::SCALE_FACTOR_NONE);
613 }
614
GetRawDataResourceForScale(int resource_id,ScaleFactor scale_factor) const615 base::StringPiece ResourceBundle::GetRawDataResourceForScale(
616 int resource_id,
617 ScaleFactor scale_factor) const {
618 base::StringPiece data;
619 if (delegate_ &&
620 delegate_->GetRawDataResource(resource_id, scale_factor, &data)) {
621 return data;
622 }
623
624 if (scale_factor != ui::SCALE_FACTOR_100P) {
625 for (size_t i = 0; i < data_packs_.size(); i++) {
626 if (data_packs_[i]->GetScaleFactor() == scale_factor &&
627 data_packs_[i]->GetStringPiece(static_cast<uint16_t>(resource_id),
628 &data))
629 return data;
630 }
631 }
632
633 for (size_t i = 0; i < data_packs_.size(); i++) {
634 if ((data_packs_[i]->GetScaleFactor() == ui::SCALE_FACTOR_100P ||
635 data_packs_[i]->GetScaleFactor() == ui::SCALE_FACTOR_200P ||
636 data_packs_[i]->GetScaleFactor() == ui::SCALE_FACTOR_300P ||
637 data_packs_[i]->GetScaleFactor() == ui::SCALE_FACTOR_NONE) &&
638 data_packs_[i]->GetStringPiece(static_cast<uint16_t>(resource_id),
639 &data)) {
640 return data;
641 }
642 }
643
644 return base::StringPiece();
645 }
646
LoadDataResourceString(int resource_id) const647 std::string ResourceBundle::LoadDataResourceString(int resource_id) const {
648 return LoadDataResourceStringForScale(resource_id, ui::SCALE_FACTOR_NONE);
649 }
650
LoadDataResourceStringForScale(int resource_id,ScaleFactor scaling_factor) const651 std::string ResourceBundle::LoadDataResourceStringForScale(
652 int resource_id,
653 ScaleFactor scaling_factor) const {
654 std::string output;
655 DecompressIfNeeded(GetRawDataResourceForScale(resource_id, scaling_factor),
656 &output);
657 return output;
658 }
659
LoadLocalizedResourceString(int resource_id) const660 std::string ResourceBundle::LoadLocalizedResourceString(int resource_id) const {
661 base::AutoLock lock_scope(*locale_resources_data_lock_);
662 base::StringPiece data;
663 if (!(locale_resources_data_.get() &&
664 locale_resources_data_->GetStringPiece(
665 static_cast<uint16_t>(resource_id), &data) &&
666 !data.empty())) {
667 if (secondary_locale_resources_data_.get() &&
668 secondary_locale_resources_data_->GetStringPiece(
669 static_cast<uint16_t>(resource_id), &data) &&
670 !data.empty()) {
671 } else {
672 data = GetRawDataResource(resource_id);
673 }
674 }
675 std::string output;
676 DecompressIfNeeded(data, &output);
677 return output;
678 }
679
IsGzipped(int resource_id) const680 bool ResourceBundle::IsGzipped(int resource_id) const {
681 base::StringPiece raw_data = GetRawDataResource(resource_id);
682 if (!raw_data.data())
683 return false;
684
685 return HasGzipHeader(raw_data);
686 }
687
IsBrotli(int resource_id) const688 bool ResourceBundle::IsBrotli(int resource_id) const {
689 base::StringPiece raw_data = GetRawDataResource(resource_id);
690 if (!raw_data.data())
691 return false;
692
693 return HasBrotliHeader(raw_data);
694 }
695
GetLocalizedString(int resource_id)696 base::string16 ResourceBundle::GetLocalizedString(int resource_id) {
697 #if DCHECK_IS_ON()
698 {
699 base::AutoLock lock_scope(*locale_resources_data_lock_);
700 // Overriding locale strings isn't supported if the first string resource
701 // has already been queried.
702 can_override_locale_string_resources_ = false;
703 }
704 #endif
705 return GetLocalizedStringImpl(resource_id);
706 }
707
LoadLocalizedResourceBytes(int resource_id)708 base::RefCountedMemory* ResourceBundle::LoadLocalizedResourceBytes(
709 int resource_id) {
710 {
711 base::AutoLock lock_scope(*locale_resources_data_lock_);
712 base::StringPiece data;
713
714 if (locale_resources_data_.get() &&
715 locale_resources_data_->GetStringPiece(
716 static_cast<uint16_t>(resource_id), &data) &&
717 !data.empty()) {
718 return new base::RefCountedStaticMemory(data.data(), data.length());
719 }
720
721 if (secondary_locale_resources_data_.get() &&
722 secondary_locale_resources_data_->GetStringPiece(
723 static_cast<uint16_t>(resource_id), &data) &&
724 !data.empty()) {
725 return new base::RefCountedStaticMemory(data.data(), data.length());
726 }
727 }
728 // Release lock_scope and fall back to main data pack.
729 return LoadDataResourceBytes(resource_id);
730 }
731
GetFontListWithDelta(int size_delta,gfx::Font::FontStyle style,gfx::Font::Weight weight)732 const gfx::FontList& ResourceBundle::GetFontListWithDelta(
733 int size_delta,
734 gfx::Font::FontStyle style,
735 gfx::Font::Weight weight) {
736 return GetFontListWithTypefaceAndDelta(/*typeface=*/std::string(), size_delta,
737 style, weight);
738 }
739
GetFontListWithTypefaceAndDelta(const std::string & typeface,int size_delta,gfx::Font::FontStyle style,gfx::Font::Weight weight)740 const gfx::FontList& ResourceBundle::GetFontListWithTypefaceAndDelta(
741 const std::string& typeface,
742 int size_delta,
743 gfx::Font::FontStyle style,
744 gfx::Font::Weight weight) {
745 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
746
747 const FontKey styled_key(typeface, size_delta, style, weight);
748
749 auto found = font_cache_.find(styled_key);
750 if (found != font_cache_.end())
751 return found->second;
752
753 const FontKey base_key(typeface, 0, gfx::Font::NORMAL,
754 gfx::Font::Weight::NORMAL);
755 gfx::FontList default_font_list = gfx::FontList();
756 gfx::FontList base_font_list =
757 typeface.empty()
758 ? default_font_list
759 : gfx::FontList({typeface}, default_font_list.GetFontStyle(),
760 default_font_list.GetFontSize(),
761 default_font_list.GetFontWeight());
762 font_cache_.emplace(base_key, base_font_list);
763 gfx::FontList& base = font_cache_.find(base_key)->second;
764 if (styled_key == base_key)
765 return base;
766
767 // Fonts of a given style are derived from the unstyled font of the same size.
768 // Cache the unstyled font by first inserting a default-constructed font list.
769 // Then, derive it for the initial insertion, or use the iterator that points
770 // to the existing entry that the insertion collided with.
771 const FontKey sized_key(typeface, size_delta, gfx::Font::NORMAL,
772 gfx::Font::Weight::NORMAL);
773 auto sized = font_cache_.emplace(sized_key, base_font_list);
774 if (sized.second)
775 sized.first->second = base.DeriveWithSizeDelta(size_delta);
776 if (styled_key == sized_key) {
777 return sized.first->second;
778 }
779
780 auto styled = font_cache_.emplace(styled_key, base_font_list);
781 DCHECK(styled.second); // Otherwise font_cache_.find(..) would have found it.
782 styled.first->second = sized.first->second.Derive(
783 0, sized.first->second.GetFontStyle() | style, weight);
784
785 return styled.first->second;
786 }
787
GetFontWithDelta(int size_delta,gfx::Font::FontStyle style,gfx::Font::Weight weight)788 const gfx::Font& ResourceBundle::GetFontWithDelta(int size_delta,
789 gfx::Font::FontStyle style,
790 gfx::Font::Weight weight) {
791 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
792 return GetFontListWithDelta(size_delta, style, weight).GetPrimaryFont();
793 }
794
GetFontList(FontStyle legacy_style)795 const gfx::FontList& ResourceBundle::GetFontList(FontStyle legacy_style) {
796 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
797 gfx::Font::Weight font_weight = gfx::Font::Weight::NORMAL;
798 if (legacy_style == BoldFont || legacy_style == MediumBoldFont)
799 font_weight = gfx::Font::Weight::BOLD;
800
801 int size_delta = 0;
802 switch (legacy_style) {
803 case SmallFont:
804 size_delta = kSmallFontDelta;
805 break;
806 case MediumFont:
807 case MediumBoldFont:
808 size_delta = kMediumFontDelta;
809 break;
810 case LargeFont:
811 size_delta = kLargeFontDelta;
812 break;
813 case BaseFont:
814 case BoldFont:
815 break;
816 }
817
818 return GetFontListWithDelta(size_delta, gfx::Font::NORMAL, font_weight);
819 }
820
GetFont(FontStyle style)821 const gfx::Font& ResourceBundle::GetFont(FontStyle style) {
822 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
823 return GetFontList(style).GetPrimaryFont();
824 }
825
ReloadFonts()826 void ResourceBundle::ReloadFonts() {
827 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
828 InitDefaultFontList();
829 font_cache_.clear();
830 }
831
GetMaxScaleFactor() const832 ScaleFactor ResourceBundle::GetMaxScaleFactor() const {
833 #if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_BSD)
834 return max_scale_factor_;
835 #else
836 return GetSupportedScaleFactors().back();
837 #endif
838 }
839
IsScaleFactorSupported(ScaleFactor scale_factor)840 bool ResourceBundle::IsScaleFactorSupported(ScaleFactor scale_factor) {
841 const std::vector<ScaleFactor>& supported_scale_factors =
842 ui::GetSupportedScaleFactors();
843 return base::Contains(supported_scale_factors, scale_factor);
844 }
845
CheckCanOverrideStringResources()846 void ResourceBundle::CheckCanOverrideStringResources() {
847 #if DCHECK_IS_ON()
848 base::AutoLock lock_scope(*locale_resources_data_lock_);
849 DCHECK(can_override_locale_string_resources_);
850 #endif
851 }
852
ResourceBundle(Delegate * delegate)853 ResourceBundle::ResourceBundle(Delegate* delegate)
854 : delegate_(delegate),
855 locale_resources_data_lock_(new base::Lock),
856 max_scale_factor_(SCALE_FACTOR_100P) {
857 mangle_localized_strings_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
858 switches::kMangleLocalizedStrings);
859 }
860
~ResourceBundle()861 ResourceBundle::~ResourceBundle() {
862 FreeImages();
863 UnloadLocaleResources();
864 }
865
866 // static
InitSharedInstance(Delegate * delegate)867 void ResourceBundle::InitSharedInstance(Delegate* delegate) {
868 DCHECK(g_shared_instance_ == nullptr) << "ResourceBundle initialized twice";
869 g_shared_instance_ = new ResourceBundle(delegate);
870 std::vector<ScaleFactor> supported_scale_factors;
871 #if defined(OS_IOS)
872 display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
873 if (display.device_scale_factor() > 2.0) {
874 DCHECK_EQ(3.0, display.device_scale_factor());
875 supported_scale_factors.push_back(SCALE_FACTOR_300P);
876 } else if (display.device_scale_factor() > 1.0) {
877 DCHECK_EQ(2.0, display.device_scale_factor());
878 supported_scale_factors.push_back(SCALE_FACTOR_200P);
879 } else {
880 supported_scale_factors.push_back(SCALE_FACTOR_100P);
881 }
882 #else
883 // On platforms other than iOS, 100P is always a supported scale factor.
884 // For Windows we have a separate case in this function.
885 supported_scale_factors.push_back(SCALE_FACTOR_100P);
886 #if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_WIN) || defined(OS_BSD)
887 supported_scale_factors.push_back(SCALE_FACTOR_200P);
888 #endif
889 #endif
890 ui::SetSupportedScaleFactors(supported_scale_factors);
891 }
892
FreeImages()893 void ResourceBundle::FreeImages() {
894 images_.clear();
895 }
896
LoadChromeResources()897 void ResourceBundle::LoadChromeResources() {
898 // Always load the 1x data pack first as the 2x data pack contains both 1x and
899 // 2x images. The 1x data pack only has 1x images, thus passes in an accurate
900 // scale factor to gfx::ImageSkia::AddRepresentation.
901 if (IsScaleFactorSupported(SCALE_FACTOR_100P)) {
902 AddDataPackFromPath(GetResourcesPakFilePath(
903 "chrome_100_percent.pak"), SCALE_FACTOR_100P);
904 }
905
906 if (IsScaleFactorSupported(SCALE_FACTOR_200P)) {
907 AddOptionalDataPackFromPath(GetResourcesPakFilePath(
908 "chrome_200_percent.pak"), SCALE_FACTOR_200P);
909 }
910 }
911
AddDataPackFromPathInternal(const base::FilePath & path,ScaleFactor scale_factor,bool optional)912 void ResourceBundle::AddDataPackFromPathInternal(
913 const base::FilePath& path,
914 ScaleFactor scale_factor,
915 bool optional) {
916 // Do not pass an empty |path| value to this method. If the absolute path is
917 // unknown pass just the pack file name.
918 DCHECK(!path.empty());
919
920 base::FilePath pack_path = path;
921 if (delegate_)
922 pack_path = delegate_->GetPathForResourcePack(pack_path, scale_factor);
923
924 // Don't try to load empty values or values that are not absolute paths.
925 if (pack_path.empty() || !pack_path.IsAbsolute())
926 return;
927
928 std::unique_ptr<DataPack> data_pack(new DataPack(scale_factor));
929 if (data_pack->LoadFromPath(pack_path)) {
930 AddDataPack(std::move(data_pack));
931 } else if (!optional) {
932 LOG(ERROR) << "Failed to load " << pack_path.value()
933 << "\nSome features may not be available.";
934 }
935 }
936
AddDataPack(std::unique_ptr<DataPack> data_pack)937 void ResourceBundle::AddDataPack(std::unique_ptr<DataPack> data_pack) {
938 #if DCHECK_IS_ON()
939 data_pack->CheckForDuplicateResources(data_packs_);
940 #endif
941
942 if (GetScaleForScaleFactor(data_pack->GetScaleFactor()) >
943 GetScaleForScaleFactor(max_scale_factor_))
944 max_scale_factor_ = data_pack->GetScaleFactor();
945
946 data_packs_.push_back(std::move(data_pack));
947 }
948
InitDefaultFontList()949 void ResourceBundle::InitDefaultFontList() {
950 #if defined(OS_CHROMEOS)
951 // InitDefaultFontList() is called earlier than overriding the locale strings.
952 // So we call the |GetLocalizedStringImpl()| which doesn't set the flag
953 // |can_override_locale_string_resources_| to false. This is okay, because the
954 // font list doesn't need to be overridden by variations.
955 std::string font_family =
956 base::UTF16ToUTF8(GetLocalizedStringImpl(IDS_UI_FONT_FAMILY_CROS));
957 gfx::FontList::SetDefaultFontDescription(font_family);
958
959 // TODO(yukishiino): Remove SetDefaultFontDescription() once the migration to
960 // the font list is done. We will no longer need SetDefaultFontDescription()
961 // after every client gets started using a FontList instead of a Font.
962 gfx::PlatformFontSkia::SetDefaultFontDescription(font_family);
963 #else
964 // Use a single default font as the default font list.
965 gfx::FontList::SetDefaultFontDescription(std::string());
966 #endif
967 }
968
LoadBitmap(const ResourceHandle & data_handle,int resource_id,SkBitmap * bitmap,bool * fell_back_to_1x) const969 bool ResourceBundle::LoadBitmap(const ResourceHandle& data_handle,
970 int resource_id,
971 SkBitmap* bitmap,
972 bool* fell_back_to_1x) const {
973 DCHECK(fell_back_to_1x);
974 scoped_refptr<base::RefCountedMemory> memory(
975 data_handle.GetStaticMemory(static_cast<uint16_t>(resource_id)));
976 if (!memory.get())
977 return false;
978
979 if (DecodePNG(memory->front(), memory->size(), bitmap, fell_back_to_1x))
980 return true;
981
982 #if !defined(OS_IOS)
983 // iOS does not compile or use the JPEG codec. On other platforms,
984 // 99% of our assets are PNGs, however fallback to JPEG.
985 std::unique_ptr<SkBitmap> jpeg_bitmap(
986 gfx::JPEGCodec::Decode(memory->front(), memory->size()));
987 if (jpeg_bitmap.get()) {
988 bitmap->swap(*jpeg_bitmap.get());
989 *fell_back_to_1x = false;
990 return true;
991 }
992 #endif
993
994 NOTREACHED() << "Unable to decode theme image resource " << resource_id;
995 return false;
996 }
997
LoadBitmap(int resource_id,ScaleFactor * scale_factor,SkBitmap * bitmap,bool * fell_back_to_1x) const998 bool ResourceBundle::LoadBitmap(int resource_id,
999 ScaleFactor* scale_factor,
1000 SkBitmap* bitmap,
1001 bool* fell_back_to_1x) const {
1002 DCHECK(fell_back_to_1x);
1003 for (const auto& pack : data_packs_) {
1004 if (pack->GetScaleFactor() == ui::SCALE_FACTOR_NONE &&
1005 LoadBitmap(*pack, resource_id, bitmap, fell_back_to_1x)) {
1006 DCHECK(!*fell_back_to_1x);
1007 *scale_factor = ui::SCALE_FACTOR_NONE;
1008 return true;
1009 }
1010
1011 if (pack->GetScaleFactor() == *scale_factor &&
1012 LoadBitmap(*pack, resource_id, bitmap, fell_back_to_1x)) {
1013 return true;
1014 }
1015 }
1016
1017 // Unit tests may only have 1x data pack. Allow them to fallback to 1x
1018 // resources.
1019 if (is_test_resources_ && *scale_factor != ui::SCALE_FACTOR_100P) {
1020 for (const auto& pack : data_packs_) {
1021 if (pack->GetScaleFactor() == ui::SCALE_FACTOR_100P &&
1022 LoadBitmap(*pack, resource_id, bitmap, fell_back_to_1x)) {
1023 *fell_back_to_1x = true;
1024 return true;
1025 }
1026 }
1027 }
1028
1029 return false;
1030 }
1031
GetEmptyImage()1032 gfx::Image& ResourceBundle::GetEmptyImage() {
1033 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
1034
1035 if (empty_image_.IsEmpty()) {
1036 // The placeholder bitmap is bright red so people notice the problem.
1037 SkBitmap bitmap = CreateEmptyBitmap();
1038 empty_image_ = gfx::Image::CreateFrom1xBitmap(bitmap);
1039 }
1040 return empty_image_;
1041 }
1042
GetLocalizedStringImpl(int resource_id)1043 base::string16 ResourceBundle::GetLocalizedStringImpl(int resource_id) {
1044 base::string16 string;
1045 if (delegate_ && delegate_->GetLocalizedString(resource_id, &string))
1046 return MaybeMangleLocalizedString(string);
1047
1048 // Ensure that ReloadLocaleResources() doesn't drop the resources while
1049 // we're using them.
1050 base::AutoLock lock_scope(*locale_resources_data_lock_);
1051
1052 IdToStringMap::const_iterator it =
1053 overridden_locale_strings_.find(resource_id);
1054 if (it != overridden_locale_strings_.end())
1055 return MaybeMangleLocalizedString(it->second);
1056
1057 // If for some reason we were unable to load the resources , return an empty
1058 // string (better than crashing).
1059 if (!locale_resources_data_.get()) {
1060 LOG(WARNING) << "locale resources are not loaded";
1061 return base::string16();
1062 }
1063
1064 base::StringPiece data;
1065 ResourceHandle::TextEncodingType encoding =
1066 locale_resources_data_->GetTextEncodingType();
1067 if (!locale_resources_data_->GetStringPiece(
1068 static_cast<uint16_t>(resource_id), &data)) {
1069 if (secondary_locale_resources_data_.get() &&
1070 secondary_locale_resources_data_->GetStringPiece(
1071 static_cast<uint16_t>(resource_id), &data)) {
1072 // Fall back on the secondary locale pak if it exists.
1073 encoding = secondary_locale_resources_data_->GetTextEncodingType();
1074 } else {
1075 // Fall back on the main data pack (shouldn't be any strings here except
1076 // in unittests).
1077 data = GetRawDataResource(resource_id);
1078 if (data.empty()) {
1079 LOG(WARNING) << "unable to find resource: " << resource_id;
1080 NOTREACHED();
1081 return base::string16();
1082 }
1083 }
1084 }
1085
1086 // Strings should not be loaded from a data pack that contains binary data.
1087 DCHECK(encoding == ResourceHandle::UTF16 || encoding == ResourceHandle::UTF8)
1088 << "requested localized string from binary pack file";
1089
1090 // Data pack encodes strings as either UTF8 or UTF16.
1091 base::string16 msg;
1092 if (encoding == ResourceHandle::UTF16) {
1093 msg = base::string16(reinterpret_cast<const base::char16*>(data.data()),
1094 data.length() / 2);
1095 } else if (encoding == ResourceHandle::UTF8) {
1096 msg = base::UTF8ToUTF16(data);
1097 }
1098 return MaybeMangleLocalizedString(msg);
1099 }
1100
1101 // static
PNGContainsFallbackMarker(const unsigned char * buf,size_t size)1102 bool ResourceBundle::PNGContainsFallbackMarker(const unsigned char* buf,
1103 size_t size) {
1104 if (size < base::size(kPngMagic) ||
1105 memcmp(buf, kPngMagic, base::size(kPngMagic)) != 0) {
1106 // Data invalid or a JPEG.
1107 return false;
1108 }
1109 size_t pos = base::size(kPngMagic);
1110
1111 // Scan for custom chunks until we find one, find the IDAT chunk, or run out
1112 // of chunks.
1113 for (;;) {
1114 if (size - pos < kPngChunkMetadataSize)
1115 break;
1116 uint32_t length = 0;
1117 base::ReadBigEndian(reinterpret_cast<const char*>(buf + pos), &length);
1118 if (size - pos - kPngChunkMetadataSize < length)
1119 break;
1120 if (length == 0 && memcmp(buf + pos + sizeof(uint32_t), kPngScaleChunkType,
1121 base::size(kPngScaleChunkType)) == 0) {
1122 return true;
1123 }
1124 if (memcmp(buf + pos + sizeof(uint32_t), kPngDataChunkType,
1125 base::size(kPngDataChunkType)) == 0) {
1126 // Stop looking for custom chunks, any custom chunks should be before an
1127 // IDAT chunk.
1128 break;
1129 }
1130 pos += length + kPngChunkMetadataSize;
1131 }
1132 return false;
1133 }
1134
1135 // static
DecodePNG(const unsigned char * buf,size_t size,SkBitmap * bitmap,bool * fell_back_to_1x)1136 bool ResourceBundle::DecodePNG(const unsigned char* buf,
1137 size_t size,
1138 SkBitmap* bitmap,
1139 bool* fell_back_to_1x) {
1140 *fell_back_to_1x = PNGContainsFallbackMarker(buf, size);
1141 return gfx::PNGCodec::Decode(buf, size, bitmap);
1142 }
1143
1144 } // namespace ui
1145