| // Copyright (c) 2012 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/developer_private_api.h" |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/lazy_instance.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/devtools/devtools_window.h" |
| #include "chrome/browser/extensions/api/developer_private/developer_private_mangle.h" |
| #include "chrome/browser/extensions/api/developer_private/entry_picker.h" |
| #include "chrome/browser/extensions/api/developer_private/extension_info_generator.h" |
| #include "chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.h" |
| #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" |
| #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" |
| #include "chrome/browser/extensions/devtools_util.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/shared_module_service.h" |
| #include "chrome/browser/extensions/unpacked_installer.h" |
| #include "chrome/browser/extensions/updater/extension_updater.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/extensions/api/developer_private.h" |
| #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" |
| #include "chrome/common/url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/app_window/app_window.h" |
| #include "extensions/browser/app_window/app_window_registry.h" |
| #include "extensions/browser/error_map.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/file_highlighter.h" |
| #include "extensions/browser/management_policy.h" |
| #include "extensions/browser/notification_types.h" |
| #include "extensions/browser/warning_service.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension_resource.h" |
| #include "extensions/common/extension_set.h" |
| #include "extensions/common/feature_switch.h" |
| #include "extensions/common/install_warning.h" |
| #include "extensions/common/manifest.h" |
| #include "extensions/common/manifest_handlers/icons_handler.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "extensions/grit/extensions_browser_resources.h" |
| #include "storage/browser/fileapi/external_mount_points.h" |
| #include "storage/browser/fileapi/file_system_context.h" |
| #include "storage/browser/fileapi/file_system_operation.h" |
| #include "storage/browser/fileapi/file_system_operation_runner.h" |
| #include "storage/browser/fileapi/isolated_context.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| namespace extensions { |
| |
| namespace developer_private = api::developer_private; |
| |
| namespace { |
| |
| const char kNoSuchExtensionError[] = "No such extension."; |
| const char kCannotModifyPolicyExtensionError[] = |
| "Cannot modify the extension by policy."; |
| const char kRequiresUserGestureError[] = |
| "This action requires a user gesture."; |
| const char kCouldNotShowSelectFileDialogError[] = |
| "Could not show a file chooser."; |
| const char kFileSelectionCanceled[] = |
| "File selection was canceled."; |
| const char kNoSuchRendererError[] = "No such renderer."; |
| const char kInvalidPathError[] = "Invalid path."; |
| const char kManifestKeyIsRequiredError[] = |
| "The 'manifestKey' argument is required for manifest files."; |
| const char kCouldNotFindWebContentsError[] = |
| "Could not find a valid web contents."; |
| |
| const char kUnpackedAppsFolder[] = "apps_target"; |
| const char kManifestFile[] = "manifest.json"; |
| |
| ExtensionService* GetExtensionService(content::BrowserContext* context) { |
| return ExtensionSystem::Get(context)->extension_service(); |
| } |
| |
| ExtensionUpdater* GetExtensionUpdater(Profile* profile) { |
| return GetExtensionService(profile)->updater(); |
| } |
| |
| GURL GetImageURLFromData(const std::string& contents) { |
| std::string contents_base64; |
| base::Base64Encode(contents, &contents_base64); |
| |
| // TODO(dvh): make use of content::kDataScheme. Filed as crbug/297301. |
| const char kDataURLPrefix[] = "data:;base64,"; |
| return GURL(kDataURLPrefix + contents_base64); |
| } |
| |
| GURL GetDefaultImageURL(developer_private::ItemType type) { |
| int icon_resource_id; |
| switch (type) { |
| case developer::ITEM_TYPE_LEGACY_PACKAGED_APP: |
| case developer::ITEM_TYPE_HOSTED_APP: |
| case developer::ITEM_TYPE_PACKAGED_APP: |
| icon_resource_id = IDR_APP_DEFAULT_ICON; |
| break; |
| default: |
| icon_resource_id = IDR_EXTENSION_DEFAULT_ICON; |
| break; |
| } |
| |
| return GetImageURLFromData( |
| ResourceBundle::GetSharedInstance().GetRawDataResourceForScale( |
| icon_resource_id, ui::SCALE_FACTOR_100P).as_string()); |
| } |
| |
| // TODO(dvh): This code should be refactored and moved to |
| // extensions::ImageLoader. Also a resize should be performed to avoid |
| // potential huge URLs: crbug/297298. |
| GURL ToDataURL(const base::FilePath& path, developer_private::ItemType type) { |
| std::string contents; |
| if (path.empty() || !base::ReadFileToString(path, &contents)) |
| return GetDefaultImageURL(type); |
| |
| return GetImageURLFromData(contents); |
| } |
| |
| void BroadcastItemStateChanged(content::BrowserContext* browser_context, |
| developer::EventType event_type, |
| const std::string& item_id) { |
| developer::EventData event_data; |
| event_data.event_type = event_type; |
| event_data.item_id = item_id; |
| |
| scoped_ptr<base::ListValue> args(new base::ListValue()); |
| args->Append(event_data.ToValue().release()); |
| scoped_ptr<Event> event(new Event( |
| developer_private::OnItemStateChanged::kEventName, args.Pass())); |
| EventRouter::Get(browser_context)->BroadcastEvent(event.Pass()); |
| } |
| |
| std::string ReadFileToString(const base::FilePath& path) { |
| std::string data; |
| ignore_result(base::ReadFileToString(path, &data)); |
| return data; |
| } |
| |
| bool UserCanModifyExtensionConfiguration( |
| const Extension* extension, |
| content::BrowserContext* browser_context, |
| std::string* error) { |
| ManagementPolicy* management_policy = |
| ExtensionSystem::Get(browser_context)->management_policy(); |
| if (!management_policy->UserMayModifySettings(extension, nullptr)) { |
| LOG(ERROR) << "Attempt to change settings of an extension that is " |
| << "non-usermanagable was made. Extension id : " |
| << extension->id(); |
| *error = kCannotModifyPolicyExtensionError; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace ChoosePath = api::developer_private::ChoosePath; |
| namespace GetItemsInfo = api::developer_private::GetItemsInfo; |
| namespace PackDirectory = api::developer_private::PackDirectory; |
| namespace Reload = api::developer_private::Reload; |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<DeveloperPrivateAPI> > |
| g_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<DeveloperPrivateAPI>* |
| DeveloperPrivateAPI::GetFactoryInstance() { |
| return g_factory.Pointer(); |
| } |
| |
| // static |
| DeveloperPrivateAPI* DeveloperPrivateAPI::Get( |
| content::BrowserContext* context) { |
| return GetFactoryInstance()->Get(context); |
| } |
| |
| DeveloperPrivateAPI::DeveloperPrivateAPI(content::BrowserContext* context) |
| : profile_(Profile::FromBrowserContext(context)) { |
| RegisterNotifications(); |
| } |
| |
| DeveloperPrivateEventRouter::DeveloperPrivateEventRouter(Profile* profile) |
| : extension_registry_observer_(this), |
| error_console_observer_(this), |
| process_manager_observer_(this), |
| profile_(profile) { |
| extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); |
| error_console_observer_.Add(ErrorConsole::Get(profile)); |
| process_manager_observer_.Add(ProcessManager::Get(profile)); |
| } |
| |
| DeveloperPrivateEventRouter::~DeveloperPrivateEventRouter() { |
| } |
| |
| void DeveloperPrivateEventRouter::AddExtensionId( |
| const std::string& extension_id) { |
| extension_ids_.insert(extension_id); |
| } |
| |
| void DeveloperPrivateEventRouter::RemoveExtensionId( |
| const std::string& extension_id) { |
| extension_ids_.erase(extension_id); |
| } |
| |
| void DeveloperPrivateEventRouter::OnExtensionLoaded( |
| content::BrowserContext* browser_context, |
| const Extension* extension) { |
| DCHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context))); |
| BroadcastItemStateChanged( |
| browser_context, developer::EVENT_TYPE_LOADED, extension->id()); |
| } |
| |
| void DeveloperPrivateEventRouter::OnExtensionUnloaded( |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| UnloadedExtensionInfo::Reason reason) { |
| DCHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context))); |
| BroadcastItemStateChanged( |
| browser_context, developer::EVENT_TYPE_UNLOADED, extension->id()); |
| } |
| |
| void DeveloperPrivateEventRouter::OnExtensionWillBeInstalled( |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| bool is_update, |
| bool from_ephemeral, |
| const std::string& old_name) { |
| DCHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context))); |
| BroadcastItemStateChanged( |
| browser_context, developer::EVENT_TYPE_INSTALLED, extension->id()); |
| } |
| |
| void DeveloperPrivateEventRouter::OnExtensionUninstalled( |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| extensions::UninstallReason reason) { |
| DCHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context))); |
| BroadcastItemStateChanged( |
| browser_context, developer::EVENT_TYPE_UNINSTALLED, extension->id()); |
| } |
| |
| void DeveloperPrivateEventRouter::OnErrorAdded(const ExtensionError* error) { |
| // We don't want to handle errors thrown by extensions subscribed to these |
| // events (currently only the Apps Developer Tool), because doing so risks |
| // entering a loop. |
| if (extension_ids_.find(error->extension_id()) != extension_ids_.end()) |
| return; |
| |
| BroadcastItemStateChanged( |
| profile_, developer::EVENT_TYPE_ERROR_ADDED, error->extension_id()); |
| } |
| |
| void DeveloperPrivateEventRouter::OnExtensionFrameRegistered( |
| const std::string& extension_id, |
| content::RenderFrameHost* render_frame_host) { |
| BroadcastItemStateChanged( |
| profile_, developer::EVENT_TYPE_VIEW_REGISTERED, extension_id); |
| } |
| |
| void DeveloperPrivateEventRouter::OnExtensionFrameUnregistered( |
| const std::string& extension_id, |
| content::RenderFrameHost* render_frame_host) { |
| BroadcastItemStateChanged( |
| profile_, developer::EVENT_TYPE_VIEW_UNREGISTERED, extension_id); |
| } |
| |
| void DeveloperPrivateAPI::SetLastUnpackedDirectory(const base::FilePath& path) { |
| last_unpacked_directory_ = path; |
| } |
| |
| void DeveloperPrivateAPI::RegisterNotifications() { |
| EventRouter::Get(profile_)->RegisterObserver( |
| this, developer_private::OnItemStateChanged::kEventName); |
| } |
| |
| DeveloperPrivateAPI::~DeveloperPrivateAPI() {} |
| |
| void DeveloperPrivateAPI::Shutdown() {} |
| |
| void DeveloperPrivateAPI::OnListenerAdded( |
| const EventListenerInfo& details) { |
| if (!developer_private_event_router_) { |
| developer_private_event_router_.reset( |
| new DeveloperPrivateEventRouter(profile_)); |
| } |
| |
| developer_private_event_router_->AddExtensionId(details.extension_id); |
| } |
| |
| void DeveloperPrivateAPI::OnListenerRemoved( |
| const EventListenerInfo& details) { |
| if (!EventRouter::Get(profile_)->HasEventListener( |
| developer_private::OnItemStateChanged::kEventName)) { |
| developer_private_event_router_.reset(NULL); |
| } else { |
| developer_private_event_router_->RemoveExtensionId(details.extension_id); |
| } |
| } |
| |
| namespace api { |
| |
| DeveloperPrivateAPIFunction::~DeveloperPrivateAPIFunction() { |
| } |
| |
| const Extension* DeveloperPrivateAPIFunction::GetExtensionById( |
| const std::string& id) { |
| return ExtensionRegistry::Get(browser_context())->GetExtensionById( |
| id, ExtensionRegistry::EVERYTHING); |
| } |
| |
| bool DeveloperPrivateAutoUpdateFunction::RunSync() { |
| ExtensionUpdater* updater = GetExtensionUpdater(GetProfile()); |
| if (updater) |
| updater->CheckNow(ExtensionUpdater::CheckParams()); |
| SetResult(new base::FundamentalValue(true)); |
| return true; |
| } |
| |
| DeveloperPrivateAutoUpdateFunction::~DeveloperPrivateAutoUpdateFunction() {} |
| |
| DeveloperPrivateGetExtensionsInfoFunction:: |
| ~DeveloperPrivateGetExtensionsInfoFunction() { |
| } |
| |
| ExtensionFunction::ResponseAction |
| DeveloperPrivateGetExtensionsInfoFunction::Run() { |
| scoped_ptr<developer::GetExtensionsInfo::Params> params( |
| developer::GetExtensionsInfo::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| bool include_disabled = true; |
| bool include_terminated = true; |
| if (params->options) { |
| if (params->options->include_disabled) |
| include_disabled = *params->options->include_disabled; |
| if (params->options->include_terminated) |
| include_terminated = *params->options->include_terminated; |
| } |
| |
| std::vector<linked_ptr<developer::ExtensionInfo>> list = |
| ExtensionInfoGenerator(browser_context()). |
| CreateExtensionsInfo(include_disabled, include_terminated); |
| |
| return RespondNow(ArgumentList( |
| developer::GetExtensionsInfo::Results::Create(list))); |
| } |
| |
| DeveloperPrivateGetExtensionInfoFunction:: |
| ~DeveloperPrivateGetExtensionInfoFunction() { |
| } |
| |
| ExtensionFunction::ResponseAction |
| DeveloperPrivateGetExtensionInfoFunction::Run() { |
| scoped_ptr<developer::GetExtensionInfo::Params> params( |
| developer::GetExtensionInfo::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context()); |
| developer::ExtensionState state = developer::EXTENSION_STATE_ENABLED; |
| const Extension* extension = |
| registry->enabled_extensions().GetByID(params->id); |
| if (!extension && |
| (extension = registry->disabled_extensions().GetByID(params->id)) != |
| nullptr) { |
| state = developer::EXTENSION_STATE_DISABLED; |
| } else if (!extension && |
| (extension = |
| registry->terminated_extensions().GetByID(params->id)) != |
| nullptr) { |
| state = developer::EXTENSION_STATE_TERMINATED; |
| } |
| |
| if (!extension) |
| return RespondNow(Error(kNoSuchExtensionError)); |
| |
| return RespondNow(OneArgument(ExtensionInfoGenerator(browser_context()). |
| CreateExtensionInfo(*extension, state)->ToValue().release())); |
| } |
| |
| DeveloperPrivateGetItemsInfoFunction::DeveloperPrivateGetItemsInfoFunction() {} |
| DeveloperPrivateGetItemsInfoFunction::~DeveloperPrivateGetItemsInfoFunction() {} |
| |
| ExtensionFunction::ResponseAction DeveloperPrivateGetItemsInfoFunction::Run() { |
| scoped_ptr<developer::GetItemsInfo::Params> params( |
| developer::GetItemsInfo::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| ExtensionSet items; |
| ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context()); |
| items.InsertAll(registry->enabled_extensions()); |
| |
| if (params->include_disabled) |
| items.InsertAll(registry->disabled_extensions()); |
| if (params->include_terminated) |
| items.InsertAll(registry->terminated_extensions()); |
| |
| std::map<std::string, ExtensionResource> resource_map; |
| for (const scoped_refptr<const Extension>& item : items) { |
| // Don't show component extensions and invisible apps. |
| if (ui_util::ShouldDisplayInExtensionSettings(item.get(), |
| browser_context())) { |
| resource_map[item->id()] = |
| IconsInfo::GetIconResource(item.get(), |
| extension_misc::EXTENSION_ICON_MEDIUM, |
| ExtensionIconSet::MATCH_BIGGER); |
| } |
| } |
| |
| std::vector<linked_ptr<developer::ExtensionInfo>> list = |
| ExtensionInfoGenerator(browser_context()). |
| CreateExtensionsInfo(params->include_disabled, |
| params->include_terminated); |
| |
| for (const linked_ptr<developer::ExtensionInfo>& info : list) |
| item_list_.push_back(developer_private_mangle::MangleExtensionInfo(*info)); |
| |
| content::BrowserThread::PostTask( |
| content::BrowserThread::FILE, |
| FROM_HERE, |
| base::Bind(&DeveloperPrivateGetItemsInfoFunction::GetIconsOnFileThread, |
| this, |
| resource_map)); |
| |
| return RespondLater(); |
| } |
| |
| void DeveloperPrivateGetItemsInfoFunction::GetIconsOnFileThread( |
| const std::map<std::string, ExtensionResource> resource_map) { |
| for (const linked_ptr<developer::ItemInfo>& item : item_list_) { |
| auto resource = resource_map.find(item->id); |
| if (resource != resource_map.end()) { |
| item->icon_url = ToDataURL(resource->second.GetFilePath(), |
| item->type).spec(); |
| } |
| } |
| |
| content::BrowserThread::PostTask( |
| content::BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&DeveloperPrivateGetItemsInfoFunction::Finish, this)); |
| } |
| |
| void DeveloperPrivateGetItemsInfoFunction::Finish() { |
| Respond(ArgumentList(developer::GetItemsInfo::Results::Create(item_list_))); |
| } |
| |
| DeveloperPrivateUpdateExtensionConfigurationFunction:: |
| ~DeveloperPrivateUpdateExtensionConfigurationFunction() {} |
| |
| ExtensionFunction::ResponseAction |
| DeveloperPrivateUpdateExtensionConfigurationFunction::Run() { |
| scoped_ptr<developer::UpdateExtensionConfiguration::Params> params( |
| developer::UpdateExtensionConfiguration::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| const developer::ExtensionConfigurationUpdate& update = params->update; |
| |
| const Extension* extension = GetExtensionById(update.extension_id); |
| if (!extension) |
| return RespondNow(Error(kNoSuchExtensionError)); |
| if (!user_gesture()) |
| return RespondNow(Error(kRequiresUserGestureError)); |
| |
| if (update.file_access) { |
| std::string error; |
| if (!UserCanModifyExtensionConfiguration(extension, |
| browser_context(), |
| &error)) { |
| return RespondNow(Error(error)); |
| } |
| util::SetAllowFileAccess( |
| extension->id(), browser_context(), *update.file_access); |
| } |
| if (update.incognito_access) { |
| util::SetIsIncognitoEnabled( |
| extension->id(), browser_context(), *update.incognito_access); |
| } |
| if (update.error_collection) { |
| ErrorConsole::Get(browser_context())->SetReportingAllForExtension( |
| extension->id(), *update.error_collection); |
| } |
| if (update.run_on_all_urls) { |
| util::SetAllowedScriptingOnAllUrls( |
| extension->id(), browser_context(), *update.run_on_all_urls); |
| } |
| if (update.show_action_button) { |
| ExtensionActionAPI::SetBrowserActionVisibility( |
| ExtensionPrefs::Get(browser_context()), |
| extension->id(), |
| *update.show_action_button); |
| } |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| DeveloperPrivateReloadFunction::~DeveloperPrivateReloadFunction() {} |
| |
| ExtensionFunction::ResponseAction DeveloperPrivateReloadFunction::Run() { |
| scoped_ptr<Reload::Params> params(Reload::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| |
| const Extension* extension = GetExtensionById(params->extension_id); |
| if (!extension) |
| return RespondNow(Error(kNoSuchExtensionError)); |
| |
| bool fail_quietly = params->options && |
| params->options->fail_quietly && |
| *params->options->fail_quietly; |
| |
| ExtensionService* service = GetExtensionService(browser_context()); |
| if (fail_quietly) |
| service->ReloadExtensionWithQuietFailure(params->extension_id); |
| else |
| service->ReloadExtension(params->extension_id); |
| |
| // TODO(devlin): We shouldn't return until the extension has finished trying |
| // to reload (and then we could also return the error). |
| return RespondNow(NoArguments()); |
| } |
| |
| DeveloperPrivateShowPermissionsDialogFunction:: |
| DeveloperPrivateShowPermissionsDialogFunction() {} |
| |
| DeveloperPrivateShowPermissionsDialogFunction:: |
| ~DeveloperPrivateShowPermissionsDialogFunction() {} |
| |
| ExtensionFunction::ResponseAction |
| DeveloperPrivateShowPermissionsDialogFunction::Run() { |
| scoped_ptr<developer::ShowPermissionsDialog::Params> params( |
| developer::ShowPermissionsDialog::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| const Extension* target_extension = GetExtensionById(params->extension_id); |
| if (!target_extension) |
| return RespondNow(Error(kNoSuchExtensionError)); |
| |
| content::WebContents* web_contents = GetSenderWebContents(); |
| if (!web_contents) |
| return RespondNow(Error(kCouldNotFindWebContentsError)); |
| |
| ShowPermissionsDialogHelper::Show( |
| browser_context(), |
| web_contents, |
| target_extension, |
| source_context_type() == Feature::WEBUI_CONTEXT, |
| base::Bind(&DeveloperPrivateShowPermissionsDialogFunction::Finish, this)); |
| return RespondLater(); |
| } |
| |
| void DeveloperPrivateShowPermissionsDialogFunction::Finish() { |
| Respond(NoArguments()); |
| } |
| |
| DeveloperPrivateLoadUnpackedFunction::DeveloperPrivateLoadUnpackedFunction() |
| : fail_quietly_(false) { |
| } |
| |
| ExtensionFunction::ResponseAction DeveloperPrivateLoadUnpackedFunction::Run() { |
| scoped_ptr<developer_private::LoadUnpacked::Params> params( |
| developer_private::LoadUnpacked::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (!ShowPicker( |
| ui::SelectFileDialog::SELECT_FOLDER, |
| l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY), |
| ui::SelectFileDialog::FileTypeInfo(), |
| 0 /* file_type_index */)) { |
| return RespondNow(Error(kCouldNotShowSelectFileDialogError)); |
| } |
| |
| fail_quietly_ = params->options && |
| params->options->fail_quietly && |
| *params->options->fail_quietly; |
| |
| AddRef(); // Balanced in FileSelected / FileSelectionCanceled. |
| return RespondLater(); |
| } |
| |
| void DeveloperPrivateLoadUnpackedFunction::FileSelected( |
| const base::FilePath& path) { |
| scoped_refptr<UnpackedInstaller> installer( |
| UnpackedInstaller::Create(GetExtensionService(browser_context()))); |
| installer->set_be_noisy_on_failure(!fail_quietly_); |
| installer->set_completion_callback( |
| base::Bind(&DeveloperPrivateLoadUnpackedFunction::OnLoadComplete, this)); |
| installer->Load(path); |
| |
| DeveloperPrivateAPI::Get(browser_context())->SetLastUnpackedDirectory(path); |
| |
| Release(); // Balanced in Run(). |
| } |
| |
| void DeveloperPrivateLoadUnpackedFunction::FileSelectionCanceled() { |
| // This isn't really an error, but we should keep it like this for |
| // backward compatability. |
| Respond(Error(kFileSelectionCanceled)); |
| Release(); // Balanced in Run(). |
| } |
| |
| void DeveloperPrivateLoadUnpackedFunction::OnLoadComplete( |
| const Extension* extension, |
| const base::FilePath& file_path, |
| const std::string& error) { |
| Respond(extension ? NoArguments() : Error(error)); |
| } |
| |
| bool DeveloperPrivateChooseEntryFunction::ShowPicker( |
| ui::SelectFileDialog::Type picker_type, |
| const base::string16& select_title, |
| const ui::SelectFileDialog::FileTypeInfo& info, |
| int file_type_index) { |
| content::WebContents* web_contents = GetSenderWebContents(); |
| if (!web_contents) |
| return false; |
| |
| // The entry picker will hold a reference to this function instance, |
| // and subsequent sending of the function response) until the user has |
| // selected a file or cancelled the picker. At that point, the picker will |
| // delete itself. |
| new EntryPicker(this, |
| web_contents, |
| picker_type, |
| DeveloperPrivateAPI::Get(browser_context())-> |
| GetLastUnpackedDirectory(), |
| select_title, |
| info, |
| file_type_index); |
| return true; |
| } |
| |
| DeveloperPrivateChooseEntryFunction::~DeveloperPrivateChooseEntryFunction() {} |
| |
| void DeveloperPrivatePackDirectoryFunction::OnPackSuccess( |
| const base::FilePath& crx_file, |
| const base::FilePath& pem_file) { |
| developer::PackDirectoryResponse response; |
| response.message = base::UTF16ToUTF8( |
| PackExtensionJob::StandardSuccessMessage(crx_file, pem_file)); |
| response.status = developer::PACK_STATUS_SUCCESS; |
| Respond(OneArgument(response.ToValue().release())); |
| Release(); // Balanced in Run(). |
| } |
| |
| void DeveloperPrivatePackDirectoryFunction::OnPackFailure( |
| const std::string& error, |
| ExtensionCreator::ErrorType error_type) { |
| developer::PackDirectoryResponse response; |
| response.message = error; |
| if (error_type == ExtensionCreator::kCRXExists) { |
| response.item_path = item_path_str_; |
| response.pem_path = key_path_str_; |
| response.override_flags = ExtensionCreator::kOverwriteCRX; |
| response.status = developer::PACK_STATUS_WARNING; |
| } else { |
| response.status = developer::PACK_STATUS_ERROR; |
| } |
| Respond(OneArgument(response.ToValue().release())); |
| Release(); // Balanced in Run(). |
| } |
| |
| ExtensionFunction::ResponseAction DeveloperPrivatePackDirectoryFunction::Run() { |
| scoped_ptr<PackDirectory::Params> params( |
| PackDirectory::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| int flags = params->flags ? *params->flags : 0; |
| item_path_str_ = params->path; |
| if (params->private_key_path) |
| key_path_str_ = *params->private_key_path; |
| |
| base::FilePath root_directory = |
| base::FilePath::FromUTF8Unsafe(item_path_str_); |
| base::FilePath key_file = base::FilePath::FromUTF8Unsafe(key_path_str_); |
| |
| developer::PackDirectoryResponse response; |
| if (root_directory.empty()) { |
| if (item_path_str_.empty()) |
| response.message = l10n_util::GetStringUTF8( |
| IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_REQUIRED); |
| else |
| response.message = l10n_util::GetStringUTF8( |
| IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_INVALID); |
| |
| response.status = developer::PACK_STATUS_ERROR; |
| return RespondNow(OneArgument(response.ToValue().release())); |
| } |
| |
| if (!key_path_str_.empty() && key_file.empty()) { |
| response.message = l10n_util::GetStringUTF8( |
| IDS_EXTENSION_PACK_DIALOG_ERROR_KEY_INVALID); |
| response.status = developer::PACK_STATUS_ERROR; |
| return RespondNow(OneArgument(response.ToValue().release())); |
| } |
| |
| AddRef(); // Balanced in OnPackSuccess / OnPackFailure. |
| |
| // TODO(devlin): Why is PackExtensionJob ref-counted? |
| pack_job_ = new PackExtensionJob(this, root_directory, key_file, flags); |
| pack_job_->Start(); |
| return RespondLater(); |
| } |
| |
| DeveloperPrivatePackDirectoryFunction::DeveloperPrivatePackDirectoryFunction() { |
| } |
| |
| DeveloperPrivatePackDirectoryFunction:: |
| ~DeveloperPrivatePackDirectoryFunction() {} |
| |
| DeveloperPrivateLoadUnpackedFunction::~DeveloperPrivateLoadUnpackedFunction() {} |
| |
| bool DeveloperPrivateLoadDirectoryFunction::RunAsync() { |
| // TODO(grv) : add unittests. |
| std::string directory_url_str; |
| std::string filesystem_name; |
| std::string filesystem_path; |
| |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name)); |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path)); |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &directory_url_str)); |
| |
| context_ = content::BrowserContext::GetStoragePartition( |
| GetProfile(), render_view_host()->GetSiteInstance()) |
| ->GetFileSystemContext(); |
| |
| // Directory url is non empty only for syncfilesystem. |
| if (!directory_url_str.empty()) { |
| storage::FileSystemURL directory_url = |
| context_->CrackURL(GURL(directory_url_str)); |
| if (!directory_url.is_valid() || |
| directory_url.type() != storage::kFileSystemTypeSyncable) { |
| SetError("DirectoryEntry of unsupported filesystem."); |
| return false; |
| } |
| return LoadByFileSystemAPI(directory_url); |
| } else { |
| // Check if the DirecotryEntry is the instance of chrome filesystem. |
| if (!app_file_handler_util::ValidateFileEntryAndGetPath(filesystem_name, |
| filesystem_path, |
| render_view_host_, |
| &project_base_path_, |
| &error_)) { |
| SetError("DirectoryEntry of unsupported filesystem."); |
| return false; |
| } |
| |
| // Try to load using the FileSystem API backend, in case the filesystem |
| // points to a non-native local directory. |
| std::string filesystem_id; |
| bool cracked = |
| storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id); |
| CHECK(cracked); |
| base::FilePath virtual_path = |
| storage::IsolatedContext::GetInstance() |
| ->CreateVirtualRootPath(filesystem_id) |
| .Append(base::FilePath::FromUTF8Unsafe(filesystem_path)); |
| storage::FileSystemURL directory_url = context_->CreateCrackedFileSystemURL( |
| extensions::Extension::GetBaseURLFromExtensionId(extension_id()), |
| storage::kFileSystemTypeIsolated, |
| virtual_path); |
| |
| if (directory_url.is_valid() && |
| directory_url.type() != storage::kFileSystemTypeNativeLocal && |
| directory_url.type() != storage::kFileSystemTypeRestrictedNativeLocal && |
| directory_url.type() != storage::kFileSystemTypeDragged) { |
| return LoadByFileSystemAPI(directory_url); |
| } |
| |
| Load(); |
| } |
| |
| return true; |
| } |
| |
| bool DeveloperPrivateLoadDirectoryFunction::LoadByFileSystemAPI( |
| const storage::FileSystemURL& directory_url) { |
| std::string directory_url_str = directory_url.ToGURL().spec(); |
| |
| size_t pos = 0; |
| // Parse the project directory name from the project url. The project url is |
| // expected to have project name as the suffix. |
| if ((pos = directory_url_str.rfind("/")) == std::string::npos) { |
| SetError("Invalid Directory entry."); |
| return false; |
| } |
| |
| std::string project_name; |
| project_name = directory_url_str.substr(pos + 1); |
| project_base_url_ = directory_url_str.substr(0, pos + 1); |
| |
| base::FilePath project_path(GetProfile()->GetPath()); |
| project_path = project_path.AppendASCII(kUnpackedAppsFolder); |
| project_path = project_path.Append( |
| base::FilePath::FromUTF8Unsafe(project_name)); |
| |
| project_base_path_ = project_path; |
| |
| content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DeveloperPrivateLoadDirectoryFunction:: |
| ClearExistingDirectoryContent, |
| this, |
| project_base_path_)); |
| return true; |
| } |
| |
| void DeveloperPrivateLoadDirectoryFunction::Load() { |
| ExtensionService* service = GetExtensionService(GetProfile()); |
| UnpackedInstaller::Create(service)->Load(project_base_path_); |
| |
| // TODO(grv) : The unpacked installer should fire an event when complete |
| // and return the extension_id. |
| SetResult(new base::StringValue("-1")); |
| SendResponse(true); |
| } |
| |
| void DeveloperPrivateLoadDirectoryFunction::ClearExistingDirectoryContent( |
| const base::FilePath& project_path) { |
| |
| // Clear the project directory before copying new files. |
| base::DeleteFile(project_path, true /*recursive*/); |
| |
| pending_copy_operations_count_ = 1; |
| |
| content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, |
| base::Bind(&DeveloperPrivateLoadDirectoryFunction:: |
| ReadDirectoryByFileSystemAPI, |
| this, project_path, project_path.BaseName())); |
| } |
| |
| void DeveloperPrivateLoadDirectoryFunction::ReadDirectoryByFileSystemAPI( |
| const base::FilePath& project_path, |
| const base::FilePath& destination_path) { |
| GURL project_url = GURL(project_base_url_ + destination_path.AsUTF8Unsafe()); |
| storage::FileSystemURL url = context_->CrackURL(project_url); |
| |
| context_->operation_runner()->ReadDirectory( |
| url, base::Bind(&DeveloperPrivateLoadDirectoryFunction:: |
| ReadDirectoryByFileSystemAPICb, |
| this, project_path, destination_path)); |
| } |
| |
| void DeveloperPrivateLoadDirectoryFunction::ReadDirectoryByFileSystemAPICb( |
| const base::FilePath& project_path, |
| const base::FilePath& destination_path, |
| base::File::Error status, |
| const storage::FileSystemOperation::FileEntryList& file_list, |
| bool has_more) { |
| if (status != base::File::FILE_OK) { |
| DLOG(ERROR) << "Error in copying files from sync filesystem."; |
| return; |
| } |
| |
| // We add 1 to the pending copy operations for both files and directories. We |
| // release the directory copy operation once all the files under the directory |
| // are added for copying. We do that to ensure that pendingCopyOperationsCount |
| // does not become zero before all copy operations are finished. |
| // In case the directory happens to be executing the last copy operation it |
| // will call SendResponse to send the response to the API. The pending copy |
| // operations of files are released by the CopyFile function. |
| pending_copy_operations_count_ += file_list.size(); |
| |
| for (size_t i = 0; i < file_list.size(); ++i) { |
| if (file_list[i].is_directory) { |
| ReadDirectoryByFileSystemAPI(project_path.Append(file_list[i].name), |
| destination_path.Append(file_list[i].name)); |
| continue; |
| } |
| |
| GURL project_url = GURL(project_base_url_ + |
| destination_path.Append(file_list[i].name).AsUTF8Unsafe()); |
| storage::FileSystemURL url = context_->CrackURL(project_url); |
| |
| base::FilePath target_path = project_path; |
| target_path = target_path.Append(file_list[i].name); |
| |
| context_->operation_runner()->CreateSnapshotFile( |
| url, |
| base::Bind(&DeveloperPrivateLoadDirectoryFunction::SnapshotFileCallback, |
| this, |
| target_path)); |
| } |
| |
| if (!has_more) { |
| // Directory copy operation released here. |
| pending_copy_operations_count_--; |
| |
| if (!pending_copy_operations_count_) { |
| content::BrowserThread::PostTask( |
| content::BrowserThread::UI, FROM_HERE, |
| base::Bind(&DeveloperPrivateLoadDirectoryFunction::SendResponse, |
| this, |
| success_)); |
| } |
| } |
| } |
| |
| void DeveloperPrivateLoadDirectoryFunction::SnapshotFileCallback( |
| const base::FilePath& target_path, |
| base::File::Error result, |
| const base::File::Info& file_info, |
| const base::FilePath& src_path, |
| const scoped_refptr<storage::ShareableFileReference>& file_ref) { |
| if (result != base::File::FILE_OK) { |
| SetError("Error in copying files from sync filesystem."); |
| success_ = false; |
| return; |
| } |
| |
| content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DeveloperPrivateLoadDirectoryFunction::CopyFile, |
| this, |
| src_path, |
| target_path)); |
| } |
| |
| void DeveloperPrivateLoadDirectoryFunction::CopyFile( |
| const base::FilePath& src_path, |
| const base::FilePath& target_path) { |
| if (!base::CreateDirectory(target_path.DirName())) { |
| SetError("Error in copying files from sync filesystem."); |
| success_ = false; |
| } |
| |
| if (success_) |
| base::CopyFile(src_path, target_path); |
| |
| CHECK(pending_copy_operations_count_ > 0); |
| pending_copy_operations_count_--; |
| |
| if (!pending_copy_operations_count_) { |
| content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| base::Bind(&DeveloperPrivateLoadDirectoryFunction::Load, |
| this)); |
| } |
| } |
| |
| DeveloperPrivateLoadDirectoryFunction::DeveloperPrivateLoadDirectoryFunction() |
| : pending_copy_operations_count_(0), success_(true) {} |
| |
| DeveloperPrivateLoadDirectoryFunction::~DeveloperPrivateLoadDirectoryFunction() |
| {} |
| |
| ExtensionFunction::ResponseAction DeveloperPrivateChoosePathFunction::Run() { |
| scoped_ptr<developer::ChoosePath::Params> params( |
| developer::ChoosePath::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| ui::SelectFileDialog::Type type = ui::SelectFileDialog::SELECT_FOLDER; |
| ui::SelectFileDialog::FileTypeInfo info; |
| |
| if (params->select_type == developer::SELECT_TYPE_FILE) |
| type = ui::SelectFileDialog::SELECT_OPEN_FILE; |
| base::string16 select_title; |
| |
| int file_type_index = 0; |
| if (params->file_type == developer::FILE_TYPE_LOAD) { |
| select_title = l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY); |
| } else if (params->file_type == developer::FILE_TYPE_PEM) { |
| select_title = l10n_util::GetStringUTF16( |
| IDS_EXTENSION_PACK_DIALOG_SELECT_KEY); |
| info.extensions.push_back(std::vector<base::FilePath::StringType>( |
| 1, FILE_PATH_LITERAL("pem"))); |
| info.extension_description_overrides.push_back( |
| l10n_util::GetStringUTF16( |
| IDS_EXTENSION_PACK_DIALOG_KEY_FILE_TYPE_DESCRIPTION)); |
| info.include_all_files = true; |
| file_type_index = 1; |
| } else { |
| NOTREACHED(); |
| } |
| |
| if (!ShowPicker( |
| type, |
| select_title, |
| info, |
| file_type_index)) { |
| return RespondNow(Error(kCouldNotShowSelectFileDialogError)); |
| } |
| |
| AddRef(); // Balanced by FileSelected / FileSelectionCanceled. |
| return RespondLater(); |
| } |
| |
| void DeveloperPrivateChoosePathFunction::FileSelected( |
| const base::FilePath& path) { |
| Respond(OneArgument(new base::StringValue(path.LossyDisplayName()))); |
| Release(); |
| } |
| |
| void DeveloperPrivateChoosePathFunction::FileSelectionCanceled() { |
| // This isn't really an error, but we should keep it like this for |
| // backward compatability. |
| Respond(Error(kFileSelectionCanceled)); |
| Release(); |
| } |
| |
| DeveloperPrivateChoosePathFunction::~DeveloperPrivateChoosePathFunction() {} |
| |
| bool DeveloperPrivateIsProfileManagedFunction::RunSync() { |
| SetResult(new base::FundamentalValue(GetProfile()->IsSupervised())); |
| return true; |
| } |
| |
| DeveloperPrivateIsProfileManagedFunction:: |
| ~DeveloperPrivateIsProfileManagedFunction() { |
| } |
| |
| DeveloperPrivateRequestFileSourceFunction:: |
| DeveloperPrivateRequestFileSourceFunction() {} |
| |
| DeveloperPrivateRequestFileSourceFunction:: |
| ~DeveloperPrivateRequestFileSourceFunction() {} |
| |
| ExtensionFunction::ResponseAction |
| DeveloperPrivateRequestFileSourceFunction::Run() { |
| params_ = developer::RequestFileSource::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(params_); |
| |
| const developer::RequestFileSourceProperties& properties = |
| params_->properties; |
| const Extension* extension = GetExtensionById(properties.extension_id); |
| if (!extension) |
| return RespondNow(Error(kNoSuchExtensionError)); |
| |
| // Under no circumstances should we ever need to reference a file outside of |
| // the extension's directory. If it tries to, abort. |
| base::FilePath path_suffix = |
| base::FilePath::FromUTF8Unsafe(properties.path_suffix); |
| if (path_suffix.empty() || path_suffix.ReferencesParent()) |
| return RespondNow(Error(kInvalidPathError)); |
| |
| if (properties.path_suffix == kManifestFile && !properties.manifest_key) |
| return RespondNow(Error(kManifestKeyIsRequiredError)); |
| |
| base::PostTaskAndReplyWithResult( |
| content::BrowserThread::GetBlockingPool(), |
| FROM_HERE, |
| base::Bind(&ReadFileToString, extension->path().Append(path_suffix)), |
| base::Bind(&DeveloperPrivateRequestFileSourceFunction::Finish, this)); |
| |
| return RespondLater(); |
| } |
| |
| void DeveloperPrivateRequestFileSourceFunction::Finish( |
| const std::string& file_contents) { |
| const developer::RequestFileSourceProperties& properties = |
| params_->properties; |
| const Extension* extension = GetExtensionById(properties.extension_id); |
| if (!extension) { |
| Respond(Error(kNoSuchExtensionError)); |
| return; |
| } |
| |
| developer::RequestFileSourceResponse response; |
| base::FilePath path_suffix = |
| base::FilePath::FromUTF8Unsafe(properties.path_suffix); |
| base::FilePath path = extension->path().Append(path_suffix); |
| response.title = base::StringPrintf("%s: %s", |
| extension->name().c_str(), |
| path.BaseName().AsUTF8Unsafe().c_str()); |
| response.message = properties.message; |
| |
| scoped_ptr<FileHighlighter> highlighter; |
| if (properties.path_suffix == kManifestFile) { |
| highlighter.reset(new ManifestHighlighter( |
| file_contents, |
| *properties.manifest_key, |
| properties.manifest_specific ? |
| *properties.manifest_specific : std::string())); |
| } else { |
| highlighter.reset(new SourceHighlighter( |
| file_contents, |
| properties.line_number ? *properties.line_number : 0)); |
| } |
| |
| response.before_highlight = highlighter->GetBeforeFeature(); |
| response.highlight = highlighter->GetFeature(); |
| response.after_highlight = highlighter->GetAfterFeature(); |
| |
| Respond(OneArgument(response.ToValue().release())); |
| } |
| |
| DeveloperPrivateOpenDevToolsFunction::DeveloperPrivateOpenDevToolsFunction() {} |
| DeveloperPrivateOpenDevToolsFunction::~DeveloperPrivateOpenDevToolsFunction() {} |
| |
| ExtensionFunction::ResponseAction |
| DeveloperPrivateOpenDevToolsFunction::Run() { |
| scoped_ptr<developer::OpenDevTools::Params> params( |
| developer::OpenDevTools::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| const developer::OpenDevToolsProperties& properties = params->properties; |
| |
| if (properties.render_process_id == -1) { |
| // This is a lazy background page. |
| const Extension* extension = properties.extension_id ? |
| ExtensionRegistry::Get(browser_context())->enabled_extensions().GetByID( |
| *properties.extension_id) : nullptr; |
| if (!extension) |
| return RespondNow(Error(kNoSuchExtensionError)); |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context()); |
| if (properties.incognito && *properties.incognito) |
| profile = profile->GetOffTheRecordProfile(); |
| |
| // Wakes up the background page and opens the inspect window. |
| devtools_util::InspectBackgroundPage(extension, profile); |
| return RespondNow(NoArguments()); |
| } |
| |
| content::RenderViewHost* rvh = |
| content::RenderViewHost::FromID(properties.render_process_id, |
| properties.render_view_id); |
| |
| content::WebContents* web_contents = |
| rvh ? content::WebContents::FromRenderViewHost(rvh) : nullptr; |
| // It's possible that the render view was closed since we last updated the |
| // links. Handle this gracefully. |
| if (!web_contents) |
| return RespondNow(Error(kNoSuchRendererError)); |
| |
| // If we include a url, we should inspect it specifically (and not just the |
| // render view). |
| if (properties.url) { |
| // Line/column numbers are reported in display-friendly 1-based numbers, |
| // but are inspected in zero-based numbers. |
| // Default to the first line/column. |
| DevToolsWindow::OpenDevToolsWindow( |
| web_contents, |
| DevToolsToggleAction::Reveal( |
| base::UTF8ToUTF16(*properties.url), |
| properties.line_number ? *properties.line_number - 1 : 0, |
| properties.column_number ? *properties.column_number - 1 : 0)); |
| } else { |
| DevToolsWindow::OpenDevToolsWindow(web_contents); |
| } |
| |
| // Once we open the inspector, we focus on the appropriate tab... |
| Browser* browser = chrome::FindBrowserWithWebContents(web_contents); |
| |
| // ... but some pages (popups and apps) don't have tabs, and some (background |
| // pages) don't have an associated browser. For these, the inspector opens in |
| // a new window, and our work is done. |
| if (!browser || !browser->is_type_tabbed()) |
| return RespondNow(NoArguments()); |
| |
| TabStripModel* tab_strip = browser->tab_strip_model(); |
| tab_strip->ActivateTabAt(tab_strip->GetIndexOfWebContents(web_contents), |
| false); // Not through direct user gesture. |
| return RespondNow(NoArguments()); |
| } |
| |
| DeveloperPrivateDeleteExtensionErrorsFunction:: |
| ~DeveloperPrivateDeleteExtensionErrorsFunction() {} |
| |
| ExtensionFunction::ResponseAction |
| DeveloperPrivateDeleteExtensionErrorsFunction::Run() { |
| scoped_ptr<developer::DeleteExtensionErrors::Params> params( |
| developer::DeleteExtensionErrors::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| const developer::DeleteExtensionErrorsProperties& properties = |
| params->properties; |
| |
| ErrorConsole* error_console = |
| ErrorConsole::Get(Profile::FromBrowserContext(browser_context())); |
| int type = -1; |
| if (properties.type != developer::ERROR_TYPE_NONE) { |
| type = properties.type == developer::ERROR_TYPE_MANIFEST ? |
| ExtensionError::MANIFEST_ERROR : ExtensionError::RUNTIME_ERROR; |
| } |
| std::set<int> error_ids; |
| if (properties.error_ids) { |
| error_ids.insert(properties.error_ids->begin(), |
| properties.error_ids->end()); |
| } |
| error_console->RemoveErrors(ErrorMap::Filter( |
| properties.extension_id, type, error_ids, false)); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| } // namespace api |
| |
| } // namespace extensions |