| // Copyright 2013 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 "extensions/browser/api/feedback_private/feedback_private_api.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/system/sys_info.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/feedback/system_logs/system_logs_fetcher.h" |
| #include "components/feedback/tracing_manager.h" |
| #include "extensions/browser/api/extensions_api_client.h" |
| #include "extensions/browser/api/feedback_private/feedback_private_delegate.h" |
| #include "extensions/browser/api/feedback_private/feedback_service.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/common/api/feedback_private.h" |
| #include "extensions/common/constants.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "ash/public/interfaces/assistant_controller.mojom.h" |
| #include "ash/public/interfaces/constants.mojom.h" |
| #include "extensions/browser/api/feedback_private/log_source_access_manager.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #endif // defined(OS_CHROMEOS) |
| |
| using extensions::api::feedback_private::SystemInformation; |
| using feedback::FeedbackData; |
| |
| namespace extensions { |
| |
| namespace feedback_private = api::feedback_private; |
| |
| using feedback_private::FeedbackInfo; |
| using feedback_private::FeedbackFlow; |
| using feedback_private::LogSource; |
| using feedback_private::SystemInformation; |
| |
| using SystemInformationList = |
| std::vector<api::feedback_private::SystemInformation>; |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<FeedbackPrivateAPI>>:: |
| DestructorAtExit g_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| namespace { |
| |
| constexpr base::FilePath::CharType kBluetoothLogsFilePath[] = |
| FILE_PATH_LITERAL("/var/log/bluetooth/log.bz2"); |
| |
| constexpr char kBluetoothLogsAttachmentName[] = "bluetooth_logs.bz2"; |
| |
| // Getting the filename of a blob prepends a "C:\fakepath" to the filename. |
| // This is undesirable, strip it if it exists. |
| std::string StripFakepath(const std::string& path) { |
| constexpr char kFakePathStr[] = "C:\\fakepath\\"; |
| if (base::StartsWith(path, kFakePathStr, |
| base::CompareCase::INSENSITIVE_ASCII)) |
| return path.substr(base::size(kFakePathStr) - 1); |
| return path; |
| } |
| |
| // Returns the type of the landing page which is shown to the user when the |
| // report is successfully sent. |
| feedback_private::LandingPageType GetLandingPageType(const std::string& email) { |
| #if defined(OS_CHROMEOS) |
| const std::string board = |
| base::ToLowerASCII(base::SysInfo::GetLsbReleaseBoard()); |
| if (board.find("eve") == std::string::npos) |
| return feedback_private::LANDING_PAGE_TYPE_NORMAL; |
| |
| if (!base::EndsWith(email, "@google.com", |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| return feedback_private::LANDING_PAGE_TYPE_NORMAL; |
| } |
| |
| return feedback_private::LANDING_PAGE_TYPE_TECHSTOP; |
| #else |
| return feedback_private::LANDING_PAGE_TYPE_NORMAL; |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| } // namespace |
| |
| // static |
| BrowserContextKeyedAPIFactory<FeedbackPrivateAPI>* |
| FeedbackPrivateAPI::GetFactoryInstance() { |
| return g_factory.Pointer(); |
| } |
| |
| FeedbackPrivateAPI::FeedbackPrivateAPI(content::BrowserContext* context) |
| : browser_context_(context), |
| #if !defined(OS_CHROMEOS) |
| service_(new FeedbackService(context)) { |
| #else |
| service_(new FeedbackService(context)), |
| log_source_access_manager_(new LogSourceAccessManager(context)){ |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| FeedbackPrivateAPI::~FeedbackPrivateAPI() {} |
| |
| FeedbackService* FeedbackPrivateAPI::GetService() const { |
| return service_.get(); |
| } |
| |
| #if defined(OS_CHROMEOS) |
| LogSourceAccessManager* FeedbackPrivateAPI::GetLogSourceAccessManager() const { |
| return log_source_access_manager_.get(); |
| } |
| #endif |
| |
| void FeedbackPrivateAPI::RequestFeedbackForFlow( |
| const std::string& description_template, |
| const std::string& description_placeholder_text, |
| const std::string& category_tag, |
| const std::string& extra_diagnostics, |
| const GURL& page_url, |
| api::feedback_private::FeedbackFlow flow, |
| bool from_assistant) { |
| if (browser_context_ && EventRouter::Get(browser_context_)) { |
| FeedbackInfo info; |
| info.description = description_template; |
| info.description_placeholder = |
| std::make_unique<std::string>(description_placeholder_text); |
| info.category_tag = std::make_unique<std::string>(category_tag); |
| info.page_url = std::make_unique<std::string>(page_url.spec()); |
| info.system_information = std::make_unique<SystemInformationList>(); |
| #if defined(OS_CHROMEOS) |
| info.from_assistant = std::make_unique<bool>(from_assistant); |
| #endif // defined(OS_CHROMEOS) |
| |
| // Any extra diagnostics information should be added to the sys info. |
| if (!extra_diagnostics.empty()) { |
| SystemInformation extra_info; |
| extra_info.key = "EXTRA_DIAGNOSTICS"; |
| extra_info.value = extra_diagnostics; |
| info.system_information->emplace_back(std::move(extra_info)); |
| } |
| |
| // The manager is only available if tracing is enabled. |
| if (TracingManager* manager = TracingManager::Get()) { |
| info.trace_id = std::make_unique<int>(manager->RequestTrace()); |
| } |
| info.flow = flow; |
| #if defined(OS_MACOSX) |
| const bool use_system_window_frame = true; |
| #else |
| const bool use_system_window_frame = false; |
| #endif |
| info.use_system_window_frame = |
| std::make_unique<bool>(use_system_window_frame); |
| |
| std::unique_ptr<base::ListValue> args = |
| feedback_private::OnFeedbackRequested::Create(info); |
| |
| auto event = std::make_unique<Event>( |
| events::FEEDBACK_PRIVATE_ON_FEEDBACK_REQUESTED, |
| feedback_private::OnFeedbackRequested::kEventName, std::move(args), |
| browser_context_); |
| |
| // TODO(weidongg/754329): Using DispatchEventWithLazyListener() is a |
| // temporary fix to the bug. Investigate a better solution that applies to |
| // all scenarios. |
| EventRouter::Get(browser_context_) |
| ->DispatchEventWithLazyListener(extension_misc::kFeedbackExtensionId, |
| std::move(event)); |
| } |
| } |
| |
| // static |
| base::Closure* FeedbackPrivateGetStringsFunction::test_callback_ = NULL; |
| |
| ExtensionFunction::ResponseAction FeedbackPrivateGetStringsFunction::Run() { |
| auto params = feedback_private::GetStrings::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| |
| FeedbackPrivateDelegate* feedback_private_delegate = |
| ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate(); |
| DCHECK(feedback_private_delegate); |
| std::unique_ptr<base::DictionaryValue> dict = |
| feedback_private_delegate->GetStrings( |
| browser_context(), |
| params->flow == FeedbackFlow::FEEDBACK_FLOW_SADTABCRASH); |
| |
| if (test_callback_ && !test_callback_->is_null()) |
| test_callback_->Run(); |
| |
| return RespondNow(OneArgument(std::move(dict))); |
| } |
| |
| ExtensionFunction::ResponseAction FeedbackPrivateGetUserEmailFunction::Run() { |
| FeedbackPrivateDelegate* feedback_private_delegate = |
| ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate(); |
| return RespondNow(OneArgument(std::make_unique<base::Value>( |
| feedback_private_delegate->GetSignedInUserEmail(browser_context())))); |
| } |
| |
| ExtensionFunction::ResponseAction |
| FeedbackPrivateGetSystemInformationFunction::Run() { |
| VLOG(1) << "Fetching system logs started."; |
| // Self-deleting object. |
| system_logs::SystemLogsFetcher* fetcher = |
| ExtensionsAPIClient::Get() |
| ->GetFeedbackPrivateDelegate() |
| ->CreateSystemLogsFetcher(browser_context()); |
| fetcher->Fetch(base::Bind( |
| &FeedbackPrivateGetSystemInformationFunction::OnCompleted, this)); |
| |
| return RespondLater(); |
| } |
| |
| void FeedbackPrivateGetSystemInformationFunction::OnCompleted( |
| std::unique_ptr<system_logs::SystemLogsResponse> sys_info) { |
| VLOG(1) << "Received system logs."; |
| SystemInformationList sys_info_list; |
| if (sys_info) { |
| sys_info_list.reserve(sys_info->size()); |
| for (auto& itr : *sys_info) { |
| SystemInformation sys_info_entry; |
| sys_info_entry.key = std::move(itr.first); |
| sys_info_entry.value = std::move(itr.second); |
| sys_info_list.emplace_back(std::move(sys_info_entry)); |
| } |
| } |
| |
| Respond(ArgumentList( |
| feedback_private::GetSystemInformation::Results::Create(sys_info_list))); |
| } |
| |
| ExtensionFunction::ResponseAction FeedbackPrivateReadLogSourceFunction::Run() { |
| #if defined(OS_CHROMEOS) |
| using Params = feedback_private::ReadLogSource::Params; |
| std::unique_ptr<Params> api_params = Params::Create(*args_); |
| |
| LogSourceAccessManager* log_source_manager = |
| FeedbackPrivateAPI::GetFactoryInstance() |
| ->Get(browser_context()) |
| ->GetLogSourceAccessManager(); |
| |
| if (!log_source_manager->FetchFromSource( |
| api_params->params, extension_id(), |
| base::Bind(&FeedbackPrivateReadLogSourceFunction::OnCompleted, |
| this))) { |
| return RespondNow(Error(base::StringPrintf( |
| "Unable to initiate fetch from log source %s.", |
| feedback_private::ToString(api_params->params.source)))); |
| } |
| |
| return RespondLater(); |
| #else |
| NOTREACHED() << "API function is not supported on this platform."; |
| return RespondNow(Error("API function is not supported on this platform.")); |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| #if defined(OS_CHROMEOS) |
| void FeedbackPrivateReadLogSourceFunction::OnCompleted( |
| std::unique_ptr<feedback_private::ReadLogSourceResult> result) { |
| Respond( |
| ArgumentList(feedback_private::ReadLogSource::Results::Create(*result))); |
| } |
| #endif // defined(OS_CHROMEOS) |
| |
| ExtensionFunction::ResponseAction FeedbackPrivateSendFeedbackFunction::Run() { |
| VLOG(1) << "Sending feedback report started."; |
| std::unique_ptr<feedback_private::SendFeedback::Params> params( |
| feedback_private::SendFeedback::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| const FeedbackInfo& feedback_info = params->feedback; |
| |
| // Populate feedback data. |
| FeedbackPrivateDelegate* delegate = |
| ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate(); |
| scoped_refptr<FeedbackData> feedback_data = |
| base::MakeRefCounted<FeedbackData>( |
| delegate->GetFeedbackUploaderForContext(browser_context())); |
| feedback_data->set_context(browser_context()); |
| feedback_data->set_description(feedback_info.description); |
| |
| if (feedback_info.product_id) |
| feedback_data->set_product_id(*feedback_info.product_id); |
| if (feedback_info.category_tag) |
| feedback_data->set_category_tag(*feedback_info.category_tag); |
| if (feedback_info.page_url) |
| feedback_data->set_page_url(*feedback_info.page_url); |
| if (feedback_info.email) |
| feedback_data->set_user_email(*feedback_info.email); |
| if (feedback_info.trace_id) |
| feedback_data->set_trace_id(*feedback_info.trace_id); |
| |
| if (feedback_info.attached_file_blob_uuid && |
| !feedback_info.attached_file_blob_uuid->empty()) { |
| feedback_data->set_attached_filename( |
| StripFakepath((*feedback_info.attached_file).name)); |
| feedback_data->set_attached_file_uuid( |
| *feedback_info.attached_file_blob_uuid); |
| } |
| |
| if (feedback_info.screenshot_blob_uuid && |
| !feedback_info.screenshot_blob_uuid->empty()) { |
| feedback_data->set_screenshot_uuid(*feedback_info.screenshot_blob_uuid); |
| } |
| |
| auto sys_logs = std::make_unique<FeedbackData::SystemLogsMap>(); |
| const SystemInformationList* sys_info = |
| feedback_info.system_information.get(); |
| if (sys_info) { |
| for (const SystemInformation& info : *sys_info) |
| sys_logs->emplace(info.key, info.value); |
| } |
| |
| #if defined(OS_CHROMEOS) |
| // Send feedback to Assistant server if triggered from Google Assistant. |
| if (feedback_info.from_assistant && *feedback_info.from_assistant) { |
| ash::mojom::AssistantControllerPtr assistant_controller; |
| content::BrowserContext::GetConnectorFor(browser_context()) |
| ->BindInterface(ash::mojom::kServiceName, &assistant_controller); |
| assistant_controller->SendAssistantFeedback( |
| feedback_info.assistant_debug_info_allowed && |
| *feedback_info.assistant_debug_info_allowed, |
| feedback_data->description()); |
| } |
| |
| delegate->FetchAndMergeIwlwifiDumpLogsIfPresent( |
| std::move(sys_logs), browser_context(), |
| base::Bind(&FeedbackPrivateSendFeedbackFunction::OnAllLogsFetched, this, |
| feedback_data, |
| feedback_info.send_bluetooth_logs && |
| *feedback_info.send_bluetooth_logs)); |
| #else |
| OnAllLogsFetched(feedback_data, false /* send_bluetooth_logs */, |
| std::move(sys_logs)); |
| #endif // defined(OS_CHROMEOS) |
| |
| return RespondLater(); |
| } |
| |
| void FeedbackPrivateSendFeedbackFunction::OnAllLogsFetched( |
| scoped_refptr<FeedbackData> feedback_data, |
| bool send_bluetooth_logs, |
| std::unique_ptr<system_logs::SystemLogsResponse> sys_logs) { |
| VLOG(1) << "All logs have been fetched. Proceeding with sending the report."; |
| |
| feedback_data->SetAndCompressSystemInfo(std::move(sys_logs)); |
| |
| if (send_bluetooth_logs) { |
| std::unique_ptr<std::string> bluetooth_logs = |
| std::make_unique<std::string>(); |
| if (base::ReadFileToString(base::FilePath(kBluetoothLogsFilePath), |
| bluetooth_logs.get())) { |
| feedback_data->AddFile(kBluetoothLogsAttachmentName, |
| std::move(bluetooth_logs)); |
| } |
| } |
| |
| FeedbackService* service = FeedbackPrivateAPI::GetFactoryInstance() |
| ->Get(browser_context()) |
| ->GetService(); |
| DCHECK(service); |
| |
| service->SendFeedback( |
| feedback_data, |
| base::Bind(&FeedbackPrivateSendFeedbackFunction::OnCompleted, this, |
| GetLandingPageType(feedback_data->user_email()))); |
| } |
| |
| void FeedbackPrivateSendFeedbackFunction::OnCompleted( |
| api::feedback_private::LandingPageType type, |
| bool success) { |
| Respond(TwoArguments( |
| std::make_unique<base::Value>(feedback_private::ToString( |
| success ? feedback_private::STATUS_SUCCESS |
| : feedback_private::STATUS_DELAYED)), |
| std::make_unique<base::Value>(feedback_private::ToString(type)))); |
| if (!success) { |
| ExtensionsAPIClient::Get() |
| ->GetFeedbackPrivateDelegate() |
| ->NotifyFeedbackDelayed(); |
| } |
| } |
| |
| ExtensionFunction::ResponseAction |
| FeedbackPrivateLogSrtPromptResultFunction::Run() { |
| std::unique_ptr<feedback_private::LogSrtPromptResult::Params> params( |
| feedback_private::LogSrtPromptResult::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| |
| const feedback_private::SrtPromptResult result = params->result; |
| |
| switch (result) { |
| case feedback_private::SRT_PROMPT_RESULT_ACCEPTED: |
| base::RecordAction(base::UserMetricsAction("Feedback.SrtPromptAccepted")); |
| break; |
| case feedback_private::SRT_PROMPT_RESULT_DECLINED: |
| base::RecordAction(base::UserMetricsAction("Feedback.SrtPromptDeclined")); |
| break; |
| case feedback_private::SRT_PROMPT_RESULT_CLOSED: |
| base::RecordAction(base::UserMetricsAction("Feedback.SrtPromptClosed")); |
| break; |
| default: |
| return RespondNow(Error("Invalid arugment.")); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| } // namespace extensions |