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 "chrome/browser/extensions/extension_web_ui.h"
6 
7 #include <stddef.h>
8 
9 #include <iterator>
10 #include <set>
11 #include <utility>
12 #include <vector>
13 
14 #include "base/bind.h"
15 #include "base/command_line.h"
16 #include "base/memory/scoped_refptr.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/threading/thread_task_runner_handle.h"
20 #include "base/values.h"
21 #include "chrome/browser/extensions/extension_tab_util.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/extensions/extension_constants.h"
25 #include "chrome/common/url_constants.h"
26 #include "components/favicon/core/favicon_service.h"
27 #include "components/favicon_base/favicon_util.h"
28 #include "components/pref_registry/pref_registry_syncable.h"
29 #include "components/prefs/pref_service.h"
30 #include "components/prefs/scoped_user_pref_update.h"
31 #include "content/public/browser/navigation_controller.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/browser/web_ui.h"
34 #include "content/public/common/bindings_policy.h"
35 #include "extensions/browser/extension_icon_placeholder.h"
36 #include "extensions/browser/extension_registry.h"
37 #include "extensions/browser/extension_util.h"
38 #include "extensions/browser/image_loader.h"
39 #include "extensions/common/extension.h"
40 #include "extensions/common/extension_icon_set.h"
41 #include "extensions/common/extension_resource.h"
42 #include "extensions/common/manifest_handlers/icons_handler.h"
43 #include "extensions/common/manifest_handlers/incognito_info.h"
44 #include "net/base/file_stream.h"
45 #include "third_party/skia/include/core/SkBitmap.h"
46 #include "ui/base/page_transition_types.h"
47 #include "ui/gfx/codec/png_codec.h"
48 #include "ui/gfx/favicon_size.h"
49 #include "ui/gfx/image/image_skia.h"
50 
51 using content::WebContents;
52 using extensions::Extension;
53 using extensions::URLOverrides;
54 
55 namespace {
56 
57 // The key to the override value for a page.
58 const char kEntry[] = "entry";
59 // The key to whether or not the override is active (i.e., can be used).
60 // Overrides may be inactive e.g. when an extension is disabled.
61 const char kActive[] = "active";
62 
63 // Iterates over |list| and:
64 // - Converts any entries of the form <entry> to
65 //   { 'entry': <entry>, 'active': true }.
66 // - Removes any duplicate entries.
67 // We do the conversion because we previously stored these values as strings
68 // rather than objects.
69 // TODO(devlin): Remove the conversion once everyone's updated.
InitializeOverridesList(base::ListValue * list)70 void InitializeOverridesList(base::ListValue* list) {
71   base::ListValue migrated;
72   std::set<std::string> seen_entries;
73   for (auto& val : *list) {
74     std::unique_ptr<base::DictionaryValue> new_dict(
75         new base::DictionaryValue());
76     std::string entry_name;
77     base::DictionaryValue* existing_dict = nullptr;
78     if (val.GetAsDictionary(&existing_dict)) {
79       bool success = existing_dict->GetString(kEntry, &entry_name);
80       if (!success)  // See comment about CHECK(success) in ForEachOverrideList.
81         continue;
82       new_dict->Swap(existing_dict);
83     } else if (val.GetAsString(&entry_name)) {
84       new_dict->SetString(kEntry, entry_name);
85       new_dict->SetBoolean(kActive, true);
86     } else {
87       NOTREACHED();
88       continue;
89     }
90 
91     if (seen_entries.count(entry_name) == 0) {
92       seen_entries.insert(entry_name);
93       migrated.Append(std::move(new_dict));
94     }
95   }
96 
97   list->Swap(&migrated);
98 }
99 
100 // Adds |override| to |list|, or, if there's already an entry for the override,
101 // marks it as active.
AddOverridesToList(base::ListValue * list,const GURL & override_url)102 void AddOverridesToList(base::ListValue* list, const GURL& override_url) {
103   const std::string& spec = override_url.spec();
104   for (auto& val : *list) {
105     base::DictionaryValue* dict = nullptr;
106     std::string entry;
107     if (!val.GetAsDictionary(&dict) || !dict->GetString(kEntry, &entry)) {
108       NOTREACHED();
109       continue;
110     }
111     if (entry == spec) {
112       dict->SetBoolean(kActive, true);
113       return;  // All done!
114     }
115     GURL entry_url(entry);
116     if (!entry_url.is_valid()) {
117       NOTREACHED();
118       continue;
119     }
120     if (entry_url.host() == override_url.host()) {
121       dict->SetBoolean(kActive, true);
122       dict->SetString(kEntry, spec);
123       return;
124     }
125   }
126 
127   auto dict = std::make_unique<base::DictionaryValue>();
128   dict->SetString(kEntry, spec);
129   dict->SetBoolean(kActive, true);
130   // Add the entry to the front of the list.
131   list->Insert(0, std::move(dict));
132 }
133 
134 // Validates that each entry in |list| contains a valid url and points to an
135 // extension contained in |all_extensions| (and, if not, removes it).
ValidateOverridesList(const extensions::ExtensionSet * all_extensions,base::ListValue * list)136 void ValidateOverridesList(const extensions::ExtensionSet* all_extensions,
137                            base::ListValue* list) {
138   base::ListValue migrated;
139   std::set<std::string> seen_hosts;
140   for (auto& val : *list) {
141     base::DictionaryValue* dict = nullptr;
142     std::string entry;
143     if (!val.GetAsDictionary(&dict) || !dict->GetString(kEntry, &entry)) {
144       NOTREACHED();
145       continue;
146     }
147     std::unique_ptr<base::DictionaryValue> new_dict(
148         new base::DictionaryValue());
149     new_dict->Swap(dict);
150     GURL override_url(entry);
151     if (!override_url.is_valid())
152       continue;
153 
154     if (!all_extensions->GetByID(override_url.host()))
155       continue;
156 
157     // If we've already seen this extension, remove the entry. Only retain the
158     // most recent entry for each extension.
159     if (!seen_hosts.insert(override_url.host()).second)
160       continue;
161 
162     migrated.Append(std::move(new_dict));
163   }
164 
165   list->Swap(&migrated);
166 }
167 
168 // Reloads the page in |web_contents| if it uses the same profile as |profile|
169 // and if the current URL is a chrome URL.
UnregisterAndReplaceOverrideForWebContents(const std::string & page,Profile * profile,WebContents * web_contents)170 void UnregisterAndReplaceOverrideForWebContents(const std::string& page,
171                                                 Profile* profile,
172                                                 WebContents* web_contents) {
173   if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile)
174     return;
175 
176   const GURL& url = web_contents->GetLastCommittedURL();
177   if (!url.SchemeIs(content::kChromeUIScheme) || url.host_piece() != page)
178     return;
179 
180   // Don't use Reload() since |url| isn't the same as the internal URL that
181   // NavigationController has.
182   web_contents->GetController().LoadURL(
183       url,
184       content::Referrer::SanitizeForRequest(
185           url,
186           content::Referrer(url, network::mojom::ReferrerPolicy::kDefault)),
187       ui::PAGE_TRANSITION_RELOAD, std::string());
188 }
189 
190 enum UpdateBehavior {
191   UPDATE_DEACTIVATE,  // Mark 'active' as false.
192   UPDATE_REMOVE,      // Remove the entry from the list.
193 };
194 
195 // Updates the entry (if any) for |override_url| in |overrides_list| according
196 // to |behavior|. Returns true if anything changed.
UpdateOverridesList(base::ListValue * overrides_list,const std::string & override_url,UpdateBehavior behavior)197 bool UpdateOverridesList(base::ListValue* overrides_list,
198                          const std::string& override_url,
199                          UpdateBehavior behavior) {
200   auto iter = std::find_if(
201       overrides_list->begin(), overrides_list->end(),
202       [&override_url](const base::Value& value) {
203         std::string entry;
204         const base::DictionaryValue* dict = nullptr;
205         return value.GetAsDictionary(&dict) &&
206                dict->GetString(kEntry, &entry) && entry == override_url;
207       });
208   if (iter != overrides_list->end()) {
209     switch (behavior) {
210       case UPDATE_DEACTIVATE: {
211         base::DictionaryValue* dict = nullptr;
212         bool success = iter->GetAsDictionary(&dict);
213         // See comment about CHECK(success) in ForEachOverrideList.
214         if (success) {
215           dict->SetBoolean(kActive, false);
216           break;
217         }
218         // Else fall through and erase the broken pref.
219         FALLTHROUGH;
220       }
221       case UPDATE_REMOVE:
222         overrides_list->Erase(iter, nullptr);
223         break;
224     }
225     return true;
226   }
227   return false;
228 }
229 
230 // Updates each list referenced in |overrides| according to |behavior|.
UpdateOverridesLists(Profile * profile,const URLOverrides::URLOverrideMap & overrides,UpdateBehavior behavior)231 void UpdateOverridesLists(Profile* profile,
232                           const URLOverrides::URLOverrideMap& overrides,
233                           UpdateBehavior behavior) {
234   if (overrides.empty())
235     return;
236   PrefService* prefs = profile->GetPrefs();
237   DictionaryPrefUpdate update(prefs, ExtensionWebUI::kExtensionURLOverrides);
238   base::DictionaryValue* all_overrides = update.Get();
239   for (const auto& page_override_pair : overrides) {
240     base::ListValue* page_overrides = nullptr;
241     // If it's being unregistered, it should already be in the list.
242     if (!all_overrides->GetList(page_override_pair.first, &page_overrides)) {
243       NOTREACHED();
244       continue;
245     }
246     if (UpdateOverridesList(page_overrides, page_override_pair.second.spec(),
247                             behavior)) {
248       // This is the active override, so we need to find all existing
249       // tabs for this override and get them to reload the original URL.
250       base::Callback<void(WebContents*)> callback =
251           base::Bind(&UnregisterAndReplaceOverrideForWebContents,
252                      page_override_pair.first, profile);
253       extensions::ExtensionTabUtil::ForEachTab(callback);
254     }
255   }
256 }
257 
258 // Run favicon callback with image result. If no favicon was available then
259 // |image| will be empty.
RunFaviconCallbackAsync(favicon_base::FaviconResultsCallback callback,const gfx::Image & image)260 void RunFaviconCallbackAsync(favicon_base::FaviconResultsCallback callback,
261                              const gfx::Image& image) {
262   std::vector<favicon_base::FaviconRawBitmapResult> favicon_bitmap_results;
263 
264   const std::vector<gfx::ImageSkiaRep>& image_reps =
265       image.AsImageSkia().image_reps();
266   for (size_t i = 0; i < image_reps.size(); ++i) {
267     const gfx::ImageSkiaRep& image_rep = image_reps[i];
268     auto bitmap_data = base::MakeRefCounted<base::RefCountedBytes>();
269     if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.GetBitmap(), false,
270                                           &bitmap_data->data())) {
271       favicon_base::FaviconRawBitmapResult bitmap_result;
272       bitmap_result.bitmap_data = bitmap_data;
273       bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(),
274                                             image_rep.pixel_height());
275       // Leave |bitmap_result|'s icon URL as the default of GURL().
276       bitmap_result.icon_type = favicon_base::IconType::kFavicon;
277 
278       favicon_bitmap_results.push_back(bitmap_result);
279     } else {
280       NOTREACHED() << "Could not encode extension favicon";
281     }
282   }
283 
284   base::ThreadTaskRunnerHandle::Get()->PostTask(
285       FROM_HERE,
286       base::BindOnce(std::move(callback), std::move(favicon_bitmap_results)));
287 }
288 
ValidateOverrideURL(const base::Value * override_url_value,const GURL & source_url,const extensions::ExtensionSet & extensions,GURL * override_url,const Extension ** extension)289 bool ValidateOverrideURL(const base::Value* override_url_value,
290                          const GURL& source_url,
291                          const extensions::ExtensionSet& extensions,
292                          GURL* override_url,
293                          const Extension** extension) {
294   const base::DictionaryValue* dict = nullptr;
295   std::string override;
296   bool is_active = false;
297   if (!override_url_value || !override_url_value->GetAsDictionary(&dict) ||
298       !dict->GetBoolean(kActive, &is_active) || !is_active ||
299       !dict->GetString(kEntry, &override)) {
300     return false;
301   }
302   if (!source_url.query().empty())
303     override += "?" + source_url.query();
304   if (!source_url.ref().empty())
305     override += "#" + source_url.ref();
306   *override_url = GURL(override);
307   if (!override_url->is_valid()) {
308     return false;
309   }
310   *extension = extensions.GetByID(override_url->host());
311   if (!*extension) {
312     return false;
313   }
314   return true;
315 }
316 
317 // Fetches each list in the overrides dictionary and runs |callback| on it.
ForEachOverrideList(Profile * profile,const base::Callback<void (base::ListValue *)> & callback)318 void ForEachOverrideList(
319     Profile* profile,
320     const base::Callback<void(base::ListValue*)>& callback) {
321   PrefService* prefs = profile->GetPrefs();
322   DictionaryPrefUpdate update(prefs, ExtensionWebUI::kExtensionURLOverrides);
323   base::DictionaryValue* all_overrides = update.Get();
324 
325   // DictionaryValue::Iterator cannot be used to modify the list. Generate the
326   // set of keys instead.
327   std::vector<std::string> keys;
328   for (base::DictionaryValue::Iterator iter(*all_overrides);
329        !iter.IsAtEnd(); iter.Advance()) {
330     keys.push_back(iter.key());
331   }
332   for (const std::string& key : keys) {
333     base::ListValue* list = nullptr;
334     bool success = all_overrides->GetList(key, &list);
335     // In a perfect world, we could CHECK(success) here. Unfortunately, if a
336     // user's prefs are mangled (by malware, user modification, hard drive
337     // corruption, evil robots, etc), this will fail. Instead, delete the pref.
338     if (!success) {
339       all_overrides->Remove(key, nullptr);
340       continue;
341     }
342     callback.Run(list);
343   }
344 }
345 
346 // A helper method to retrieve active overrides for the given |url|, if any. If
347 // |get_all| is true, this will retrieve all active overrides; otherwise it will
348 // return the highest-priority one (potentially early-out-ing). The resulting
349 // vector is ordered by priority.
GetOverridesForChromeURL(const GURL & url,content::BrowserContext * browser_context,bool get_all)350 std::vector<GURL> GetOverridesForChromeURL(
351     const GURL& url,
352     content::BrowserContext* browser_context,
353     bool get_all) {
354   // Only chrome: URLs can be overridden like this.
355   DCHECK(url.SchemeIs(content::kChromeUIScheme));
356 
357   Profile* profile = Profile::FromBrowserContext(browser_context);
358   const base::DictionaryValue* overrides = profile->GetPrefs()->GetDictionary(
359       ExtensionWebUI::kExtensionURLOverrides);
360 
361   const base::ListValue* url_list = nullptr;
362   if (!overrides || !overrides->GetList(url.host_piece(), &url_list))
363     return {};  // No overrides present for this host.
364 
365   extensions::ExtensionRegistry* registry =
366       extensions::ExtensionRegistry::Get(browser_context);
367   const extensions::ExtensionSet& extensions = registry->enabled_extensions();
368 
369   // Separate out overrides from non-component extensions (higher priority).
370   std::vector<GURL> override_urls;
371   std::vector<GURL> component_overrides;
372 
373   // Iterate over the URL list looking for suitable overrides.
374   for (const auto& value : *url_list) {
375     GURL override_url;
376     const Extension* extension = nullptr;
377     if (!ValidateOverrideURL(&value, url, extensions, &override_url,
378                              &extension)) {
379       // Invalid overrides are cleaned up on startup.
380       continue;
381     }
382 
383     // We can't handle chrome-extension URLs in incognito mode unless the
384     // extension uses split mode.
385     bool incognito_override_allowed =
386         extensions::IncognitoInfo::IsSplitMode(extension) &&
387         extensions::util::IsIncognitoEnabled(extension->id(), profile);
388     if (profile->IsOffTheRecord() && !incognito_override_allowed) {
389       continue;
390     }
391 
392     if (extensions::Manifest::IsComponentLocation(extension->location())) {
393       component_overrides.push_back(override_url);
394     } else {
395       override_urls.push_back(override_url);
396       if (!get_all) {  // Early out, since the highest-priority was found.
397         DCHECK_EQ(1u, override_urls.size());
398         return override_urls;
399       }
400     }
401   }
402 
403   if (!get_all) {
404     // Since component overrides are lower priority, we should only get here if
405     // there are no non-component overrides.
406     DCHECK(override_urls.empty());
407     // Return the highest-priority component override, if any.
408     if (component_overrides.size() > 1u) {
409       component_overrides.erase(component_overrides.begin() + 1,
410                                 component_overrides.end());
411     }
412     return component_overrides;
413   }
414 
415   override_urls.insert(override_urls.end(),
416                        std::make_move_iterator(component_overrides.begin()),
417                        std::make_move_iterator(component_overrides.end()));
418   return override_urls;
419 }
420 
421 }  // namespace
422 
423 const char ExtensionWebUI::kExtensionURLOverrides[] =
424     "extensions.chrome_url_overrides";
425 
426 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)427 void ExtensionWebUI::RegisterProfilePrefs(
428     user_prefs::PrefRegistrySyncable* registry) {
429   registry->RegisterDictionaryPref(kExtensionURLOverrides);
430 }
431 
432 // static
HandleChromeURLOverride(GURL * url,content::BrowserContext * browser_context)433 bool ExtensionWebUI::HandleChromeURLOverride(
434     GURL* url,
435     content::BrowserContext* browser_context) {
436   if (!url->SchemeIs(content::kChromeUIScheme))
437     return false;
438 
439   std::vector<GURL> overrides =
440       GetOverridesForChromeURL(*url, browser_context, /*get_all=*/false);
441   if (overrides.empty())
442     return false;
443 
444   *url = overrides[0];
445   return true;
446 }
447 
448 // static
HandleChromeURLOverrideReverse(GURL * url,content::BrowserContext * browser_context)449 bool ExtensionWebUI::HandleChromeURLOverrideReverse(
450     GURL* url, content::BrowserContext* browser_context) {
451   Profile* profile = Profile::FromBrowserContext(browser_context);
452   const base::DictionaryValue* overrides =
453       profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
454   if (!overrides)
455     return false;
456 
457   // Find the reverse mapping based on the given URL. For example this maps the
458   // internal URL
459   // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to
460   // chrome://bookmarks/#1 for display in the omnibox.
461   for (base::DictionaryValue::Iterator dict_iter(*overrides);
462        !dict_iter.IsAtEnd(); dict_iter.Advance()) {
463     const base::ListValue* url_list = nullptr;
464     if (!dict_iter.value().GetAsList(&url_list))
465       continue;
466 
467     for (auto list_iter = url_list->begin(); list_iter != url_list->end();
468          ++list_iter) {
469       const base::DictionaryValue* dict = nullptr;
470       if (!list_iter->GetAsDictionary(&dict))
471         continue;
472       std::string override;
473       if (!dict->GetString(kEntry, &override))
474         continue;
475       if (base::StartsWith(url->spec(), override,
476                            base::CompareCase::SENSITIVE)) {
477         GURL original_url(content::kChromeUIScheme + std::string("://") +
478                           dict_iter.key() +
479                           url->spec().substr(override.length()));
480         *url = original_url;
481         return true;
482       }
483     }
484   }
485 
486   return false;
487 }
488 
489 // static
GetExtensionControllingURL(const GURL & url,content::BrowserContext * browser_context)490 const extensions::Extension* ExtensionWebUI::GetExtensionControllingURL(
491     const GURL& url,
492     content::BrowserContext* browser_context) {
493   GURL mutable_url(url);
494   if (!HandleChromeURLOverride(&mutable_url, browser_context))
495     return nullptr;
496 
497   DCHECK_NE(url, mutable_url);
498   DCHECK(mutable_url.SchemeIs(extensions::kExtensionScheme));
499 
500   const extensions::Extension* extension =
501       extensions::ExtensionRegistry::Get(browser_context)
502           ->enabled_extensions()
503           .GetByID(mutable_url.host());
504   DCHECK(extension);
505 
506   return extension;
507 }
508 
509 // static
GetNumberOfExtensionsOverridingURL(const GURL & url,content::BrowserContext * browser_context)510 size_t ExtensionWebUI::GetNumberOfExtensionsOverridingURL(
511     const GURL& url,
512     content::BrowserContext* browser_context) {
513   if (!url.SchemeIs(content::kChromeUIScheme))
514     return 0;
515 
516   return GetOverridesForChromeURL(url, browser_context, /*get_all=*/true)
517       .size();
518 }
519 
520 // static
InitializeChromeURLOverrides(Profile * profile)521 void ExtensionWebUI::InitializeChromeURLOverrides(Profile* profile) {
522   ForEachOverrideList(profile, base::Bind(&InitializeOverridesList));
523 }
524 
525 // static
ValidateChromeURLOverrides(Profile * profile)526 void ExtensionWebUI::ValidateChromeURLOverrides(Profile* profile) {
527   std::unique_ptr<extensions::ExtensionSet> all_extensions =
528       extensions::ExtensionRegistry::Get(profile)
529           ->GenerateInstalledExtensionsSet();
530 
531   ForEachOverrideList(profile,
532                       base::Bind(&ValidateOverridesList, all_extensions.get()));
533 }
534 
535 // static
RegisterOrActivateChromeURLOverrides(Profile * profile,const URLOverrides::URLOverrideMap & overrides)536 void ExtensionWebUI::RegisterOrActivateChromeURLOverrides(
537     Profile* profile,
538     const URLOverrides::URLOverrideMap& overrides) {
539   if (overrides.empty())
540     return;
541   PrefService* prefs = profile->GetPrefs();
542   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
543   base::DictionaryValue* all_overrides = update.Get();
544   for (const auto& page_override_pair : overrides) {
545     base::ListValue* page_overrides_weak = nullptr;
546     if (!all_overrides->GetList(page_override_pair.first,
547                                 &page_overrides_weak)) {
548       auto page_overrides = std::make_unique<base::ListValue>();
549       page_overrides_weak = page_overrides.get();
550       all_overrides->Set(page_override_pair.first, std::move(page_overrides));
551     }
552     AddOverridesToList(page_overrides_weak, page_override_pair.second);
553   }
554 }
555 
556 // static
DeactivateChromeURLOverrides(Profile * profile,const URLOverrides::URLOverrideMap & overrides)557 void ExtensionWebUI::DeactivateChromeURLOverrides(
558     Profile* profile,
559     const URLOverrides::URLOverrideMap& overrides) {
560   UpdateOverridesLists(profile, overrides, UPDATE_DEACTIVATE);
561 }
562 
563 // static
UnregisterChromeURLOverrides(Profile * profile,const URLOverrides::URLOverrideMap & overrides)564 void ExtensionWebUI::UnregisterChromeURLOverrides(
565     Profile* profile,
566     const URLOverrides::URLOverrideMap& overrides) {
567   UpdateOverridesLists(profile, overrides, UPDATE_REMOVE);
568 }
569 
570 // static
GetFaviconForURL(Profile * profile,const GURL & page_url,favicon_base::FaviconResultsCallback callback)571 void ExtensionWebUI::GetFaviconForURL(
572     Profile* profile,
573     const GURL& page_url,
574     favicon_base::FaviconResultsCallback callback) {
575   const Extension* extension = extensions::ExtensionRegistry::Get(
576       profile)->enabled_extensions().GetByID(page_url.host());
577   if (!extension) {
578     RunFaviconCallbackAsync(std::move(callback), gfx::Image());
579     return;
580   }
581 
582   // Fetch resources for all supported scale factors for which there are
583   // resources. Load image reps for all supported scale factors (in addition to
584   // 1x) immediately instead of in an as needed fashion to be consistent with
585   // how favicons are requested for chrome:// and page URLs.
586   const std::vector<float>& favicon_scales = favicon_base::GetFaviconScales();
587   std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
588   for (size_t i = 0; i < favicon_scales.size(); ++i) {
589     float scale = favicon_scales[i];
590     int pixel_size = static_cast<int>(gfx::kFaviconSize * scale);
591     extensions::ExtensionResource icon_resource =
592         extensions::IconsInfo::GetIconResource(extension,
593                                                pixel_size,
594                                                ExtensionIconSet::MATCH_BIGGER);
595 
596     ui::ScaleFactor resource_scale_factor = ui::GetSupportedScaleFactor(scale);
597     if (!icon_resource.empty()) {
598       info_list.push_back(extensions::ImageLoader::ImageRepresentation(
599           icon_resource,
600           extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
601           gfx::Size(pixel_size, pixel_size), resource_scale_factor));
602     }
603   }
604 
605   if (info_list.empty()) {
606     // Use the placeholder image when no default icon is available.
607     gfx::Image placeholder_image =
608         extensions::ExtensionIconPlaceholder::CreateImage(
609             extension_misc::EXTENSION_ICON_SMALL, extension->name());
610     gfx::ImageSkia placeholder_skia(placeholder_image.AsImageSkia());
611     // Ensure the ImageSkia has representation at all scales we would use for
612     // favicons.
613     std::vector<ui::ScaleFactor> scale_factors = ui::GetSupportedScaleFactors();
614     for (const auto& scale_factor : scale_factors) {
615       placeholder_skia.GetRepresentation(
616           ui::GetScaleForScaleFactor(scale_factor));
617     }
618     RunFaviconCallbackAsync(std::move(callback), gfx::Image(placeholder_skia));
619   } else {
620     // LoadImagesAsync actually can run callback synchronously. We want to force
621     // async.
622     extensions::ImageLoader::Get(profile)->LoadImagesAsync(
623         extension, info_list,
624         base::BindOnce(&RunFaviconCallbackAsync, std::move(callback)));
625   }
626 }
627