blob: 962faca7cd5e6847ee006e0f95b8e4e30e53a5c6 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/feedback/show_feedback_page.h"
#include <string>
#include "base/json/json_writer.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/escape.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "chrome/browser/feedback/feedback_dialog_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/webui/feedback/feedback_dialog.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/consent_level.h"
#include "extensions/browser/api/feedback_private/feedback_private_api.h"
#include "third_party/re2/src/re2/re2.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/webui/os_feedback_ui/url_constants.h"
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "base/functional/bind.h"
#include "base/strings/strcat.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "google_apis/gaia/gaia_auth_util.h"
#endif
namespace feedback_private = extensions::api::feedback_private;
namespace chrome {
namespace {
#if BUILDFLAG(IS_CHROMEOS)
constexpr char kExtraDiagnosticsQueryParam[] = "extra_diagnostics";
constexpr char kDescriptionTemplateQueryParam[] = "description_template";
constexpr char kDescriptionPlaceholderQueryParam[] =
"description_placeholder_text";
constexpr char kFromAssistantQueryParam[] = "from_assistant";
constexpr char kSettingsSearchDoNotRecordMetricsQueryParam[] =
"settings_search_do_not_record_metrics";
constexpr char kCategoryTagParam[] = "category_tag";
constexpr char kPageURLParam[] = "page_url";
constexpr char kQueryParamSeparator[] = "&";
constexpr char kQueryParamKeyValueSeparator[] = "=";
constexpr char kFromAssistantQueryParamValue[] = "true";
constexpr char kSettingsSearchDoNotRecordMetricsQueryParamValue[] = "true";
constexpr char kFromAutofillQueryParam[] = "from_autofill";
constexpr char kFromAutofillParamValue[] = "true";
constexpr char kAutofillMetadataQueryParam[] = "autofill_metadata";
// Concat query parameter with escaped value.
std::string StrCatQueryParam(std::string_view query_param,
std::string_view value) {
return base::StrCat({query_param, kQueryParamKeyValueSeparator,
base::EscapeQueryParamValue(value, /*use_plus=*/false)});
}
// Returns URL for OS Feedback with additional data passed as query parameters.
GURL BuildFeedbackUrl(const std::string& extra_diagnostics,
const std::string& description_template,
const std::string& description_placeholder_text,
const std::string& category_tag,
const GURL& page_url,
feedback::FeedbackSource source,
base::Value::Dict autofill_metadata) {
std::vector<std::string> query_params;
if (!extra_diagnostics.empty()) {
query_params.emplace_back(
StrCatQueryParam(kExtraDiagnosticsQueryParam, extra_diagnostics));
}
if (!description_template.empty()) {
query_params.emplace_back(
StrCatQueryParam(kDescriptionTemplateQueryParam, description_template));
}
if (!description_placeholder_text.empty()) {
query_params.emplace_back(StrCatQueryParam(
kDescriptionPlaceholderQueryParam, description_placeholder_text));
}
if (!category_tag.empty()) {
query_params.emplace_back(
StrCatQueryParam(kCategoryTagParam, category_tag));
}
if (!page_url.is_empty()) {
query_params.emplace_back(StrCatQueryParam(kPageURLParam, page_url.spec()));
}
if (source == feedback::kFeedbackSourceAssistant) {
query_params.emplace_back(StrCatQueryParam(kFromAssistantQueryParam,
kFromAssistantQueryParamValue));
}
if (source == feedback::kFeedbackSourceOsSettingsSearch) {
// If the user has queried for "fingerprint" in Settings app, we want to
// check the 'Send system & app info and metrics' checkbox in the feedback
// dialog.
if (description_template.empty() ||
!re2::RE2::PartialMatch(description_template, "fingerprint")) {
query_params.emplace_back(
StrCatQueryParam(kSettingsSearchDoNotRecordMetricsQueryParam,
kSettingsSearchDoNotRecordMetricsQueryParamValue));
}
}
if (source == feedback::kFeedbackSourceAutofillContextMenu) {
query_params.emplace_back(
StrCatQueryParam(kFromAutofillQueryParam, kFromAutofillParamValue));
std::string autofill_metadata_json =
base::WriteJson(autofill_metadata).value_or("");
query_params.emplace_back(
StrCatQueryParam(kAutofillMetadataQueryParam, autofill_metadata_json));
}
// Use default URL if no extra parameters to be added.
if (query_params.empty()) {
return GURL(ash::kChromeUIOSFeedbackUrl);
}
return GURL(
base::StrCat({ash::kChromeUIOSFeedbackUrl, "/?",
base::JoinString(query_params, kQueryParamSeparator)}));
}
// Returns whether the user has an internal Google account (e.g. @google.com).
bool IsGoogleInternalAccount(Profile* profile) {
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
if (!identity_manager) // Non-GAIA account, e.g. guest mode.
return false;
// Browser sync consent is not required to use feedback.
CoreAccountInfo account_info =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
return gaia::IsGoogleInternalAccountEmail(account_info.email);
}
// Returns if the feedback page is considered to be triggered from user
// interaction.
bool IsFromUserInteraction(feedback::FeedbackSource source) {
switch (source) {
case feedback::kFeedbackSourceArcApp:
case feedback::kFeedbackSourceAsh:
case feedback::kFeedbackSourceAssistant:
case feedback::kFeedbackSourceAutofillContextMenu:
case feedback::kFeedbackSourceBrowserCommand:
case feedback::kFeedbackSourceConnectivityDiagnostics:
case feedback::kFeedbackSourceDesktopTabGroups:
case feedback::kFeedbackSourceCookieControls:
case feedback::kFeedbackSourceNetworkHealthPage:
case feedback::kFeedbackSourceMdSettingsAboutPage:
case feedback::kFeedbackSourceOsSettingsSearch:
case feedback::kFeedbackSourcePriceInsights:
case feedback::kFeedbackSourceQuickAnswers:
case feedback::kFeedbackSourceQuickOffice:
case feedback::kFeedbackSourceSettingsPerformancePage:
return true;
default:
return false;
}
}
#endif // BUILDFLAG(IS_CHROMEOS)
feedback_private::FeedbackFlow GetFeedbackFlowFromSource(
feedback::FeedbackSource source) {
switch (source) {
case feedback::kFeedbackSourceSadTabPage:
return feedback_private::FeedbackFlow::kSadTabCrash;
case feedback::kFeedbackSourceAutofillContextMenu:
return feedback_private::FeedbackFlow::kGoogleInternal;
case feedback::kFeedbackSourceAI:
return feedback_private::FeedbackFlow::kAi;
default:
return feedback_private::FeedbackFlow::kRegular;
}
}
// Calls feedback private api to show Feedback ui.
void RequestFeedbackFlow(const GURL& page_url,
Profile* profile,
feedback::FeedbackSource source,
const std::string& description_template,
const std::string& description_placeholder_text,
const std::string& category_tag,
const std::string& extra_diagnostics,
base::Value::Dict autofill_metadata,
base::Value::Dict ai_metadata) {
feedback_private::FeedbackFlow flow = GetFeedbackFlowFromSource(source);
bool include_bluetooth_logs = false;
bool show_questionnaire = false;
#if BUILDFLAG(IS_CHROMEOS)
// TODO(crbug.com/40941303) Support ChromeOS feedback dialog for
// `kFeedbackSourceAI`.
if (source != feedback::kFeedbackSourceAI) {
if (IsGoogleInternalAccount(profile)) {
flow = feedback_private::FeedbackFlow::kGoogleInternal;
include_bluetooth_logs = IsFromUserInteraction(source);
show_questionnaire = IsFromUserInteraction(source);
}
if (!chromeos::IsKioskSession()) {
// TODO(crbug.com/40253237): Include autofill metadata into CrOS new
// feedback tool.
ash::SystemAppLaunchParams params;
params.url = BuildFeedbackUrl(
extra_diagnostics, description_template, description_placeholder_text,
category_tag, page_url, source, std::move(autofill_metadata));
ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::OS_FEEDBACK,
std::move(params));
return;
}
}
#endif // BUILDFLAG(IS_CHROMEOS)
extensions::FeedbackPrivateAPI* api =
extensions::FeedbackPrivateAPI::GetFactoryInstance()->Get(profile);
auto info = api->CreateFeedbackInfo(
description_template, description_placeholder_text, category_tag,
extra_diagnostics, page_url, flow,
source == feedback::kFeedbackSourceAssistant, include_bluetooth_logs,
show_questionnaire, source == feedback::kFeedbackSourceChromeLabs,
source == feedback::kFeedbackSourceAutofillContextMenu, autofill_metadata,
ai_metadata);
FeedbackDialog::CreateOrShow(profile, *info);
}
} // namespace
void ShowFeedbackPage(BrowserWindowInterface* bwi,
feedback::FeedbackSource source,
const std::string& description_template,
const std::string& description_placeholder_text,
const std::string& category_tag,
const std::string& extra_diagnostics,
base::Value::Dict autofill_metadata,
base::Value::Dict ai_metadata) {
GURL page_url;
if (bwi) {
page_url = GetTargetTabUrl(bwi, bwi->GetTabStripModel()->active_index());
}
Profile* profile = GetFeedbackProfile(bwi);
ShowFeedbackPage(page_url, profile, source, description_template,
description_placeholder_text, category_tag,
extra_diagnostics, std::move(autofill_metadata),
std::move(ai_metadata));
}
void ShowFeedbackPage(const GURL& page_url,
Profile* profile,
feedback::FeedbackSource source,
const std::string& description_template,
const std::string& description_placeholder_text,
const std::string& category_tag,
const std::string& extra_diagnostics,
base::Value::Dict autofill_metadata,
base::Value::Dict ai_metadata) {
if (!profile) {
LOG(ERROR) << "Cannot invoke feedback: No profile found!";
return;
}
if (!profile->GetPrefs()->GetBoolean(prefs::kUserFeedbackAllowed)) {
return;
}
// Record an UMA histogram to know the most frequent feedback request source.
UMA_HISTOGRAM_ENUMERATION("Feedback.RequestSource", source,
feedback::kFeedbackSourceCount);
// Show feedback dialog using feedback extension API.
RequestFeedbackFlow(page_url, profile, source, description_template,
description_placeholder_text, category_tag,
extra_diagnostics, std::move(autofill_metadata),
std::move(ai_metadata));
}
} // namespace chrome