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