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
;
57 // Given a Manifest::Type, converts it into its developer_private
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
;
65 case Manifest::TYPE_THEME
:
66 type
= developer::EXTENSION_TYPE_THEME
;
68 case Manifest::TYPE_HOSTED_APP
:
69 type
= developer::EXTENSION_TYPE_HOSTED_APP
;
71 case Manifest::TYPE_LEGACY_PACKAGED_APP
:
72 type
= developer::EXTENSION_TYPE_LEGACY_PACKAGED_APP
;
74 case Manifest::TYPE_PLATFORM_APP
:
75 type
= developer::EXTENSION_TYPE_PLATFORM_APP
;
77 case Manifest::TYPE_SHARED_MODULE
:
78 type
= developer::EXTENSION_TYPE_SHARED_MODULE
;
86 // Populates the common fields of an extension error.
87 template <typename ErrorType
>
88 void PopulateErrorBase(const ExtensionError
& error
, ErrorType
* 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());
99 // Given a ManifestError object, converts it into its developer_private
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())));
113 // Given a RuntimeError object, converts it into its developer_private
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
;
124 case logging::LOG_WARNING
:
125 result
->severity
= developer::ERROR_LEVEL_WARN
;
127 case logging::LOG_FATAL
:
128 case logging::LOG_ERROR
:
129 result
->severity
= developer::ERROR_LEVEL_ERROR
;
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
);
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
,
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
;
175 Command browser_action
;
176 if (command_service
->GetBrowserActionCommand(extension_id
,
181 make_linked_ptr(construct_command(browser_action
, active
, true)));
185 if (command_service
->GetPageActionCommand(extension_id
,
190 make_linked_ptr(construct_command(page_action
, active
, true)));
193 CommandMap named_commands
;
194 if (command_service
->GetNamedCommands(extension_id
,
196 CommandService::ANY_SCOPE
,
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
;
213 make_linked_ptr(construct_command(command_to_use
, active
, false)));
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_
));
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(),
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_
));
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();
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
;
319 case BLACKLISTED_CWS_POLICY_VIOLATION
:
320 blacklist_text
= IDS_OPTIONS_BLACKLISTED_CWS_POLICY_VIOLATION
;
322 case BLACKLISTED_POTENTIALLY_UNWANTED
:
323 blacklist_text
= IDS_OPTIONS_BLACKLISTED_POTENTIALLY_UNWANTED
;
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_
);
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
);
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
;
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();
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;
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());
391 info
->file_access
.is_enabled
= extension
.wants_file_access();
392 info
->file_access
.is_active
=
393 util::AllowFileAccess(extension
.id(), browser_context_
);
396 info
->home_page
.url
= ManifestURL::GetHomepageURL(&extension
).spec();
397 info
->home_page
.specified
= ManifestURL::SpecifiedHomepageURL(&extension
);
399 info
->id
= extension
.id();
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
);
416 if (extension
.is_app()) {
417 info
->launch_url
.reset(
418 new std::string(AppLaunchInfo::GetFullLaunchURL(&extension
).spec()));
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
;
431 info
->location
= developer::LOCATION_UNKNOWN
;
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
)));
457 case ExtensionError::RUNTIME_ERROR
:
458 info
->runtime_errors
.push_back(ConstructRuntimeError(
459 static_cast<const RuntimeError
&>(*error
)));
461 case ExtensionError::INTERNAL_ERROR
:
462 // TODO(wittman): Support InternalError in developer tools:
463 // https://crbug.com/503427.
465 case ExtensionError::NUM_ERROR_TYPES
:
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
);
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();
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()));
496 info
->run_on_all_urls
.is_enabled
=
497 (FeatureSwitch::scripts_require_action()->IsEnabled() &&
498 PermissionsData::ScriptsMayRequireActionForExtension(
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_
);
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
);
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
);
529 ExtensionResource icon
=
530 IconsInfo::GetIconResource(&extension
,
531 extension_misc::EXTENSION_ICON_MEDIUM
,
532 ExtensionIconSet::MATCH_BIGGER
);
534 info
->icon_url
= GetDefaultIconUrl(extension
.is_app(), !is_enabled
);
535 list_
.push_back(make_linked_ptr(info
.release()));
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(
546 base::Bind(&ExtensionInfoGenerator::OnImageLoaded
,
547 weak_factory_
.GetWeakPtr(),
548 base::Passed(info
.Pass())));
552 const std::string
& ExtensionInfoGenerator::GetDefaultIconUrl(
557 str
= is_greyscale
? &default_disabled_app_icon_url_
:
558 &default_app_icon_url_
;
560 str
= is_greyscale
? &default_disabled_extension_icon_url_
:
561 &default_extension_icon_url_
;
565 *str
= GetIconUrlFromImage(
566 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
567 is_app
? IDR_APP_DEFAULT_ICON
: IDR_EXTENSION_DEFAULT_ICON
),
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();
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());
588 data
= image
.As1xPNGBytes();
592 base::Base64Encode(std::string(data
->front_as
<char>(), data
->size()),
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
);
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
;
622 ExtensionInfosCallback callback
= callback_
;
624 callback
.Run(list
); // WARNING: |this| is possibly deleted after this line!
628 } // namespace extensions