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