| // 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 <string> |
| |
| #include "ash/constants/ash_features.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/escape.h" |
| #include "base/strings/string_util.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/feedback/feedback_dialog_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/chrome_pages.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" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #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/crosapi/browser_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 "components/signin/public/identity_manager/identity_manager.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| #include "chromeos/crosapi/mojom/feedback.mojom.h" |
| #include "chromeos/lacros/lacros_service.h" |
| #endif |
| |
| namespace feedback_private = extensions::api::feedback_private; |
| |
| namespace chrome { |
| |
| namespace { |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| constexpr char kExtraDiagnosticsQueryParam[] = "extra_diagnostics"; |
| constexpr char kDescriptionTemplateQueryParam[] = "description_template"; |
| constexpr char kDescriptionPlaceholderQueryParam[] = |
| "description_placeholder_text"; |
| constexpr char kFromAssistantQueryParam[] = "from_assistant"; |
| constexpr char kSettingsSearchFeedbackQueryParam[] = "from_settings_search"; |
| constexpr char kCategoryTagParam[] = "category_tag"; |
| constexpr char kPageURLParam[] = "page_url"; |
| constexpr char kQueryParamSeparator[] = "&"; |
| constexpr char kQueryParamKeyValueSeparator[] = "="; |
| constexpr char kFromAssistantQueryParamValue[] = "true"; |
| constexpr char kSettingsSearchFeedbackQueryParamValue[] = "true"; |
| |
| // Concat query parameter with escaped value. |
| std::string StrCatQueryParam(const std::string query_param, |
| const std::string 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, |
| FeedbackSource source) { |
| 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 == kFeedbackSourceAssistant) { |
| query_params.emplace_back(StrCatQueryParam(kFromAssistantQueryParam, |
| kFromAssistantQueryParamValue)); |
| } |
| |
| if (source == kFeedbackSourceOsSettingsSearch) { |
| query_params.emplace_back( |
| StrCatQueryParam(kSettingsSearchFeedbackQueryParam, |
| kSettingsSearchFeedbackQueryParamValue)); |
| } |
| |
| // 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(FeedbackSource source) { |
| switch (source) { |
| case kFeedbackSourceArcApp: |
| case kFeedbackSourceAsh: |
| case kFeedbackSourceAssistant: |
| case kFeedbackSourceAutofillContextMenu: |
| case kFeedbackSourceBrowserCommand: |
| case kFeedbackSourceConnectivityDiagnostics: |
| case kFeedbackSourceDesktopTabGroups: |
| case kFeedbackSourceNetworkHealthPage: |
| case kFeedbackSourceMdSettingsAboutPage: |
| case kFeedbackSourceOldSettingsAboutPage: |
| case kFeedbackSourceOsSettingsSearch: |
| case kFeedbackSourceQuickAnswers: |
| case kFeedbackSourceQuickOffice: |
| case kFeedbackSourceSettingsPerformancePage: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| void OnLacrosActiveTabUrlFeteched( |
| Profile* profile, |
| chrome::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, |
| const absl::optional<GURL>& active_tab_url) { |
| GURL page_url; |
| if (active_tab_url) |
| page_url = *active_tab_url; |
| chrome::ShowFeedbackPage(page_url, profile, source, description_template, |
| description_placeholder_text, category_tag, |
| extra_diagnostics, std::move(autofill_metadata)); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| #if !BUILDFLAG(IS_CHROMEOS_LACROS) |
| |
| feedback_private::FeedbackFlow GetFeedbackFlowFromSource( |
| FeedbackSource source) { |
| switch (source) { |
| case kFeedbackSourceSadTabPage: |
| return feedback_private::FeedbackFlow::FEEDBACK_FLOW_SADTABCRASH; |
| case kFeedbackSourceAutofillContextMenu: |
| return feedback_private::FeedbackFlow::FEEDBACK_FLOW_GOOGLEINTERNAL; |
| default: |
| return feedback_private::FeedbackFlow::FEEDBACK_FLOW_REGULAR; |
| } |
| } |
| |
| // Calls feedback private api to show Feedback ui. |
| void RequestFeedbackFlow(const GURL& page_url, |
| Profile* profile, |
| 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) { |
| feedback_private::FeedbackFlow flow = GetFeedbackFlowFromSource(source); |
| bool include_bluetooth_logs = false; |
| bool show_questionnaire = false; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (IsGoogleInternalAccount(profile)) { |
| flow = feedback_private::FeedbackFlow::FEEDBACK_FLOW_GOOGLEINTERNAL; |
| include_bluetooth_logs = IsFromUserInteraction(source); |
| show_questionnaire = IsFromUserInteraction(source); |
| } |
| if (base::FeatureList::IsEnabled(ash::features::kOsFeedback)) { |
| // TODO(crbug.com/1407646): 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); |
| |
| ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::OS_FEEDBACK, |
| std::move(params)); |
| return; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| 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 == kFeedbackSourceAssistant, |
| include_bluetooth_logs, show_questionnaire, |
| source == kFeedbackSourceChromeLabs || |
| source == kFeedbackSourceKaleidoscope, |
| source == kFeedbackSourceAutofillContextMenu, autofill_metadata); |
| |
| FeedbackDialog::CreateOrShow(profile, *info); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_LACROS) |
| |
| } // namespace |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| namespace internal { |
| // Requests to show Feedback ui remotely in ash via crosapi mojo call. |
| void ShowFeedbackPageLacros(const GURL& page_url, |
| 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); |
| } // namespace internal |
| #endif |
| |
| void ShowFeedbackPage(const Browser* browser, |
| 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) { |
| GURL page_url; |
| if (browser) { |
| page_url = GetTargetTabUrl(browser->session_id(), |
| browser->tab_strip_model()->active_index()); |
| } |
| |
| Profile* profile = GetFeedbackProfile(browser); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // When users invoke the feedback dialog by pressing alt-shift-i without |
| // an active ash window, we need to check if there is an active lacros window |
| // and show its Url in the feedback dialog if there is any. |
| if (!browser && crosapi::BrowserManager::Get()->IsRunning() && |
| crosapi::BrowserManager::Get()->GetActiveTabUrlSupported()) { |
| crosapi::BrowserManager::Get()->GetActiveTabUrl(base::BindOnce( |
| &OnLacrosActiveTabUrlFeteched, profile, source, description_template, |
| description_placeholder_text, category_tag, extra_diagnostics, |
| std::move(autofill_metadata))); |
| } else { |
| ShowFeedbackPage(page_url, profile, source, description_template, |
| description_placeholder_text, category_tag, |
| extra_diagnostics, std::move(autofill_metadata)); |
| } |
| #else |
| ShowFeedbackPage(page_url, profile, source, description_template, |
| description_placeholder_text, category_tag, |
| extra_diagnostics, std::move(autofill_metadata)); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| void ShowFeedbackPage(const GURL& page_url, |
| Profile* profile, |
| 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) { |
| 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, |
| kFeedbackSourceCount); |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| // After M87 beta, Feedback API should be supported in crosapi with all ash |
| // versions on chromeOS platform where lacros is deployed. |
| DCHECK( |
| chromeos::LacrosService::Get()->IsAvailable<crosapi::mojom::Feedback>()); |
| // Send request to ash via crosapi mojo to show Feedback ui from ash. |
| internal::ShowFeedbackPageLacros( |
| page_url, source, description_template, description_placeholder_text, |
| category_tag, extra_diagnostics, std::move(autofill_metadata)); |
| #else |
| // 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)); |
| #endif // BUILDFLAG(IS_CHROMEOS_LACROS) |
| } |
| |
| } // namespace chrome |