Allow only one bookmark to be added for multiple fast starring
[chromium-blink-merge.git] / chrome / browser / extensions / api / developer_private / extension_info_generator.cc
blobb88bdbc55718acbd118c27c11439845a1677084f
1 // Copyright 2015 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.
5 #include "chrome/browser/extensions/api/developer_private/extension_info_generator.h"
7 #include "base/base64.h"
8 #include "base/location.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "chrome/browser/extensions/api/commands/command_service.h"
13 #include "chrome/browser/extensions/api/developer_private/inspectable_views_finder.h"
14 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
15 #include "chrome/browser/extensions/error_console/error_console.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_ui_util.h"
18 #include "chrome/browser/extensions/extension_util.h"
19 #include "chrome/browser/extensions/path_util.h"
20 #include "chrome/browser/extensions/shared_module_service.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
23 #include "chrome/common/extensions/command.h"
24 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
25 #include "chrome/grit/generated_resources.h"
26 #include "content/public/browser/render_frame_host.h"
27 #include "extensions/browser/extension_error.h"
28 #include "extensions/browser/extension_prefs.h"
29 #include "extensions/browser/extension_registry.h"
30 #include "extensions/browser/extension_system.h"
31 #include "extensions/browser/image_loader.h"
32 #include "extensions/browser/warning_service.h"
33 #include "extensions/common/extension_set.h"
34 #include "extensions/common/feature_switch.h"
35 #include "extensions/common/install_warning.h"
36 #include "extensions/common/manifest.h"
37 #include "extensions/common/manifest_handlers/background_info.h"
38 #include "extensions/common/manifest_handlers/icons_handler.h"
39 #include "extensions/common/manifest_handlers/offline_enabled_info.h"
40 #include "extensions/common/manifest_handlers/options_page_info.h"
41 #include "extensions/common/manifest_url_handlers.h"
42 #include "extensions/common/permissions/permissions_data.h"
43 #include "extensions/grit/extensions_browser_resources.h"
44 #include "ui/base/l10n/l10n_util.h"
45 #include "ui/base/resource/resource_bundle.h"
46 #include "ui/gfx/codec/png_codec.h"
47 #include "ui/gfx/color_utils.h"
48 #include "ui/gfx/image/image.h"
49 #include "ui/gfx/skbitmap_operations.h"
51 namespace extensions {
53 namespace developer = api::developer_private;
55 namespace {
57 // Given a Manifest::Type, converts it into its developer_private
58 // counterpart.
59 developer::ExtensionType GetExtensionType(Manifest::Type manifest_type) {
60 developer::ExtensionType type = developer::EXTENSION_TYPE_EXTENSION;
61 switch (manifest_type) {
62 case Manifest::TYPE_EXTENSION:
63 type = developer::EXTENSION_TYPE_EXTENSION;
64 break;
65 case Manifest::TYPE_THEME:
66 type = developer::EXTENSION_TYPE_THEME;
67 break;
68 case Manifest::TYPE_HOSTED_APP:
69 type = developer::EXTENSION_TYPE_HOSTED_APP;
70 break;
71 case Manifest::TYPE_LEGACY_PACKAGED_APP:
72 type = developer::EXTENSION_TYPE_LEGACY_PACKAGED_APP;
73 break;
74 case Manifest::TYPE_PLATFORM_APP:
75 type = developer::EXTENSION_TYPE_PLATFORM_APP;
76 break;
77 case Manifest::TYPE_SHARED_MODULE:
78 type = developer::EXTENSION_TYPE_SHARED_MODULE;
79 break;
80 default:
81 NOTREACHED();
83 return type;
86 // Populates the common fields of an extension error.
87 template <typename ErrorType>
88 void PopulateErrorBase(const ExtensionError& error, ErrorType* out) {
89 CHECK(out);
90 out->type = error.type() == ExtensionError::MANIFEST_ERROR ?
91 developer::ERROR_TYPE_MANIFEST : developer::ERROR_TYPE_RUNTIME;
92 out->extension_id = error.extension_id();
93 out->from_incognito = error.from_incognito();
94 out->source = base::UTF16ToUTF8(error.source());
95 out->message = base::UTF16ToUTF8(error.message());
96 out->id = error.id();
99 // Given a ManifestError object, converts it into its developer_private
100 // counterpart.
101 linked_ptr<developer::ManifestError> ConstructManifestError(
102 const ManifestError& error) {
103 linked_ptr<developer::ManifestError> result(new developer::ManifestError());
104 PopulateErrorBase(error, result.get());
105 result->manifest_key = base::UTF16ToUTF8(error.manifest_key());
106 if (!error.manifest_specific().empty()) {
107 result->manifest_specific.reset(
108 new std::string(base::UTF16ToUTF8(error.manifest_specific())));
110 return result;
113 // Given a RuntimeError object, converts it into its developer_private
114 // counterpart.
115 linked_ptr<developer::RuntimeError> ConstructRuntimeError(
116 const RuntimeError& error) {
117 linked_ptr<developer::RuntimeError> result(new developer::RuntimeError());
118 PopulateErrorBase(error, result.get());
119 switch (error.level()) {
120 case logging::LOG_VERBOSE:
121 case logging::LOG_INFO:
122 result->severity = developer::ERROR_LEVEL_LOG;
123 break;
124 case logging::LOG_WARNING:
125 result->severity = developer::ERROR_LEVEL_WARN;
126 break;
127 case logging::LOG_FATAL:
128 case logging::LOG_ERROR:
129 result->severity = developer::ERROR_LEVEL_ERROR;
130 break;
131 default:
132 NOTREACHED();
134 result->occurrences = error.occurrences();
135 // NOTE(devlin): This is called "render_view_id" in the api for legacy
136 // reasons, but it's not a high priority to change.
137 result->render_view_id = error.render_frame_id();
138 result->render_process_id = error.render_process_id();
139 result->can_inspect =
140 content::RenderFrameHost::FromID(error.render_process_id(),
141 error.render_frame_id()) != nullptr;
142 for (const StackFrame& f : error.stack_trace()) {
143 linked_ptr<developer::StackFrame> frame(new developer::StackFrame());
144 frame->line_number = f.line_number;
145 frame->column_number = f.column_number;
146 frame->url = base::UTF16ToUTF8(f.source);
147 frame->function_name = base::UTF16ToUTF8(f.function);
148 result->stack_trace.push_back(frame);
150 return result;
153 // Constructs any commands for the extension with the given |id|, and adds them
154 // to the list of |commands|.
155 void ConstructCommands(CommandService* command_service,
156 const std::string& extension_id,
157 std::vector<linked_ptr<developer::Command>>* commands) {
158 auto construct_command = [](const Command& command,
159 bool active,
160 bool is_extension_action) {
161 developer::Command* command_value = new developer::Command();
162 command_value->description = is_extension_action ?
163 l10n_util::GetStringUTF8(IDS_EXTENSION_COMMANDS_GENERIC_ACTIVATE) :
164 base::UTF16ToUTF8(command.description());
165 command_value->keybinding =
166 base::UTF16ToUTF8(command.accelerator().GetShortcutText());
167 command_value->name = command.command_name();
168 command_value->is_active = active;
169 command_value->scope = command.global() ? developer::COMMAND_SCOPE_GLOBAL :
170 developer::COMMAND_SCOPE_CHROME;
171 command_value->is_extension_action = is_extension_action;
172 return command_value;
174 bool active = false;
175 Command browser_action;
176 if (command_service->GetBrowserActionCommand(extension_id,
177 CommandService::ALL,
178 &browser_action,
179 &active)) {
180 commands->push_back(
181 make_linked_ptr(construct_command(browser_action, active, true)));
184 Command page_action;
185 if (command_service->GetPageActionCommand(extension_id,
186 CommandService::ALL,
187 &page_action,
188 &active)) {
189 commands->push_back(
190 make_linked_ptr(construct_command(page_action, active, true)));
193 CommandMap named_commands;
194 if (command_service->GetNamedCommands(extension_id,
195 CommandService::ALL,
196 CommandService::ANY_SCOPE,
197 &named_commands)) {
198 for (auto& pair : named_commands) {
199 Command& command_to_use = pair.second;
200 // TODO(devlin): For some reason beyond my knowledge, FindCommandByName
201 // returns different data than GetNamedCommands, including the
202 // accelerators, but not the descriptions - and even then, only if the
203 // command is active.
204 // Unfortunately, some systems may be relying on the other data (which
205 // more closely matches manifest data).
206 // Until we can sort all this out, we merge the two command structures.
207 Command active_command = command_service->FindCommandByName(
208 extension_id, command_to_use.command_name());
209 command_to_use.set_accelerator(active_command.accelerator());
210 command_to_use.set_global(active_command.global());
211 bool active = command_to_use.accelerator().key_code() != ui::VKEY_UNKNOWN;
212 commands->push_back(
213 make_linked_ptr(construct_command(command_to_use, active, false)));
218 } // namespace
220 ExtensionInfoGenerator::ExtensionInfoGenerator(
221 content::BrowserContext* browser_context)
222 : browser_context_(browser_context),
223 command_service_(CommandService::Get(browser_context)),
224 extension_system_(ExtensionSystem::Get(browser_context)),
225 extension_prefs_(ExtensionPrefs::Get(browser_context)),
226 extension_action_api_(ExtensionActionAPI::Get(browser_context)),
227 warning_service_(WarningService::Get(browser_context)),
228 error_console_(ErrorConsole::Get(browser_context)),
229 image_loader_(ImageLoader::Get(browser_context)),
230 pending_image_loads_(0u),
231 weak_factory_(this) {
234 ExtensionInfoGenerator::~ExtensionInfoGenerator() {
237 void ExtensionInfoGenerator::CreateExtensionInfo(
238 const std::string& id,
239 const ExtensionInfosCallback& callback) {
240 DCHECK(callback_.is_null() && list_.empty()) <<
241 "Only a single generation can be running at a time!";
242 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
244 developer::ExtensionState state = developer::EXTENSION_STATE_NONE;
245 const Extension* ext = nullptr;
246 if ((ext = registry->enabled_extensions().GetByID(id)) != nullptr)
247 state = developer::EXTENSION_STATE_ENABLED;
248 else if ((ext = registry->disabled_extensions().GetByID(id)) != nullptr)
249 state = developer::EXTENSION_STATE_DISABLED;
250 else if ((ext = registry->terminated_extensions().GetByID(id)) != nullptr)
251 state = developer::EXTENSION_STATE_TERMINATED;
253 if (ext && ui_util::ShouldDisplayInExtensionSettings(ext, browser_context_))
254 CreateExtensionInfoHelper(*ext, state);
256 if (pending_image_loads_ == 0) {
257 // Don't call the callback re-entrantly.
258 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
259 base::Bind(callback, list_));
260 list_.clear();
261 } else {
262 callback_ = callback;
266 void ExtensionInfoGenerator::CreateExtensionsInfo(
267 bool include_disabled,
268 bool include_terminated,
269 const ExtensionInfosCallback& callback) {
270 auto add_to_list = [this](const ExtensionSet& extensions,
271 developer::ExtensionState state) {
272 for (const scoped_refptr<const Extension>& extension : extensions) {
273 if (ui_util::ShouldDisplayInExtensionSettings(extension.get(),
274 browser_context_)) {
275 CreateExtensionInfoHelper(*extension, state);
280 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
281 add_to_list(registry->enabled_extensions(),
282 developer::EXTENSION_STATE_ENABLED);
283 if (include_disabled) {
284 add_to_list(registry->disabled_extensions(),
285 developer::EXTENSION_STATE_DISABLED);
287 if (include_terminated) {
288 add_to_list(registry->terminated_extensions(),
289 developer::EXTENSION_STATE_TERMINATED);
292 if (pending_image_loads_ == 0) {
293 // Don't call the callback re-entrantly.
294 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
295 base::Bind(callback, list_));
296 list_.clear();
297 } else {
298 callback_ = callback;
302 void ExtensionInfoGenerator::CreateExtensionInfoHelper(
303 const Extension& extension,
304 developer::ExtensionState state) {
305 scoped_ptr<developer::ExtensionInfo> info(new developer::ExtensionInfo());
307 // Don't consider the button hidden with the redesign, because "hidden"
308 // buttons are now just hidden in the wrench menu.
309 info->action_button_hidden =
310 !extension_action_api_->GetBrowserActionVisibility(extension.id()) &&
311 !FeatureSwitch::extension_action_redesign()->IsEnabled();
313 // Blacklist text.
314 int blacklist_text = -1;
315 switch (extension_prefs_->GetExtensionBlacklistState(extension.id())) {
316 case BLACKLISTED_SECURITY_VULNERABILITY:
317 blacklist_text = IDS_OPTIONS_BLACKLISTED_SECURITY_VULNERABILITY;
318 break;
319 case BLACKLISTED_CWS_POLICY_VIOLATION:
320 blacklist_text = IDS_OPTIONS_BLACKLISTED_CWS_POLICY_VIOLATION;
321 break;
322 case BLACKLISTED_POTENTIALLY_UNWANTED:
323 blacklist_text = IDS_OPTIONS_BLACKLISTED_POTENTIALLY_UNWANTED;
324 break;
325 default:
326 break;
328 if (blacklist_text != -1) {
329 info->blacklist_text.reset(
330 new std::string(l10n_util::GetStringUTF8(blacklist_text)));
333 Profile* profile = Profile::FromBrowserContext(browser_context_);
335 // ControlledInfo.
336 bool is_policy_location = Manifest::IsPolicyLocation(extension.location());
337 if (is_policy_location || util::IsExtensionSupervised(&extension, profile)) {
338 info->controlled_info.reset(new developer::ControlledInfo());
339 if (is_policy_location) {
340 info->controlled_info->type = developer::CONTROLLER_TYPE_POLICY;
341 info->controlled_info->text =
342 l10n_util::GetStringUTF8(IDS_OPTIONS_INSTALL_LOCATION_ENTERPRISE);
343 } else if (profile->IsChild()) {
344 info->controlled_info->type = developer::CONTROLLER_TYPE_CHILD_CUSTODIAN;
345 info->controlled_info->text = l10n_util::GetStringUTF8(
346 IDS_EXTENSIONS_INSTALLED_BY_CHILD_CUSTODIAN);
347 } else {
348 info->controlled_info->type =
349 developer::CONTROLLER_TYPE_SUPERVISED_USER_CUSTODIAN;
350 info->controlled_info->text = l10n_util::GetStringUTF8(
351 IDS_EXTENSIONS_INSTALLED_BY_SUPERVISED_USER_CUSTODIAN);
355 bool is_enabled = state == developer::EXTENSION_STATE_ENABLED;
357 // Commands.
358 if (is_enabled)
359 ConstructCommands(command_service_, extension.id(), &info->commands);
361 // Dependent extensions.
362 if (extension.is_shared_module()) {
363 scoped_ptr<ExtensionSet> dependent_extensions =
364 extension_system_->extension_service()->
365 shared_module_service()->GetDependentExtensions(&extension);
366 for (const scoped_refptr<const Extension>& dependent :
367 *dependent_extensions)
368 info->dependent_extensions.push_back(dependent->id());
371 info->description = extension.description();
373 // Disable reasons.
374 int disable_reasons = extension_prefs_->GetDisableReasons(extension.id());
375 info->disable_reasons.suspicious_install =
376 (disable_reasons & Extension::DISABLE_NOT_VERIFIED) != 0;
377 info->disable_reasons.corrupt_install =
378 (disable_reasons & Extension::DISABLE_CORRUPTED) != 0;
379 info->disable_reasons.update_required =
380 (disable_reasons & Extension::DISABLE_UPDATE_REQUIRED_BY_POLICY) != 0;
382 // Error collection.
383 bool error_console_enabled =
384 error_console_->IsEnabledForChromeExtensionsPage();
385 info->error_collection.is_enabled = error_console_enabled;
386 info->error_collection.is_active =
387 error_console_enabled &&
388 error_console_->IsReportingEnabledForExtension(extension.id());
390 // File access.
391 info->file_access.is_enabled = extension.wants_file_access();
392 info->file_access.is_active =
393 util::AllowFileAccess(extension.id(), browser_context_);
395 // Home page.
396 info->home_page.url = ManifestURL::GetHomepageURL(&extension).spec();
397 info->home_page.specified = ManifestURL::SpecifiedHomepageURL(&extension);
399 info->id = extension.id();
401 // Incognito access.
402 info->incognito_access.is_enabled = extension.can_be_incognito_enabled();
403 info->incognito_access.is_active =
404 util::IsIncognitoEnabled(extension.id(), browser_context_);
406 // Install warnings (only if unpacked and no error console).
407 if (!error_console_enabled &&
408 Manifest::IsUnpackedLocation(extension.location())) {
409 const std::vector<InstallWarning>& install_warnings =
410 extension.install_warnings();
411 for (const InstallWarning& warning : install_warnings)
412 info->install_warnings.push_back(warning.message);
415 // Launch url.
416 if (extension.is_app()) {
417 info->launch_url.reset(
418 new std::string(AppLaunchInfo::GetFullLaunchURL(&extension).spec()));
421 // Location.
422 if (extension.location() == Manifest::INTERNAL &&
423 ManifestURL::UpdatesFromGallery(&extension)) {
424 info->location = developer::LOCATION_FROM_STORE;
425 } else if (Manifest::IsUnpackedLocation(extension.location())) {
426 info->location = developer::LOCATION_UNPACKED;
427 } else if (Manifest::IsExternalLocation(extension.location()) &&
428 ManifestURL::UpdatesFromGallery(&extension)) {
429 info->location = developer::LOCATION_THIRD_PARTY;
430 } else {
431 info->location = developer::LOCATION_UNKNOWN;
434 // Location text.
435 int location_text = -1;
436 if (info->location == developer::LOCATION_UNKNOWN)
437 location_text = IDS_OPTIONS_INSTALL_LOCATION_UNKNOWN;
438 else if (extension.location() == Manifest::EXTERNAL_REGISTRY)
439 location_text = IDS_OPTIONS_INSTALL_LOCATION_3RD_PARTY;
440 else if (extension.is_shared_module())
441 location_text = IDS_OPTIONS_INSTALL_LOCATION_SHARED_MODULE;
442 if (location_text != -1) {
443 info->location_text.reset(
444 new std::string(l10n_util::GetStringUTF8(location_text)));
447 // Runtime/Manifest errors.
448 if (error_console_enabled) {
449 const ErrorList& errors =
450 error_console_->GetErrorsForExtension(extension.id());
451 for (const ExtensionError* error : errors) {
452 switch (error->type()) {
453 case ExtensionError::MANIFEST_ERROR:
454 info->manifest_errors.push_back(ConstructManifestError(
455 static_cast<const ManifestError&>(*error)));
456 break;
457 case ExtensionError::RUNTIME_ERROR:
458 info->runtime_errors.push_back(ConstructRuntimeError(
459 static_cast<const RuntimeError&>(*error)));
460 break;
461 case ExtensionError::INTERNAL_ERROR:
462 // TODO(wittman): Support InternalError in developer tools:
463 // https://crbug.com/503427.
464 break;
465 case ExtensionError::NUM_ERROR_TYPES:
466 NOTREACHED();
467 break;
472 ManagementPolicy* management_policy = extension_system_->management_policy();
473 info->must_remain_installed =
474 management_policy->MustRemainInstalled(&extension, nullptr);
476 info->name = extension.name();
477 info->offline_enabled = OfflineEnabledInfo::IsOfflineEnabled(&extension);
479 // Options page.
480 if (OptionsPageInfo::HasOptionsPage(&extension)) {
481 info->options_page.reset(new developer::OptionsPage());
482 info->options_page->open_in_tab =
483 OptionsPageInfo::ShouldOpenInTab(&extension);
484 info->options_page->url =
485 OptionsPageInfo::GetOptionsPage(&extension).spec();
488 // Path.
489 if (Manifest::IsUnpackedLocation(extension.location())) {
490 info->path.reset(new std::string(extension.path().AsUTF8Unsafe()));
491 info->prettified_path.reset(new std::string(
492 extensions::path_util::PrettifyPath(extension.path()).AsUTF8Unsafe()));
495 // Runs on all urls.
496 info->run_on_all_urls.is_enabled =
497 (FeatureSwitch::scripts_require_action()->IsEnabled() &&
498 PermissionsData::ScriptsMayRequireActionForExtension(
499 &extension,
500 extension.permissions_data()->active_permissions().get())) ||
501 extension.permissions_data()->HasWithheldImpliedAllHosts() ||
502 util::HasSetAllowedScriptingOnAllUrls(extension.id(), browser_context_);
503 info->run_on_all_urls.is_active =
504 util::AllowedScriptingOnAllUrls(extension.id(), browser_context_);
506 // Runtime warnings.
507 std::vector<std::string> warnings =
508 warning_service_->GetWarningMessagesForExtension(extension.id());
509 for (const std::string& warning : warnings)
510 info->runtime_warnings.push_back(warning);
512 info->state = state;
514 info->type = GetExtensionType(extension.manifest()->type());
516 info->update_url = ManifestURL::GetUpdateURL(&extension).spec();
518 info->user_may_modify =
519 management_policy->UserMayModifySettings(&extension, nullptr);
521 info->version = extension.GetVersionForDisplay();
523 if (state != developer::EXTENSION_STATE_TERMINATED) {
524 info->views = InspectableViewsFinder(profile).
525 GetViewsForExtension(extension, is_enabled);
528 // The icon.
529 ExtensionResource icon =
530 IconsInfo::GetIconResource(&extension,
531 extension_misc::EXTENSION_ICON_MEDIUM,
532 ExtensionIconSet::MATCH_BIGGER);
533 if (icon.empty()) {
534 info->icon_url = GetDefaultIconUrl(extension.is_app(), !is_enabled);
535 list_.push_back(make_linked_ptr(info.release()));
536 } else {
537 ++pending_image_loads_;
538 // Max size of 128x128 is a random guess at a nice balance between being
539 // overly eager to resize and sending across gigantic data urls. (The icon
540 // used by the url is 48x48).
541 gfx::Size max_size(128, 128);
542 image_loader_->LoadImageAsync(
543 &extension,
544 icon,
545 max_size,
546 base::Bind(&ExtensionInfoGenerator::OnImageLoaded,
547 weak_factory_.GetWeakPtr(),
548 base::Passed(info.Pass())));
552 const std::string& ExtensionInfoGenerator::GetDefaultIconUrl(
553 bool is_app,
554 bool is_greyscale) {
555 std::string* str;
556 if (is_app) {
557 str = is_greyscale ? &default_disabled_app_icon_url_ :
558 &default_app_icon_url_;
559 } else {
560 str = is_greyscale ? &default_disabled_extension_icon_url_ :
561 &default_extension_icon_url_;
564 if (str->empty()) {
565 *str = GetIconUrlFromImage(
566 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
567 is_app ? IDR_APP_DEFAULT_ICON : IDR_EXTENSION_DEFAULT_ICON),
568 is_greyscale);
571 return *str;
574 std::string ExtensionInfoGenerator::GetIconUrlFromImage(
575 const gfx::Image& image,
576 bool should_greyscale) {
577 scoped_refptr<base::RefCountedMemory> data;
578 if (should_greyscale) {
579 color_utils::HSL shift = {-1, 0, 0.6};
580 const SkBitmap* bitmap = image.ToSkBitmap();
581 DCHECK(bitmap);
582 SkBitmap grey = SkBitmapOperations::CreateHSLShiftedBitmap(*bitmap, shift);
583 scoped_refptr<base::RefCountedBytes> image_bytes(
584 new base::RefCountedBytes());
585 gfx::PNGCodec::EncodeBGRASkBitmap(grey, false, &image_bytes->data());
586 data = image_bytes;
587 } else {
588 data = image.As1xPNGBytes();
591 std::string base_64;
592 base::Base64Encode(std::string(data->front_as<char>(), data->size()),
593 &base_64);
594 const char kDataUrlPrefix[] = "data:image/png;base64,";
595 return GURL(kDataUrlPrefix + base_64).spec();
598 void ExtensionInfoGenerator::OnImageLoaded(
599 scoped_ptr<developer::ExtensionInfo> info,
600 const gfx::Image& icon) {
601 if (!icon.IsEmpty()) {
602 info->icon_url = GetIconUrlFromImage(
603 icon, info->state != developer::EXTENSION_STATE_ENABLED);
604 } else {
605 bool is_app =
606 info->type == developer::EXTENSION_TYPE_HOSTED_APP ||
607 info->type == developer::EXTENSION_TYPE_LEGACY_PACKAGED_APP ||
608 info->type == developer::EXTENSION_TYPE_PLATFORM_APP;
609 info->icon_url = GetDefaultIconUrl(
610 is_app, info->state != developer::EXTENSION_STATE_ENABLED);
613 list_.push_back(make_linked_ptr(info.release()));
615 --pending_image_loads_;
617 if (pending_image_loads_ == 0) { // All done!
618 // We assign to a temporary callback and list and reset the stored values so
619 // that at the end of the method, any stored refs are destroyed.
620 ExtensionInfoList list;
621 list.swap(list_);
622 ExtensionInfosCallback callback = callback_;
623 callback_.Reset();
624 callback.Run(list); // WARNING: |this| is possibly deleted after this line!
628 } // namespace extensions