| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/developer_private/extension_info_generator.h" |
| |
| #include <iterator> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/extensions/api/commands/command_service.h" |
| #include "chrome/browser/extensions/api/developer_private/inspectable_views_finder.h" |
| #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" |
| #include "chrome/browser/extensions/error_console/error_console.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_ui_util.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/extensions/scripting_permissions_modifier.h" |
| #include "chrome/browser/extensions/shared_module_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/webui/extensions/extension_icon_source.h" |
| #include "chrome/common/extensions/command.h" |
| #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "extensions/browser/extension_error.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/extension_util.h" |
| #include "extensions/browser/image_loader.h" |
| #include "extensions/browser/path_util.h" |
| #include "extensions/browser/warning_service.h" |
| #include "extensions/common/extension_set.h" |
| #include "extensions/common/install_warning.h" |
| #include "extensions/common/manifest.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/common/manifest_handlers/icons_handler.h" |
| #include "extensions/common/manifest_handlers/offline_enabled_info.h" |
| #include "extensions/common/manifest_handlers/options_page_info.h" |
| #include "extensions/common/manifest_url_handlers.h" |
| #include "extensions/common/permissions/permission_message_provider.h" |
| #include "extensions/common/permissions/permission_message_util.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "extensions/grit/extensions_browser_resources.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/color_utils.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/skbitmap_operations.h" |
| |
| namespace extensions { |
| |
| namespace developer = api::developer_private; |
| |
| namespace { |
| |
| // Given a Manifest::Type, converts it into its developer_private |
| // counterpart. |
| developer::ExtensionType GetExtensionType(Manifest::Type manifest_type) { |
| developer::ExtensionType type = developer::EXTENSION_TYPE_EXTENSION; |
| switch (manifest_type) { |
| case Manifest::TYPE_EXTENSION: |
| type = developer::EXTENSION_TYPE_EXTENSION; |
| break; |
| case Manifest::TYPE_THEME: |
| type = developer::EXTENSION_TYPE_THEME; |
| break; |
| case Manifest::TYPE_HOSTED_APP: |
| type = developer::EXTENSION_TYPE_HOSTED_APP; |
| break; |
| case Manifest::TYPE_LEGACY_PACKAGED_APP: |
| type = developer::EXTENSION_TYPE_LEGACY_PACKAGED_APP; |
| break; |
| case Manifest::TYPE_PLATFORM_APP: |
| type = developer::EXTENSION_TYPE_PLATFORM_APP; |
| break; |
| case Manifest::TYPE_SHARED_MODULE: |
| type = developer::EXTENSION_TYPE_SHARED_MODULE; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return type; |
| } |
| |
| // Populates the common fields of an extension error. |
| template <typename ErrorType> |
| void PopulateErrorBase(const ExtensionError& error, ErrorType* out) { |
| CHECK(out); |
| out->type = error.type() == ExtensionError::MANIFEST_ERROR ? |
| developer::ERROR_TYPE_MANIFEST : developer::ERROR_TYPE_RUNTIME; |
| out->extension_id = error.extension_id(); |
| out->from_incognito = error.from_incognito(); |
| out->source = base::UTF16ToUTF8(error.source()); |
| out->message = base::UTF16ToUTF8(error.message()); |
| out->id = error.id(); |
| } |
| |
| // Given a ManifestError object, converts it into its developer_private |
| // counterpart. |
| developer::ManifestError ConstructManifestError(const ManifestError& error) { |
| developer::ManifestError result; |
| PopulateErrorBase(error, &result); |
| result.manifest_key = base::UTF16ToUTF8(error.manifest_key()); |
| if (!error.manifest_specific().empty()) { |
| result.manifest_specific.reset( |
| new std::string(base::UTF16ToUTF8(error.manifest_specific()))); |
| } |
| return result; |
| } |
| |
| // Given a RuntimeError object, converts it into its developer_private |
| // counterpart. |
| developer::RuntimeError ConstructRuntimeError(const RuntimeError& error) { |
| developer::RuntimeError result; |
| PopulateErrorBase(error, &result); |
| switch (error.level()) { |
| case logging::LOG_VERBOSE: |
| case logging::LOG_INFO: |
| result.severity = developer::ERROR_LEVEL_LOG; |
| break; |
| case logging::LOG_WARNING: |
| result.severity = developer::ERROR_LEVEL_WARN; |
| break; |
| case logging::LOG_FATAL: |
| case logging::LOG_ERROR: |
| result.severity = developer::ERROR_LEVEL_ERROR; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| result.context_url = error.context_url().spec(); |
| result.occurrences = error.occurrences(); |
| // NOTE(devlin): This is called "render_view_id" in the api for legacy |
| // reasons, but it's not a high priority to change. |
| result.render_view_id = error.render_frame_id(); |
| result.render_process_id = error.render_process_id(); |
| result.can_inspect = |
| content::RenderFrameHost::FromID(error.render_process_id(), |
| error.render_frame_id()) != nullptr; |
| for (const StackFrame& f : error.stack_trace()) { |
| developer::StackFrame frame; |
| frame.line_number = f.line_number; |
| frame.column_number = f.column_number; |
| frame.url = base::UTF16ToUTF8(f.source); |
| frame.function_name = base::UTF16ToUTF8(f.function); |
| result.stack_trace.push_back(std::move(frame)); |
| } |
| return result; |
| } |
| |
| // Constructs any commands for the extension with the given |id|, and adds them |
| // to the list of |commands|. |
| void ConstructCommands(CommandService* command_service, |
| const std::string& extension_id, |
| std::vector<developer::Command>* commands) { |
| auto construct_command = [](const Command& command, bool active, |
| bool is_extension_action) { |
| developer::Command command_value; |
| command_value.description = |
| is_extension_action |
| ? l10n_util::GetStringUTF8(IDS_EXTENSION_COMMANDS_GENERIC_ACTIVATE) |
| : base::UTF16ToUTF8(command.description()); |
| command_value.keybinding = |
| base::UTF16ToUTF8(command.accelerator().GetShortcutText()); |
| command_value.name = command.command_name(); |
| command_value.is_active = active; |
| command_value.scope = command.global() ? developer::COMMAND_SCOPE_GLOBAL |
| : developer::COMMAND_SCOPE_CHROME; |
| command_value.is_extension_action = is_extension_action; |
| return command_value; |
| }; |
| bool active = false; |
| Command browser_action; |
| if (command_service->GetBrowserActionCommand(extension_id, |
| CommandService::ALL, |
| &browser_action, |
| &active)) { |
| commands->push_back(construct_command(browser_action, active, true)); |
| } |
| |
| Command page_action; |
| if (command_service->GetPageActionCommand(extension_id, |
| CommandService::ALL, |
| &page_action, |
| &active)) { |
| commands->push_back(construct_command(page_action, active, true)); |
| } |
| |
| CommandMap named_commands; |
| if (command_service->GetNamedCommands(extension_id, |
| CommandService::ALL, |
| CommandService::ANY_SCOPE, |
| &named_commands)) { |
| for (auto& pair : named_commands) { |
| Command& command_to_use = pair.second; |
| // TODO(devlin): For some reason beyond my knowledge, FindCommandByName |
| // returns different data than GetNamedCommands, including the |
| // accelerators, but not the descriptions - and even then, only if the |
| // command is active. |
| // Unfortunately, some systems may be relying on the other data (which |
| // more closely matches manifest data). |
| // Until we can sort all this out, we merge the two command structures. |
| Command active_command = command_service->FindCommandByName( |
| extension_id, command_to_use.command_name()); |
| command_to_use.set_accelerator(active_command.accelerator()); |
| command_to_use.set_global(active_command.global()); |
| bool active = command_to_use.accelerator().key_code() != ui::VKEY_UNKNOWN; |
| commands->push_back(construct_command(command_to_use, active, false)); |
| } |
| } |
| } |
| |
| // Populates the |permissions| data for the given |extension|. |
| void AddPermissionsInfo(content::BrowserContext* browser_context, |
| const Extension& extension, |
| developer::Permissions* permissions) { |
| auto get_permission_messages = [](const PermissionMessages& messages) { |
| std::vector<developer::Permission> permissions; |
| permissions.reserve(messages.size()); |
| for (const PermissionMessage& message : messages) { |
| permissions.push_back(developer::Permission()); |
| developer::Permission& permission_message = permissions.back(); |
| permission_message.message = base::UTF16ToUTF8(message.message()); |
| permission_message.submessages.reserve(message.submessages().size()); |
| for (const auto& submessage : message.submessages()) |
| permission_message.submessages.push_back(base::UTF16ToUTF8(submessage)); |
| } |
| return permissions; |
| }; |
| |
| ScriptingPermissionsModifier permissions_modifier( |
| browser_context, base::WrapRefCounted(&extension)); |
| bool enable_runtime_host_permissions = |
| permissions_modifier.CanAffectExtension(); |
| |
| if (!enable_runtime_host_permissions) { |
| // Without runtime host permissions, everything goes into |
| // simple_permissions. |
| permissions->simple_permissions = get_permission_messages( |
| extension.permissions_data()->GetPermissionMessages()); |
| return; |
| } |
| |
| // With runtime host permissions, we separate out API permission messages |
| // from host permissions. |
| const PermissionSet& active_permissions = |
| extension.permissions_data()->active_permissions(); |
| PermissionSet non_host_permissions(active_permissions.apis(), |
| active_permissions.manifest_permissions(), |
| URLPatternSet(), URLPatternSet()); |
| const PermissionMessageProvider* message_provider = |
| PermissionMessageProvider::Get(); |
| // Generate the messages for just the API (and manifest) permissions. |
| PermissionMessages api_messages = message_provider->GetPermissionMessages( |
| message_provider->GetAllPermissionIDs(non_host_permissions, |
| extension.GetType())); |
| permissions->simple_permissions = get_permission_messages(api_messages); |
| |
| // Add the host access data, including the mode and any runtime-granted |
| // hosts. |
| if (!permissions_modifier.HasWithheldHostPermissions()) { |
| permissions->host_access = developer::HOST_ACCESS_ON_ALL_SITES; |
| } else { |
| ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser_context); |
| std::unique_ptr<const PermissionSet> runtime_granted_permissions = |
| extension_prefs->GetRuntimeGrantedPermissions(extension.id()); |
| if (runtime_granted_permissions->effective_hosts().is_empty()) { |
| // TODO(devlin): This isn't quite right - it's possible the user just |
| // selected "on specific sites" from the dropdown, and hasn't yet added |
| // any sites. We'll need to handle this. |
| // https://crbug.com/844128. |
| permissions->host_access = developer::HOST_ACCESS_ON_CLICK; |
| } else { |
| permissions->host_access = developer::HOST_ACCESS_ON_SPECIFIC_SITES; |
| std::set<std::string> distinct_hosts; |
| for (auto pattern : runtime_granted_permissions->effective_hosts()) { |
| // We only allow addition/removal of full hosts (since from a |
| // permissions point of view, path is irrelevant). We always make the |
| // path wildcard when adding through this UI, but the optional |
| // permissions API may allow adding permissions with paths. |
| // TODO(devlin): Investigate, and possibly change the optional |
| // permissions API. |
| pattern.SetPath("/*"); |
| distinct_hosts.insert(pattern.GetAsString()); |
| } |
| |
| permissions->runtime_host_permissions = |
| std::make_unique<std::vector<std::string>>( |
| std::make_move_iterator(distinct_hosts.begin()), |
| std::make_move_iterator(distinct_hosts.end())); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| ExtensionInfoGenerator::ExtensionInfoGenerator( |
| content::BrowserContext* browser_context) |
| : browser_context_(browser_context), |
| command_service_(CommandService::Get(browser_context)), |
| extension_system_(ExtensionSystem::Get(browser_context)), |
| extension_prefs_(ExtensionPrefs::Get(browser_context)), |
| extension_action_api_(ExtensionActionAPI::Get(browser_context)), |
| warning_service_(WarningService::Get(browser_context)), |
| error_console_(ErrorConsole::Get(browser_context)), |
| image_loader_(ImageLoader::Get(browser_context)), |
| pending_image_loads_(0u), |
| weak_factory_(this) { |
| } |
| |
| ExtensionInfoGenerator::~ExtensionInfoGenerator() { |
| } |
| |
| void ExtensionInfoGenerator::CreateExtensionInfo( |
| const std::string& id, |
| const ExtensionInfosCallback& callback) { |
| DCHECK(callback_.is_null() && list_.empty()) << |
| "Only a single generation can be running at a time!"; |
| ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); |
| |
| developer::ExtensionState state = developer::EXTENSION_STATE_NONE; |
| const Extension* ext = nullptr; |
| if ((ext = registry->enabled_extensions().GetByID(id)) != nullptr) |
| state = developer::EXTENSION_STATE_ENABLED; |
| else if ((ext = registry->disabled_extensions().GetByID(id)) != nullptr) |
| state = developer::EXTENSION_STATE_DISABLED; |
| else if ((ext = registry->terminated_extensions().GetByID(id)) != nullptr) |
| state = developer::EXTENSION_STATE_TERMINATED; |
| |
| if (ext && ui_util::ShouldDisplayInExtensionSettings(ext, browser_context_)) |
| CreateExtensionInfoHelper(*ext, state); |
| |
| if (pending_image_loads_ == 0) { |
| // Don't call the callback re-entrantly. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(callback, std::move(list_))); |
| list_.clear(); |
| } else { |
| callback_ = callback; |
| } |
| } |
| |
| void ExtensionInfoGenerator::CreateExtensionsInfo( |
| bool include_disabled, |
| bool include_terminated, |
| const ExtensionInfosCallback& callback) { |
| auto add_to_list = [this](const ExtensionSet& extensions, |
| developer::ExtensionState state) { |
| for (const scoped_refptr<const Extension>& extension : extensions) { |
| if (ui_util::ShouldDisplayInExtensionSettings(extension.get(), |
| browser_context_)) { |
| CreateExtensionInfoHelper(*extension, state); |
| } |
| } |
| }; |
| |
| ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); |
| add_to_list(registry->enabled_extensions(), |
| developer::EXTENSION_STATE_ENABLED); |
| if (include_disabled) { |
| add_to_list(registry->disabled_extensions(), |
| developer::EXTENSION_STATE_DISABLED); |
| add_to_list(registry->blacklisted_extensions(), |
| developer::EXTENSION_STATE_BLACKLISTED); |
| } |
| if (include_terminated) { |
| add_to_list(registry->terminated_extensions(), |
| developer::EXTENSION_STATE_TERMINATED); |
| } |
| |
| if (pending_image_loads_ == 0) { |
| // Don't call the callback re-entrantly. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(callback, std::move(list_))); |
| list_.clear(); |
| } else { |
| callback_ = callback; |
| } |
| } |
| |
| void ExtensionInfoGenerator::CreateExtensionInfoHelper( |
| const Extension& extension, |
| developer::ExtensionState state) { |
| std::unique_ptr<developer::ExtensionInfo> info( |
| new developer::ExtensionInfo()); |
| |
| // Blacklist text. |
| int blacklist_text = -1; |
| switch (extension_prefs_->GetExtensionBlacklistState(extension.id())) { |
| case BLACKLISTED_MALWARE: |
| blacklist_text = IDS_EXTENSIONS_BLACKLISTED_MALWARE; |
| break; |
| case BLACKLISTED_SECURITY_VULNERABILITY: |
| blacklist_text = IDS_EXTENSIONS_BLACKLISTED_SECURITY_VULNERABILITY; |
| break; |
| case BLACKLISTED_CWS_POLICY_VIOLATION: |
| blacklist_text = IDS_EXTENSIONS_BLACKLISTED_CWS_POLICY_VIOLATION; |
| break; |
| case BLACKLISTED_POTENTIALLY_UNWANTED: |
| blacklist_text = IDS_EXTENSIONS_BLACKLISTED_POTENTIALLY_UNWANTED; |
| break; |
| default: |
| break; |
| } |
| if (blacklist_text != -1) { |
| info->blacklist_text.reset( |
| new std::string(l10n_util::GetStringUTF8(blacklist_text))); |
| } |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context_); |
| |
| // ControlledInfo. |
| bool is_policy_location = Manifest::IsPolicyLocation(extension.location()); |
| if (is_policy_location || util::IsExtensionSupervised(&extension, profile)) { |
| info->controlled_info.reset(new developer::ControlledInfo()); |
| if (is_policy_location) { |
| info->controlled_info->type = developer::CONTROLLER_TYPE_POLICY; |
| info->controlled_info->text = |
| l10n_util::GetStringUTF8(IDS_EXTENSIONS_INSTALL_LOCATION_ENTERPRISE); |
| } else if (profile->IsChild()) { |
| info->controlled_info->type = developer::CONTROLLER_TYPE_CHILD_CUSTODIAN; |
| info->controlled_info->text = l10n_util::GetStringUTF8( |
| IDS_EXTENSIONS_INSTALLED_BY_CHILD_CUSTODIAN); |
| } else { |
| info->controlled_info->type = |
| developer::CONTROLLER_TYPE_SUPERVISED_USER_CUSTODIAN; |
| info->controlled_info->text = l10n_util::GetStringUTF8( |
| IDS_EXTENSIONS_INSTALLED_BY_SUPERVISED_USER_CUSTODIAN); |
| } |
| } |
| |
| bool is_enabled = state == developer::EXTENSION_STATE_ENABLED; |
| |
| // Commands. |
| if (is_enabled) |
| ConstructCommands(command_service_, extension.id(), &info->commands); |
| |
| // Dependent extensions. |
| if (extension.is_shared_module()) { |
| std::unique_ptr<ExtensionSet> dependent_extensions = |
| extension_system_->extension_service() |
| ->shared_module_service() |
| ->GetDependentExtensions(&extension); |
| for (const scoped_refptr<const Extension>& dependent : |
| *dependent_extensions) { |
| developer::DependentExtension extension; |
| extension.id = dependent->id(); |
| extension.name = dependent->name(); |
| info->dependent_extensions.push_back(std::move(extension)); |
| } |
| } |
| |
| info->description = extension.description(); |
| |
| // Disable reasons. |
| int disable_reasons = extension_prefs_->GetDisableReasons(extension.id()); |
| info->disable_reasons.suspicious_install = |
| (disable_reasons & disable_reason::DISABLE_NOT_VERIFIED) != 0; |
| info->disable_reasons.corrupt_install = |
| (disable_reasons & disable_reason::DISABLE_CORRUPTED) != 0; |
| info->disable_reasons.update_required = |
| (disable_reasons & disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY) != |
| 0; |
| |
| // Error collection. |
| bool error_console_enabled = |
| error_console_->IsEnabledForChromeExtensionsPage(); |
| info->error_collection.is_enabled = error_console_enabled; |
| info->error_collection.is_active = |
| error_console_enabled && |
| error_console_->IsReportingEnabledForExtension(extension.id()); |
| |
| // File access. |
| ManagementPolicy* management_policy = extension_system_->management_policy(); |
| info->file_access.is_enabled = |
| (extension.wants_file_access() || |
| Manifest::ShouldAlwaysAllowFileAccess(extension.location())) && |
| management_policy->UserMayModifySettings(&extension, nullptr); |
| info->file_access.is_active = |
| util::AllowFileAccess(extension.id(), browser_context_); |
| |
| // Home page. |
| info->home_page.url = ManifestURL::GetHomepageURL(&extension).spec(); |
| info->home_page.specified = ManifestURL::SpecifiedHomepageURL(&extension); |
| |
| // Developer and web store URLs. |
| // TODO(dschuyler) after MD extensions releases (expected in m64), look into |
| // removing the |home_page.url| and |home_page.specified| above. |
| info->manifest_home_page_url = |
| ManifestURL::GetManifestHomePageURL(&extension).spec(); |
| info->web_store_url = ManifestURL::GetWebStoreURL(&extension).spec(); |
| |
| info->id = extension.id(); |
| |
| // Incognito access. |
| info->incognito_access.is_enabled = util::CanBeIncognitoEnabled(&extension); |
| info->incognito_access.is_active = |
| util::IsIncognitoEnabled(extension.id(), browser_context_); |
| |
| // Install warnings, but only if unpacked, the error console isn't enabled |
| // (otherwise it shows these), and we're in developer mode (normal users don't |
| // need to see these). |
| if (!error_console_enabled && |
| Manifest::IsUnpackedLocation(extension.location()) && |
| profile->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode)) { |
| const std::vector<InstallWarning>& install_warnings = |
| extension.install_warnings(); |
| for (const InstallWarning& warning : install_warnings) |
| info->install_warnings.push_back(warning.message); |
| } |
| |
| // Launch url. |
| if (extension.is_app()) { |
| info->launch_url.reset( |
| new std::string(AppLaunchInfo::GetFullLaunchURL(&extension).spec())); |
| } |
| |
| // Location. |
| if (extension.location() == Manifest::INTERNAL && |
| ManifestURL::UpdatesFromGallery(&extension)) { |
| info->location = developer::LOCATION_FROM_STORE; |
| } else if (Manifest::IsUnpackedLocation(extension.location())) { |
| info->location = developer::LOCATION_UNPACKED; |
| } else if (Manifest::IsExternalLocation(extension.location()) && |
| ManifestURL::UpdatesFromGallery(&extension)) { |
| info->location = developer::LOCATION_THIRD_PARTY; |
| } else { |
| info->location = developer::LOCATION_UNKNOWN; |
| } |
| |
| // Location text. |
| int location_text = -1; |
| if (info->location == developer::LOCATION_UNKNOWN) |
| location_text = IDS_EXTENSIONS_INSTALL_LOCATION_UNKNOWN; |
| else if (extension.location() == Manifest::EXTERNAL_REGISTRY) |
| location_text = IDS_EXTENSIONS_INSTALL_LOCATION_3RD_PARTY; |
| else if (extension.is_shared_module()) |
| location_text = IDS_EXTENSIONS_INSTALL_LOCATION_SHARED_MODULE; |
| if (location_text != -1) { |
| info->location_text.reset( |
| new std::string(l10n_util::GetStringUTF8(location_text))); |
| } |
| |
| // Runtime/Manifest errors. |
| if (error_console_enabled) { |
| const ErrorList& errors = |
| error_console_->GetErrorsForExtension(extension.id()); |
| for (const auto& error : errors) { |
| switch (error->type()) { |
| case ExtensionError::MANIFEST_ERROR: |
| info->manifest_errors.push_back(ConstructManifestError( |
| static_cast<const ManifestError&>(*error))); |
| break; |
| case ExtensionError::RUNTIME_ERROR: |
| info->runtime_errors.push_back(ConstructRuntimeError( |
| static_cast<const RuntimeError&>(*error))); |
| break; |
| case ExtensionError::INTERNAL_ERROR: |
| // TODO(wittman): Support InternalError in developer tools: |
| // https://crbug.com/503427. |
| break; |
| case ExtensionError::NUM_ERROR_TYPES: |
| NOTREACHED(); |
| break; |
| } |
| } |
| } |
| |
| info->must_remain_installed = |
| management_policy->MustRemainInstalled(&extension, nullptr); |
| |
| info->name = extension.name(); |
| info->offline_enabled = OfflineEnabledInfo::IsOfflineEnabled(&extension); |
| |
| // Options page. |
| if (OptionsPageInfo::HasOptionsPage(&extension)) { |
| info->options_page.reset(new developer::OptionsPage()); |
| info->options_page->open_in_tab = |
| OptionsPageInfo::ShouldOpenInTab(&extension); |
| info->options_page->url = |
| OptionsPageInfo::GetOptionsPage(&extension).spec(); |
| } |
| |
| // Path. |
| if (Manifest::IsUnpackedLocation(extension.location())) { |
| info->path.reset(new std::string(extension.path().AsUTF8Unsafe())); |
| info->prettified_path.reset(new std::string( |
| extensions::path_util::PrettifyPath(extension.path()).AsUTF8Unsafe())); |
| } |
| |
| AddPermissionsInfo(browser_context_, extension, &info->permissions); |
| |
| // Runtime warnings. |
| std::vector<std::string> warnings = |
| warning_service_->GetWarningMessagesForExtension(extension.id()); |
| for (const std::string& warning : warnings) |
| info->runtime_warnings.push_back(warning); |
| |
| info->state = state; |
| |
| info->type = GetExtensionType(extension.manifest()->type()); |
| |
| info->update_url = ManifestURL::GetUpdateURL(&extension).spec(); |
| |
| info->user_may_modify = |
| management_policy->UserMayModifySettings(&extension, nullptr); |
| |
| info->version = extension.GetVersionForDisplay(); |
| |
| if (state != developer::EXTENSION_STATE_TERMINATED) { |
| info->views = InspectableViewsFinder(profile). |
| GetViewsForExtension(extension, is_enabled); |
| } |
| |
| // The icon. |
| ExtensionResource icon = |
| IconsInfo::GetIconResource(&extension, |
| extension_misc::EXTENSION_ICON_MEDIUM, |
| ExtensionIconSet::MATCH_BIGGER); |
| if (icon.empty()) { |
| info->icon_url = GetDefaultIconUrl(extension.is_app(), !is_enabled); |
| list_.push_back(std::move(*info)); |
| } else { |
| ++pending_image_loads_; |
| // Max size of 128x128 is a random guess at a nice balance between being |
| // overly eager to resize and sending across gigantic data urls. (The icon |
| // used by the url is 48x48). |
| gfx::Size max_size(128, 128); |
| image_loader_->LoadImageAsync( |
| &extension, icon, max_size, |
| base::Bind(&ExtensionInfoGenerator::OnImageLoaded, |
| weak_factory_.GetWeakPtr(), base::Passed(&info))); |
| } |
| } |
| |
| const std::string& ExtensionInfoGenerator::GetDefaultIconUrl( |
| bool is_app, |
| bool is_greyscale) { |
| std::string* str; |
| if (is_app) { |
| str = is_greyscale ? &default_disabled_app_icon_url_ : |
| &default_app_icon_url_; |
| } else { |
| str = is_greyscale ? &default_disabled_extension_icon_url_ : |
| &default_extension_icon_url_; |
| } |
| |
| if (str->empty()) { |
| *str = GetIconUrlFromImage( |
| ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| is_app ? IDR_APP_DEFAULT_ICON : IDR_EXTENSION_DEFAULT_ICON)); |
| } |
| |
| return *str; |
| } |
| |
| std::string ExtensionInfoGenerator::GetIconUrlFromImage( |
| const gfx::Image& image) { |
| scoped_refptr<base::RefCountedMemory> data; |
| data = image.As1xPNGBytes(); |
| std::string base_64; |
| base::Base64Encode(base::StringPiece(data->front_as<char>(), data->size()), |
| &base_64); |
| const char kDataUrlPrefix[] = "data:image/png;base64,"; |
| return GURL(kDataUrlPrefix + base_64).spec(); |
| } |
| |
| void ExtensionInfoGenerator::OnImageLoaded( |
| std::unique_ptr<developer::ExtensionInfo> info, |
| const gfx::Image& icon) { |
| if (!icon.IsEmpty()) { |
| info->icon_url = GetIconUrlFromImage(icon); |
| } else { |
| bool is_app = |
| info->type == developer::EXTENSION_TYPE_HOSTED_APP || |
| info->type == developer::EXTENSION_TYPE_LEGACY_PACKAGED_APP || |
| info->type == developer::EXTENSION_TYPE_PLATFORM_APP; |
| info->icon_url = GetDefaultIconUrl( |
| is_app, info->state != developer::EXTENSION_STATE_ENABLED); |
| } |
| |
| list_.push_back(std::move(*info)); |
| |
| --pending_image_loads_; |
| |
| if (pending_image_loads_ == 0) { // All done! |
| ExtensionInfoList list = std::move(list_); |
| list_.clear(); |
| base::ResetAndReturn(&callback_).Run(std::move(list)); |
| // WARNING: |this| is possibly deleted after this line! |
| } |
| } |
| |
| } // namespace extensions |