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 #ifndef UI_BASE_RESOURCE_RESOURCE_BUNDLE_H_
6 #define UI_BASE_RESOURCE_RESOURCE_BUNDLE_H_
7 
8 #include <stddef.h>
9 
10 #include <map>
11 #include <memory>
12 #include <string>
13 #include <unordered_map>
14 #include <vector>
15 
16 #include "base/component_export.h"
17 #include "base/files/file_path.h"
18 #include "base/files/memory_mapped_file.h"
19 #include "base/gtest_prod_util.h"
20 #include "base/macros.h"
21 #include "base/optional.h"
22 #include "base/sequence_checker.h"
23 #include "base/strings/string16.h"
24 #include "base/strings/string_piece.h"
25 #include "ui/base/layout.h"
26 #include "ui/gfx/font_list.h"
27 #include "ui/gfx/image/image.h"
28 #include "ui/gfx/native_widget_types.h"
29 
30 class SkBitmap;
31 
32 namespace base {
33 class File;
34 class Lock;
35 class RefCountedMemory;
36 }
37 
38 namespace ui {
39 
40 class DataPack;
41 class ResourceHandle;
42 
43 // ResourceBundle is a central facility to load images and other resources,
44 // such as theme graphics. Every resource is loaded only once.
COMPONENT_EXPORT(UI_BASE)45 class COMPONENT_EXPORT(UI_BASE) ResourceBundle {
46  public:
47   // Legacy font size deltas. Consider these to be magic numbers. New code
48   // should declare their own size delta constant using an identifier that
49   // imparts some semantic meaning.
50   static const int kSmallFontDelta = -1;
51   static const int kMediumFontDelta = 3;
52   static const int kLargeFontDelta = 8;
53 
54   // The constant added during the compression to the front of Brotli-compressed
55   // resources in Chromium. Compression occurs at tools/grit/grit/node/base.py.
56   static constexpr uint8_t kBrotliConst[] = {0x1e, 0x9b};
57   static const size_t kBrotliHeaderSize = 8;
58 
59   // Legacy font style mappings. TODO(tapted): Phase these out in favour of
60   // client code providing their own constant with the desired font size delta.
61   enum FontStyle {
62     SmallFont,
63     BaseFont,
64     BoldFont,
65     MediumFont,
66     MediumBoldFont,
67     LargeFont,
68   };
69 
70   enum LoadResources {
71     LOAD_COMMON_RESOURCES,
72     DO_NOT_LOAD_COMMON_RESOURCES
73   };
74 
75   // Delegate class that allows interception of pack file loading and resource
76   // requests. The methods of this class may be called on multiple threads.
77   // TODO(crbug.com/1146446): The interface and usage model of this class are
78   // clunky; it would be good to clean them up.
79   class Delegate {
80    public:
81     // Called before a resource pack file is loaded. Return the full path for
82     // the pack file to continue loading or an empty value to cancel loading.
83     // |pack_path| will contain the complete default path for the pack file if
84     // known or just the pack file name otherwise.
85     virtual base::FilePath GetPathForResourcePack(
86         const base::FilePath& pack_path,
87         ScaleFactor scale_factor) = 0;
88 
89     // Called before a locale pack file is loaded. Return the full path for
90     // the pack file to continue loading or an empty value to cancel loading.
91     // |pack_path| will contain the complete default path for the pack file if
92     // known or just the pack file name otherwise.
93     virtual base::FilePath GetPathForLocalePack(
94         const base::FilePath& pack_path,
95         const std::string& locale) = 0;
96 
97     // Return an image resource or an empty value to attempt retrieval of the
98     // default resource.
99     virtual gfx::Image GetImageNamed(int resource_id) = 0;
100 
101     // Return an image resource or an empty value to attempt retrieval of the
102     // default resource.
103     virtual gfx::Image GetNativeImageNamed(int resource_id) = 0;
104 
105     // Return a ref counted memory resource or null to attempt retrieval of the
106     // default resource.
107     virtual base::RefCountedMemory* LoadDataResourceBytes(
108         int resource_id,
109         ScaleFactor scale_factor) = 0;
110 
111     // Supports intercepting of ResourceBundle::LoadDataResourceString(): Return
112     // a populated base::Optional instance to override the value that
113     // ResourceBundle::LoadDataResourceString() would return by default, or an
114     // empty base::Optional instance to pass through to the default behavior of
115     // ResourceBundle::LoadDataResourceString().
116     virtual base::Optional<std::string> LoadDataResourceString(
117         int resource_id) = 0;
118 
119     // Retrieve a raw data resource. Return true if a resource was provided or
120     // false to attempt retrieval of the default resource.
121     virtual bool GetRawDataResource(int resource_id,
122                                     ScaleFactor scale_factor,
123                                     base::StringPiece* value) const = 0;
124 
125     // Retrieve a localized string. Return true if a string was provided or
126     // false to attempt retrieval of the default string.
127     virtual bool GetLocalizedString(int message_id,
128                                     base::string16* value) const = 0;
129 
130    protected:
131     virtual ~Delegate() {}
132   };
133 
134   // Initialize the ResourceBundle for this process. Does not take ownership of
135   // the |delegate| value. Returns the language selected or an empty string if
136   // no candidate bundle file could be determined, or crashes the process if a
137   // candidate could not be loaded (e.g., file not found or corrupted).  NOTE:
138   // Mac ignores this and always loads up resources for the language defined by
139   // the Cocoa UI (i.e., NSBundle does the language work).
140   //
141   // TODO(sergeyu): This method also loads common resources (i.e. chrome.pak).
142   // There is no way to specify which resource files are loaded, i.e. names of
143   // the files are hardcoded in ResourceBundle. Fix it to allow to specify which
144   // files are loaded (e.g. add a new method in Delegate).
145   // |load_resources| controls whether or not LoadCommonResources is called.
146   static std::string InitSharedInstanceWithLocale(
147       const std::string& pref_locale,
148       Delegate* delegate,
149       LoadResources load_resources);
150 
151   // Initialize the ResourceBundle using the given file region. If |region| is
152   // MemoryMappedFile::Region::kWholeFile, the entire |pak_file| is used.
153   // This allows the use of this function in a sandbox without local file
154   // access (as on Android).
155   static void InitSharedInstanceWithPakFileRegion(
156       base::File pak_file,
157       const base::MemoryMappedFile::Region& region);
158 
159   // Initialize the ResourceBundle using given data pack path for testing.
160   static void InitSharedInstanceWithPakPath(const base::FilePath& path);
161 
162   // Delete the ResourceBundle for this process if it exists.
163   static void CleanupSharedInstance();
164 
165   // Returns the existing shared instance and sets it to the given instance.
166   static ResourceBundle* SwapSharedInstanceForTesting(ResourceBundle* instance);
167 
168   // Returns true after the global resource loader instance has been created.
169   static bool HasSharedInstance();
170 
171   // Initialize the ResourceBundle using data pack from given buffer.
172   // Return the global resource loader instance.
173   static ResourceBundle& GetSharedInstance();
174 
175   // Loads a secondary locale data pack using the given file region.
176   void LoadSecondaryLocaleDataWithPakFileRegion(
177       base::File pak_file,
178       const base::MemoryMappedFile::Region& region);
179 
180   // Check if the .pak for the given locale exists.
181   static bool LocaleDataPakExists(const std::string& locale);
182 
183   // Registers additional data pack files with this ResourceBundle.  When
184   // looking for a DataResource, we will search these files after searching the
185   // main module. |path| should be the complete path to the pack file if known
186   // or just the pack file name otherwise (the delegate may optionally override
187   // this value). |scale_factor| is the scale of images in this resource pak
188   // relative to the images in the 1x resource pak. This method is not thread
189   // safe! You should call it immediately after calling InitSharedInstance.
190   void AddDataPackFromPath(const base::FilePath& path,
191                            ScaleFactor scale_factor);
192 
193   // Same as above but using an already open file.
194   void AddDataPackFromFile(base::File file, ScaleFactor scale_factor);
195 
196   // Same as above but using only a region (offset + size) of the file.
197   void AddDataPackFromFileRegion(base::File file,
198                                  const base::MemoryMappedFile::Region& region,
199                                  ScaleFactor scale_factor);
200 
201   // Same as above but using contents of the given buffer.
202   void AddDataPackFromBuffer(base::StringPiece buffer,
203                              ScaleFactor scale_factor);
204 
205   // Same as AddDataPackFromPath but does not log an error if the pack fails to
206   // load.
207   void AddOptionalDataPackFromPath(const base::FilePath& path,
208                                    ScaleFactor scale_factor);
209 
210   // Changes the locale for an already-initialized ResourceBundle, returning the
211   // name of the newly-loaded locale, or an empty string if initialization
212   // failed (e.g. resource bundle not found or corrupted). Future calls to get
213   // strings will return the strings for this new locale. This has no effect on
214   // existing or future image resources. |locale_resources_data_| is protected
215   // by a lock for the duration of the swap, as GetLocalizedString() may be
216   // concurrently invoked on another thread.
217   std::string ReloadLocaleResources(const std::string& pref_locale);
218 
219   // Gets image with the specified resource_id from the current module data.
220   // Returns a pointer to a shared instance of gfx::ImageSkia. This shared
221   // instance is owned by the resource bundle and should not be freed.
222   // TODO(pkotwicz): Make method return const gfx::ImageSkia*
223   //
224   // NOTE: GetNativeImageNamed is preferred for cross-platform gfx::Image use.
225   gfx::ImageSkia* GetImageSkiaNamed(int resource_id);
226 
227   // Gets an image resource from the current module data. This will load the
228   // image in Skia format by default. The ResourceBundle owns this.
229   gfx::Image& GetImageNamed(int resource_id);
230 
231   // Similar to GetImageNamed, but rather than loading the image in Skia format,
232   // it will load in the native platform type. This can avoid conversion from
233   // one image type to another. ResourceBundle owns the result.
234   //
235   // Note that if the same resource has already been loaded in GetImageNamed(),
236   // gfx::Image will perform a conversion, rather than using the native image
237   // loading code of ResourceBundle.
238   gfx::Image& GetNativeImageNamed(int resource_id);
239 
240   // Loads the raw bytes of a scale independent data resource or null.
241   base::RefCountedMemory* LoadDataResourceBytes(int resource_id) const;
242 
243   // Whether the |resource_id| is gzipped in this bundle. False is also returned
244   // if the resource is not found.
245   bool IsGzipped(int resource_id) const;
246 
247   // Whether the |resource_id| is brotli compressed in this bundle. False is
248   // also returned if the resource is not found.
249   bool IsBrotli(int resource_id) const;
250 
251   // Loads the raw bytes of a data resource nearest the scale factor
252   // |scale_factor| into |bytes|. If the resource is compressed, decompress
253   // before returning. Use ResourceHandle::SCALE_FACTOR_NONE for scale
254   // independent image resources (such as wallpaper). Returns null if we fail
255   // to read the resource.
256   base::RefCountedMemory* LoadDataResourceBytesForScale(
257       int resource_id,
258       ScaleFactor scale_factor) const;
259 
260   // Return the contents of a scale independent resource in a
261   // StringPiece given the resource id.
262   base::StringPiece GetRawDataResource(int resource_id) const;
263 
264   // Return the contents of a resource in a StringPiece given the resource id
265   // nearest the scale factor |scale_factor|.
266   // Use ResourceHandle::SCALE_FACTOR_NONE for scale independent image resources
267   // (such as wallpaper).
268   base::StringPiece GetRawDataResourceForScale(int resource_id,
269                                                ScaleFactor scale_factor) const;
270 
271   // Return the contents of a scale independent resource, decompressed
272   // into a newly allocated string given the resource id. Todo: Look into
273   // introducing an Async version of this function in the future.
274   // Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=973417
275   std::string LoadDataResourceString(int resource_id) const;
276 
277   // Return the contents of a scale dependent resource, decompressed into
278   // a newly allocated string given the resource id.
279   std::string LoadDataResourceStringForScale(int resource_id,
280                                              ScaleFactor scaling_factor) const;
281 
282   // Return the contents of a localized resource, decompressed into a
283   // newly allocated string given the resource id.
284   std::string LoadLocalizedResourceString(int resource_id) const;
285 
286   // Get a localized string given a message id.  Returns an empty string if the
287   // resource_id is not found.
288   base::string16 GetLocalizedString(int resource_id);
289 
290   // Get a localized resource (for example, localized image logo) given a
291   // resource id.
292   base::RefCountedMemory* LoadLocalizedResourceBytes(int resource_id) const;
293 
294   // Returns a font list derived from the platform-specific "Base" font list.
295   // The result is always cached and exists for the lifetime of the process.
296   const gfx::FontList& GetFontListWithDelta(
297       int size_delta,
298       gfx::Font::FontStyle style = gfx::Font::NORMAL,
299       gfx::Font::Weight weight = gfx::Font::Weight::NORMAL);
300 
301   // Returns a font list derived from the user-specified typeface. The
302   // result is always cached and exists for the lifetime of the process.
303   // If typeface is empty, we default to the platform-specific "Base" font
304   // list.
305   const gfx::FontList& GetFontListWithTypefaceAndDelta(
306       const std::string& typeface,
307       int size_delta,
308       gfx::Font::FontStyle style = gfx::Font::NORMAL,
309       gfx::Font::Weight weight = gfx::Font::Weight::NORMAL);
310 
311   // Returns the primary font from the FontList given by GetFontListWithDelta().
312   const gfx::Font& GetFontWithDelta(
313       int size_delta,
314       gfx::Font::FontStyle style = gfx::Font::NORMAL,
315       gfx::Font::Weight weight = gfx::Font::Weight::NORMAL);
316 
317   // Deprecated. Returns fonts using hard-coded size deltas implied by |style|.
318   const gfx::FontList& GetFontList(FontStyle style);
319   const gfx::Font& GetFont(FontStyle style);
320 
321   // Resets and reloads the cached fonts.  This is useful when the fonts of the
322   // system have changed, for example, when the locale has changed.
323   void ReloadFonts();
324 
325   // Overrides the path to the pak file from which the locale resources will be
326   // loaded. Pass an empty path to undo.
327   void OverrideLocalePakForTest(const base::FilePath& pak_path);
328 
329   // Overrides a localized string resource with the given string. If no delegate
330   // is present, the |string| will be returned when getting the localized string
331   // |resource_id|. If |ReloadLocaleResources| is called, all overrides are
332   // cleared. This is intended to be used in conjunction with field trials and
333   // the variations service to experiment with different UI strings. This method
334   // is not thread safe!
335   void OverrideLocaleStringResource(int resource_id,
336                                     const base::string16& string);
337 
338   // Returns the full pathname of the locale file to load, which may be a
339   // compressed locale file ending in .gz. Returns an empty path if |app_locale|
340   // is empty, the directory of locale files cannot be determined, or if the
341   // path to the directory of locale files is relative. If not empty, the
342   // returned path is not guaranteed to reference an existing file.
343   // Used on Android to load the local file in the browser process and pass it
344   // to the sandboxed renderer process.
345   static base::FilePath GetLocaleFilePath(const std::string& app_locale);
346 
347   // Returns the maximum scale factor currently loaded.
348   // Returns SCALE_FACTOR_100P if no resource is loaded.
349   ScaleFactor GetMaxScaleFactor() const;
350 
351   // Returns true if |scale_factor| is supported by this platform.
352   static bool IsScaleFactorSupported(ScaleFactor scale_factor);
353 
354   // Checks whether overriding locale strings is supported. This will fail with
355   // a DCHECK if the first string resource has already been queried.
356   void CheckCanOverrideStringResources();
357 
358   // Sets whether this ResourceBundle should mangle localized strings or not.
359   void set_mangle_localized_strings_for_test(bool mangle) {
360     mangle_localized_strings_ = mangle;
361   }
362 
363 #if DCHECK_IS_ON()
364   // Gets whether overriding locale strings is supported.
365   bool get_can_override_locale_string_resources_for_test() {
366     return can_override_locale_string_resources_;
367   }
368 #endif
369 
370  private:
371   FRIEND_TEST_ALL_PREFIXES(ResourceBundleTest, DelegateGetPathForLocalePack);
372   FRIEND_TEST_ALL_PREFIXES(ResourceBundleTest, DelegateGetImageNamed);
373   FRIEND_TEST_ALL_PREFIXES(ResourceBundleTest, DelegateGetNativeImageNamed);
374 
375   friend class ResourceBundleMacImageTest;
376   friend class ResourceBundleImageTest;
377   friend class ResourceBundleTest;
378   friend class ChromeBrowserMainMacBrowserTest;
379 
380   class ResourceBundleImageSource;
381   friend class ResourceBundleImageSource;
382 
383   struct FontKey;
384 
385   using IdToStringMap = std::unordered_map<int, base::string16>;
386 
387   // Ctor/dtor are private, since we're a singleton.
388   explicit ResourceBundle(Delegate* delegate);
389   ~ResourceBundle();
390 
391   // Shared initialization.
392   static void InitSharedInstance(Delegate* delegate);
393 
394   // Free skia_images_.
395   void FreeImages();
396 
397   // Load the main resources.
398   void LoadCommonResources();
399 
400   // Loads the resource paks chrome_{100,200}_percent.pak.
401   void LoadChromeResources();
402 
403   // Implementation for the public methods which add a DataPack from a path. If
404   // |optional| is false, an error is logged on failure to load.
405   void AddDataPackFromPathInternal(const base::FilePath& path,
406                                    ScaleFactor scale_factor,
407                                    bool optional);
408 
409   // Inserts |data_pack| to |data_pack_| and updates |max_scale_factor_|
410   // accordingly.
411   void AddDataPack(std::unique_ptr<DataPack> data_pack);
412 
413   // Try to load the locale specific strings from an external data module.
414   // Returns the locale that is loaded or an empty string if no resources were
415   // loaded. If |crash_on_failure| is true on non-Android platforms, the process
416   // is terminated if a candidate locale file could not be loaded.
417   std::string LoadLocaleResources(const std::string& pref_locale,
418                                   bool crash_on_failure);
419 
420   // Load test resources in given paths. If either path is empty an empty
421   // resource pack is loaded.
422   void LoadTestResources(const base::FilePath& path,
423                          const base::FilePath& locale_path);
424 
425   // Unload the locale specific strings and prepares to load new ones. See
426   // comments for ReloadLocaleResources().
427   void UnloadLocaleResources();
428 
429   // Initializes the font description of default gfx::FontList.
430   void InitDefaultFontList();
431 
432   // Fills the |bitmap| given the data file to look in and the |resource_id|.
433   // Returns false if the resource does not exist.
434   //
435   // If the call succeeds, |fell_back_to_1x| indicates whether Chrome's custom
436   // csCl PNG chunk is present (added by GRIT if it falls back to a 100% image).
437   bool LoadBitmap(const ResourceHandle& data_handle,
438                   int resource_id,
439                   SkBitmap* bitmap,
440                   bool* fell_back_to_1x) const;
441 
442   // Fills the |bitmap| given the |resource_id| and |scale_factor|.
443   // Returns false if the resource does not exist. This may fall back to
444   // the data pack with SCALE_FACTOR_NONE, and when this happens,
445   // |scale_factor| will be set to SCALE_FACTOR_NONE.
446   bool LoadBitmap(int resource_id,
447                   ScaleFactor* scale_factor,
448                   SkBitmap* bitmap,
449                   bool* fell_back_to_1x) const;
450 
451   // Returns true if missing scaled resources should be visually indicated when
452   // drawing the fallback (e.g., by tinting the image).
453   static bool ShouldHighlightMissingScaledResources();
454 
455   // Returns true if the data in |buf| is a PNG that has the special marker
456   // added by GRIT that indicates that the image is actually 1x data.
457   static bool PNGContainsFallbackMarker(const unsigned char* buf, size_t size);
458 
459   // A wrapper for PNGCodec::Decode that returns information about custom
460   // chunks. For security reasons we can't alter PNGCodec to return this
461   // information. Our PNG files are preprocessed by GRIT, and any special chunks
462   // should occur immediately after the IHDR chunk.
463   static bool DecodePNG(const unsigned char* buf,
464                         size_t size,
465                         SkBitmap* bitmap,
466                         bool* fell_back_to_1x);
467 
468   // Returns an empty image for when a resource cannot be loaded. This is a
469   // bright red bitmap.
470   gfx::Image& GetEmptyImage();
471 
472   const base::FilePath& GetOverriddenPakPath() const;
473 
474   // If mangling of localized strings is enabled, mangles |str| to make it
475   // longer and to add begin and end markers so that any truncation of it is
476   // visible and returns the mangled string. If not, returns |str|.
477   base::string16 MaybeMangleLocalizedString(const base::string16& str) const;
478 
479   // An internal implementation of |GetLocalizedString()| without setting the
480   // flag of whether overriding locale strings is supported to false. We don't
481   // update this flag only in |InitDefaultFontList()| which is called earlier
482   // than the overriding. This is okay, because the font list doesn't need to be
483   // overridden by variations.
484   base::string16 GetLocalizedStringImpl(int resource_id) const;
485 
486   // This pointer is guaranteed to outlive the ResourceBundle instance and may
487   // be null.
488   Delegate* delegate_;
489 
490   // Protects |locale_resources_data_|.
491   std::unique_ptr<base::Lock> locale_resources_data_lock_;
492 
493   // Handles for data sources.
494   std::unique_ptr<ResourceHandle> locale_resources_data_;
495   std::unique_ptr<ResourceHandle> secondary_locale_resources_data_;
496   std::vector<std::unique_ptr<ResourceHandle>> data_packs_;
497 
498   // The maximum scale factor currently loaded.
499   ScaleFactor max_scale_factor_;
500 
501   // Cached images. The ResourceBundle caches all retrieved images and keeps
502   // ownership of the pointers.
503   using ImageMap = std::map<int, gfx::Image>;
504   ImageMap images_;
505 
506   gfx::Image empty_image_;
507 
508   // The various font lists used, as a map from a signed size delta from the
509   // platform base font size, plus style, to the FontList. Cached to avoid
510   // repeated GDI creation/destruction and font derivation.
511   // Must be accessed only from UI thread.
512   std::map<FontKey, gfx::FontList> font_cache_;
513 
514   base::FilePath overridden_pak_path_;
515 
516   IdToStringMap overridden_locale_strings_;
517 
518 #if DCHECK_IS_ON()
519   bool can_override_locale_string_resources_ = true;
520 #endif
521 
522   bool is_test_resources_ = false;
523   bool mangle_localized_strings_ = false;
524 
525   SEQUENCE_CHECKER(sequence_checker_);
526 
527   DISALLOW_COPY_AND_ASSIGN(ResourceBundle);
528 };
529 
530 }  // namespace ui
531 
532 #endif  // UI_BASE_RESOURCE_RESOURCE_BUNDLE_H_
533