Revert "Refactor HaTS service to manage Clank HaTS surveys"
This reverts commit 0076d35594bb0c3e0a3f46d2242b6d597ee59a37.
Reason for revert: Related to failure: https://ci.chromium.org/ui/b/8764172966563883329
Original change's description:
> Refactor HaTS service to manage Clank HaTS surveys
>
> Refactors the service and integrates the permission prompt survey
> trigger
>
> Bug: 1498980, 1495484
> Change-Id: I92853f0261bd81ad6e5b296c0c58a819bbf6b8f2
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5004674
> Reviewed-by: David Roger <[email protected]>
> Commit-Queue: Florian Jacky <[email protected]>
> Code-Coverage: [email protected] <[email protected]>
> Reviewed-by: Wenyu Fu <[email protected]>
> Reviewed-by: Dominic Battre <[email protected]>
> Auto-Submit: Florian Jacky <[email protected]>
> Cr-Commit-Position: refs/heads/main@{#1226063}
Bug: 1498980, 1495484
Change-Id: I936c40b8bab85c99e413a418af5fb4e15fe77727
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5040565
Bot-Commit: Rubber Stamper <[email protected]>
Commit-Queue: Yifan Luo <[email protected]>
Owners-Override: Yifan Luo <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1226091}
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 531b5cf..d43a0317c 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -47,7 +47,6 @@
#include "components/paint_preview/features/features.h"
#include "components/password_manager/core/browser/features/password_features.h"
#include "components/password_manager/core/common/password_manager_features.h"
-#include "components/permissions/features.h"
#include "components/policy/core/common/features.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/query_tiles/switches.h"
@@ -113,7 +112,6 @@
&features::kGenericSensorExtraClasses,
&features::kBackForwardCache,
&features::kBackForwardTransitions,
- &features::kBlockMidiByDefault,
&features::kMetricsSettingsAndroid,
&features::kNetworkServiceInProcess,
&shared_highlighting::kPreemptiveLinkToTextGeneration,
@@ -378,7 +376,7 @@
&password_manager::features::kRecoverFromNeverSaveAndroid,
&password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning,
- &permissions::features::kPermissionsPromptSurvey,
+ &features::kBlockMidiByDefault,
&privacy_sandbox::kPrivacySandboxFirstPartySetsUI,
&privacy_sandbox::kPrivacySandboxSettings3,
&privacy_sandbox::kPrivacySandboxSettings4,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 5b80db0..2f06dbd 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -359,7 +359,6 @@
"PasskeyManagementUsingAccountSettingsAndroid";
public static final String PASSWORD_GENERATION_BOTTOM_SHEET = "PasswordGenerationBottomSheet";
public static final String PASSWORD_EDIT_DIALOG_WITH_DETAILS = "PasswordEditDialogWithDetails";
- public static final String PERMISSION_PROMPT_SURVEY = "PermissionPromptSurvey";
public static final String PORTALS = "Portals";
public static final String PORTALS_CROSS_ORIGIN = "PortalsCrossOrigin";
public static final String PREEMPTIVE_LINK_TO_TEXT_GENERATION =
diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index c26bff9..dff5883 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -34,9 +34,6 @@
#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
#include "chrome/browser/subresource_filter/subresource_filter_profile_context_factory.h"
#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/browser/ui/hats/hats_service_factory.h"
-#include "chrome/browser/ui/hats/survey_config.h"
#include "chrome/browser/usb/usb_chooser_context.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/common/channel_info.h"
@@ -47,7 +44,6 @@
#include "components/permissions/constants.h"
#include "components/permissions/contexts/bluetooth_chooser_context.h"
#include "components/permissions/features.h"
-#include "components/permissions/permission_hats_trigger_helper.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
@@ -77,7 +73,10 @@
#include "components/permissions/permission_request_manager.h"
#else
#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/permission_bubble/permission_prompt.h"
+#include "components/permissions/permission_hats_trigger_helper.h"
#include "components/vector_icons/vector_icons.h"
#endif
@@ -262,10 +261,11 @@
return PermissionsClient::GetOverrideIconId(request_type);
}
+#if !BUILDFLAG(IS_ANDROID)
// Triggers the prompt HaTS survey if enabled by field trials for this
// combination of prompt parameters.
void ChromePermissionsClient::TriggerPromptHatsSurveyIfEnabled(
- content::WebContents* web_contents,
+ content::BrowserContext* context,
permissions::RequestType request_type,
absl::optional<permissions::PermissionAction> action,
permissions::PermissionPromptDisposition prompt_disposition,
@@ -275,8 +275,7 @@
bool is_post_prompt,
const GURL& gurl,
base::OnceCallback<void()> hats_shown_callback) {
- Profile* profile =
- Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ Profile* profile = Profile::FromBrowserContext(context);
absl::optional<GURL> recorded_gurl =
profile->GetPrefs()->GetBoolean(
unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled)
@@ -316,13 +315,12 @@
auto survey_data = permissions::PermissionHatsTriggerHelper::
SurveyProductSpecificData::PopulateFrom(prompt_parameters);
- hats_service->LaunchSurveyForWebContents(
- trigger_and_probability->first, web_contents,
- survey_data.survey_bits_data, survey_data.survey_string_data,
- std::move(hats_shown_callback), base::DoNothing());
+ hats_service->LaunchSurvey(trigger_and_probability->first,
+ std::move(hats_shown_callback), base::DoNothing(),
+ survey_data.survey_bits_data,
+ survey_data.survey_string_data);
}
-#if !BUILDFLAG(IS_ANDROID)
permissions::PermissionIgnoredReason
ChromePermissionsClient::DetermineIgnoreReason(
content::WebContents* web_contents) {
@@ -386,6 +384,7 @@
}
}
+#if !BUILDFLAG(IS_ANDROID)
auto content_setting_type = RequestTypeToContentSettingsType(request_type);
if (content_setting_type.has_value()) {
permissions::PermissionHatsTriggerHelper::
@@ -394,10 +393,12 @@
}
TriggerPromptHatsSurveyIfEnabled(
- web_contents, request_type, absl::make_optional(action),
- prompt_disposition, prompt_disposition_reason, gesture_type,
+ web_contents->GetBrowserContext(), request_type,
+ absl::make_optional(action), prompt_disposition,
+ prompt_disposition_reason, gesture_type,
absl::make_optional(prompt_display_duration), true,
web_contents->GetLastCommittedURL(), base::DoNothing());
+#endif // !BUILDFLAG(IS_ANDROID)
}
absl::optional<bool>
diff --git a/chrome/browser/permissions/chrome_permissions_client.h b/chrome/browser/permissions/chrome_permissions_client.h
index 1c6115e..57c7368d 100644
--- a/chrome/browser/permissions/chrome_permissions_client.h
+++ b/chrome/browser/permissions/chrome_permissions_client.h
@@ -58,8 +58,9 @@
CreatePermissionUiSelectors(
content::BrowserContext* browser_context) override;
+#if !BUILDFLAG(IS_ANDROID)
void TriggerPromptHatsSurveyIfEnabled(
- content::WebContents* web_contents,
+ content::BrowserContext* context,
permissions::RequestType request_type,
absl::optional<permissions::PermissionAction> action,
permissions::PermissionPromptDisposition prompt_disposition,
@@ -70,7 +71,6 @@
const GURL& gurl,
base::OnceCallback<void()> hats_shown_callback_) override;
-#if !BUILDFLAG(IS_ANDROID)
permissions::PermissionIgnoredReason DetermineIgnoreReason(
content::WebContents* web_contents) override;
#endif
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 2c72490..82fc470 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -76,7 +76,7 @@
#include "chrome/browser/tpcd/experiment/tpcd_pref_names.h"
#include "chrome/browser/tracing/chrome_tracing_delegate.h"
#include "chrome/browser/ui/browser_ui_prefs.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
+#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/network_profile_bubble.h"
#include "chrome/browser/ui/prefs/prefs_tab_helper.h"
#include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
@@ -138,7 +138,6 @@
#include "components/password_manager/core/browser/password_manager.h"
#include "components/payments/core/payment_prefs.h"
#include "components/performance_manager/public/user_tuning/prefs.h"
-#include "components/permissions/permission_hats_trigger_helper.h"
#include "components/permissions/pref_names.h"
#include "components/plus_addresses/plus_address_prefs.h"
#include "components/policy/core/browser/browser_policy_connector.h"
@@ -307,6 +306,7 @@
#include "components/live_caption/live_caption_controller.h"
#include "components/live_caption/live_translate_controller.h"
#include "components/ntp_tiles/custom_links_manager_impl.h"
+#include "components/permissions/permission_hats_trigger_helper.h"
#include "components/user_notes/user_notes_prefs.h"
#endif // BUILDFLAG(IS_ANDROID)
@@ -1695,7 +1695,6 @@
dom_distiller::DistilledPagePrefs::RegisterProfilePrefs(registry);
dom_distiller::RegisterProfilePrefs(registry);
DownloadPrefs::RegisterProfilePrefs(registry);
- permissions::PermissionHatsTriggerHelper::RegisterProfilePrefs(registry);
history_clusters::prefs::RegisterProfilePrefs(registry);
HostContentSettingsMap::RegisterProfilePrefs(registry);
image_fetcher::ImageCache::RegisterProfilePrefs(registry);
@@ -1848,7 +1847,7 @@
extensions::TabsCaptureVisibleTabFunction::RegisterProfilePrefs(registry);
first_run::RegisterProfilePrefs(registry);
gcm::RegisterProfilePrefs(registry);
- HatsServiceDesktop::RegisterProfilePrefs(registry);
+ HatsService::RegisterProfilePrefs(registry);
NtpCustomBackgroundService::RegisterProfilePrefs(registry);
media_router::RegisterAccessCodeProfilePrefs(registry);
media_router::RegisterProfilePrefs(registry);
@@ -2057,6 +2056,7 @@
registry->RegisterIntegerPref(prefs::kHighEfficiencyChipExpandedCount, 0);
registry->RegisterTimePref(prefs::kLastHighEfficiencyChipExpandedTimestamp,
base::Time());
+ permissions::PermissionHatsTriggerHelper::RegisterProfilePrefs(registry);
#endif
#if BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 839daf21..98241e2 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -187,7 +187,6 @@
#include "chrome/browser/translate/translate_ranker_factory.h"
#include "chrome/browser/ui/cookie_controls/cookie_controls_service_factory.h"
#include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
-#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.h"
#include "chrome/browser/ui/prefs/prefs_tab_helper.h"
#include "chrome/browser/ui/tabs/pinned_tab_service_factory.h"
@@ -948,7 +947,6 @@
#endif
ProfilePasswordStoreFactory::GetInstance();
payments::CanMakePaymentQueryFactory::GetInstance();
- HatsServiceFactory::GetInstance();
#if !BUILDFLAG(IS_ANDROID)
payments::PaymentRequestDisplayManagerFactory::GetInstance();
performance_manager::SiteDataCacheFacadeFactory::GetInstance();
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a1bfc289..97c456a 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -114,10 +114,6 @@
"find_bar/find_bar_state.h",
"find_bar/find_bar_state_factory.cc",
"find_bar/find_bar_state_factory.h",
- "hats/hats_service.cc",
- "hats/hats_service.h",
- "hats/hats_service_factory.cc",
- "hats/hats_service_factory.h",
"hats/survey_config.cc",
"hats/survey_config.h",
"idle_bubble.h",
@@ -832,8 +828,6 @@
"android/fast_checkout/fast_checkout_view_impl.h",
"android/fast_checkout/ui_view_android_utils.cc",
"android/fast_checkout/ui_view_android_utils.h",
- "android/hats/hats_service_android.cc",
- "android/hats/hats_service_android.h",
"android/hats/survey_client_android.cc",
"android/hats/survey_client_android.h",
"android/hats/survey_config_android.cc",
@@ -1222,8 +1216,10 @@
"global_media_controls/supplemental_device_picker_producer.h",
"hats/hats_helper.cc",
"hats/hats_helper.h",
- "hats/hats_service_desktop.cc",
- "hats/hats_service_desktop.h",
+ "hats/hats_service.cc",
+ "hats/hats_service.h",
+ "hats/hats_service_factory.cc",
+ "hats/hats_service_factory.h",
"hats/trust_safety_sentiment_service.cc",
"hats/trust_safety_sentiment_service.h",
"hats/trust_safety_sentiment_service_factory.cc",
diff --git a/chrome/browser/ui/android/hats/hats_service_android.cc b/chrome/browser/ui/android/hats/hats_service_android.cc
deleted file mode 100644
index 459c182..0000000
--- a/chrome/browser/ui/android/hats/hats_service_android.cc
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright 2023 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/ui/android/hats/hats_service_android.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/functional/bind.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/notreached.h"
-#include "base/ranges/algorithm.h"
-#include "base/task/sequenced_task_runner.h"
-#include "chrome/browser/android/resource_mapper.h"
-#include "chrome/browser/prefs/incognito_mode_prefs.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profiles_state.h"
-#include "chrome/browser/sessions/exit_type_service.h"
-#include "chrome/browser/ui/android/hats/survey_client_android.h"
-#include "chrome/browser/ui/android/hats/survey_ui_delegate_android.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/messages/android/message_wrapper.h"
-#include "components/resources/android/theme_resources.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/base/l10n/l10n_util.h"
-
-constexpr char kHatsShouldShowSurveyReasonAndroidHistogram[] =
- "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReasonAndroid";
-
-HatsServiceAndroid::DelayedSurveyTask::DelayedSurveyTask(
- HatsServiceAndroid* hats_service,
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback)
- : web_contents_(web_contents),
- hats_service_(hats_service),
- trigger_(trigger),
- product_specific_bits_data_(product_specific_bits_data),
- product_specific_string_data_(product_specific_string_data),
- success_callback_(std::move(success_callback)),
- failure_callback_(std::move(failure_callback)) {}
-
-HatsServiceAndroid::DelayedSurveyTask::~DelayedSurveyTask() = default;
-
-void HatsServiceAndroid::DelayedSurveyTask::Launch() {
- CHECK(web_contents());
- if (!web_contents() ||
- web_contents()->GetVisibility() != content::Visibility::VISIBLE) {
- return;
- }
-
- message_ = std::make_unique<messages::MessageWrapper>(
- messages::MessageIdentifier::CHROME_SURVEY, std::move(success_callback_),
- base::BindOnce(&HatsServiceAndroid::DelayedSurveyTask::DismissCallback,
- weak_ptr_factory_.GetWeakPtr()));
-
- hats::SurveyUiDelegateAndroid delegate(
- message_.get(), web_contents()->GetTopLevelNativeWindow());
-
- // Create survey client with delegate.
- hats::SurveyClientAndroid survey_client(trigger_, &delegate,
- hats_service_->profile());
- survey_client.LaunchSurvey(web_contents()->GetTopLevelNativeWindow(),
- product_specific_bits_data_,
- product_specific_string_data_);
-}
-
-void HatsServiceAndroid::DelayedSurveyTask::DismissCallback(
- messages::DismissReason dismiss_reason) {
- if (dismiss_reason != messages::DismissReason::PRIMARY_ACTION &&
- !failure_callback_.is_null()) {
- std::move(failure_callback_).Run();
- }
-
- ShouldShowSurveyReasonsAndroid reason;
- switch (dismiss_reason) {
- case messages::DismissReason::UNKNOWN:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidUnknown;
- break;
- case messages::DismissReason::PRIMARY_ACTION:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidAccepted;
- break;
- case messages::DismissReason::SECONDARY_ACTION:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidSecondaryAction;
- break;
- case messages::DismissReason::TIMER:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidExpired;
- break;
- case messages::DismissReason::GESTURE:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidDismissedByGesture;
- break;
- case messages::DismissReason::TAB_SWITCHED:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidTabSwitched;
- break;
- case messages::DismissReason::TAB_DESTROYED:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidTabDestroyed;
- break;
- case messages::DismissReason::ACTIVITY_DESTROYED:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidActivityDestroyed;
- break;
- case messages::DismissReason::SCOPE_DESTROYED:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidScopeDestroyed;
- break;
- case messages::DismissReason::DISMISSED_BY_FEATURE:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidDismissedByFeature;
- break;
- case messages::DismissReason::COUNT:
- reason = ShouldShowSurveyReasonsAndroid::kAndroidUnknown;
- NOTREACHED();
- }
- UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonAndroidHistogram,
- reason);
- hats_service_->RemoveTask(*this);
-}
-
-base::WeakPtr<HatsServiceAndroid::DelayedSurveyTask>
-HatsServiceAndroid::DelayedSurveyTask::GetWeakPtr() {
- return weak_ptr_factory_.GetWeakPtr();
-}
-
-HatsServiceAndroid::HatsServiceAndroid(Profile* profile)
- : HatsService(profile) {}
-
-HatsServiceAndroid::~HatsServiceAndroid() = default;
-
-void HatsServiceAndroid::LaunchSurvey(
- const std::string& trigger,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data) {
- NOTIMPLEMENTED();
-}
-
-void HatsServiceAndroid::LaunchSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback) {
- // By using a delayed survey with a delay of 0, we can centralize the object
- // lifecycle management duties for native clank survey triggers.
- LaunchDelayedSurveyForWebContents(
- trigger, web_contents, 0, product_specific_bits_data,
- product_specific_string_data, false, std::move(success_callback),
- std::move(failure_callback));
-}
-
-bool HatsServiceAndroid::LaunchDelayedSurvey(
- const std::string& trigger,
- int timeout_ms,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data) {
- NOTIMPLEMENTED();
- return false;
-}
-
-bool HatsServiceAndroid::LaunchDelayedSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- int timeout_ms,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- bool require_same_origin,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback) {
- CHECK(web_contents);
- CHECK(!require_same_origin); // Currently not supported on Android
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- if (survey_configs_by_triggers_.find(trigger) ==
- survey_configs_by_triggers_.end()) {
- // Survey configuration is not available.
- if (!failure_callback.is_null()) {
- std::move(failure_callback).Run();
- }
- return false;
- }
- auto result = pending_tasks_.emplace(
- this, trigger, web_contents, product_specific_bits_data,
- product_specific_string_data, std::move(success_callback),
- std::move(failure_callback));
- if (!result.second) {
- return false;
- }
- auto success =
- base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&HatsServiceAndroid::DelayedSurveyTask::Launch,
- const_cast<HatsServiceAndroid::DelayedSurveyTask&>(
- *(result.first))
- .GetWeakPtr()),
- base::Milliseconds(timeout_ms));
- if (!success) {
- pending_tasks_.erase(result.first);
- }
- return success;
-}
-
-bool HatsServiceAndroid::CanShowAnySurvey(bool user_prompted) const {
- NOTIMPLEMENTED(); // Survey throttling happens on the clank side
- return false;
-}
-
-bool HatsServiceAndroid::CanShowSurvey(const std::string& trigger) const {
- NOTIMPLEMENTED(); // Survey throttling happens on the clank side
- return false;
-}
-
-void HatsServiceAndroid::RecordSurveyAsShown(std::string trigger_id) {
- // Record the trigger associated with the trigger_id. This is recorded
- // instead of the trigger ID itself, as the ID is specific to individual
- // survey versions. There should be a cooldown before a user is prompted to
- // take a survey from the same trigger, regardless of whether the survey was
- // updated.
- auto trigger_survey_config =
- base::ranges::find(survey_configs_by_triggers_, trigger_id,
- [](const SurveyConfigs::value_type& pair) {
- return pair.second.trigger_id;
- });
-
- DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
- std::string trigger = trigger_survey_config->first;
-
- UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonAndroidHistogram,
- ShouldShowSurveyReasonsAndroid::kYes);
-}
-
-void HatsServiceAndroid::RemoveTask(const DelayedSurveyTask& task) {
- pending_tasks_.erase(task);
-}
diff --git a/chrome/browser/ui/android/hats/hats_service_android.h b/chrome/browser/ui/android/hats/hats_service_android.h
deleted file mode 100644
index 1a8512cd..0000000
--- a/chrome/browser/ui/android/hats/hats_service_android.h
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
-#define CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
-
-#include <memory>
-#include <set>
-#include <string>
-
-#include "base/functional/callback.h"
-#include "base/functional/callback_forward.h"
-#include "base/functional/callback_helpers.h"
-#include "base/gtest_prod_util.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "components/messages/android/message_enums.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_observer.h"
-
-namespace messages {
-class MessageWrapper;
-}
-
-// The name of the histogram which records if a survey was shown, or if not, the
-// reason why not.
-extern const char kHatsShouldShowSurveyReasonAndroidHistogram[];
-
-// This class provides the client side logic for determining if a
-// survey should be shown for any trigger based on input from a finch
-// configuration. It is created on a per profile basis.
-class HatsServiceAndroid : public HatsService {
- public:
- class DelayedSurveyTask {
- public:
- DelayedSurveyTask(HatsServiceAndroid* hats_service,
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback);
-
- // Not copyable or movable
- DelayedSurveyTask(const DelayedSurveyTask&) = delete;
- DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
-
- ~DelayedSurveyTask();
-
- // Asks |hats_service_| to launch the survey with id |trigger_| for tab
- // |web_contents_|.
- void Launch();
-
- void DismissCallback(messages::DismissReason reason);
-
- content::WebContents* web_contents() const { return web_contents_.get(); }
-
- // Returns a weak pointer to this object.
- base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
-
- bool operator<(const HatsServiceAndroid::DelayedSurveyTask& other) const {
- return trigger_ < other.trigger_ ? true
- : web_contents() < other.web_contents();
- }
-
- messages::MessageWrapper* GetMessageForTesting() { return message_.get(); }
-
- private:
- raw_ptr<content::WebContents> web_contents_;
- raw_ptr<HatsServiceAndroid> hats_service_;
-
- std::unique_ptr<messages::MessageWrapper> message_;
- std::string trigger_;
- SurveyBitsData product_specific_bits_data_;
- SurveyStringData product_specific_string_data_;
- base::OnceClosure success_callback_;
- base::OnceClosure failure_callback_;
- base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
- };
-
- // These values are persisted to logs. Entries should not be renumbered and
- // numeric values should never be reused.
- enum class ShouldShowSurveyReasonsAndroid {
- kYes = 0,
- kAndroidUnknown = 1, // Catch all for Android invitation dismissals.
- // Should be investigated if this regularly occurs.
- kAndroidAccepted = 2, // Invitation accepted
- kAndroidSecondaryAction =
- 3, // Not in use by the default survey implementation. May be used by
- // customized trigger implementations.
- kAndroidExpired = 4, // Survey invitation expired and was automatically
- // dismissed. Default timeout is 10s, see
- // `ChromeMessageAutodismissDurationProvider.java`
- kAndroidDismissedByGesture = 5, // Dismissed by swiping the dialog away
- kAndroidTabSwitched = 6,
- kAndroidTabDestroyed = 7,
- kAndroidActivityDestroyed = 8,
- kAndroidScopeDestroyed = 9,
- kAndroidDismissedByFeature =
- 10, // Another survey was already launched, leading to the current one
- // being aborted.
- kMaxValue = kAndroidDismissedByFeature,
- };
-
- explicit HatsServiceAndroid(Profile* profile);
-
- HatsServiceAndroid(const HatsServiceAndroid&) = delete;
- HatsServiceAndroid& operator=(const HatsServiceAndroid&) = delete;
-
- ~HatsServiceAndroid() override;
-
- void LaunchSurvey(
- const std::string& trigger,
- base::OnceClosure success_callback = base::DoNothing(),
- base::OnceClosure failure_callback = base::DoNothing(),
- const SurveyBitsData& product_specific_bits_data = {},
- const SurveyStringData& product_specific_string_data = {}) override;
-
- void LaunchSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- base::OnceClosure success_callback = base::DoNothing(),
- base::OnceClosure failure_callback = base::DoNothing()) override;
-
- bool LaunchDelayedSurvey(
- const std::string& trigger,
- int timeout_ms,
- const SurveyBitsData& product_specific_bits_data = {},
- const SurveyStringData& product_specific_string_data = {}) override;
-
- bool LaunchDelayedSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- int timeout_ms,
- const SurveyBitsData& product_specific_bits_data = {},
- const SurveyStringData& product_specific_string_data = {},
- bool require_same_origin = false,
- base::OnceClosure success_callback = base::DoNothing(),
- base::OnceClosure failure_callback = base::DoNothing()) override;
-
- // Currently not implemented
- bool CanShowAnySurvey(bool user_prompted) const override;
-
- // Currently not implemented
- bool CanShowSurvey(const std::string& trigger) const override;
-
- void RecordSurveyAsShown(std::string trigger_id) override;
-
- DelayedSurveyTask& GetFirstTaskForTesting() {
- return const_cast<DelayedSurveyTask&>(*pending_tasks_.begin());
- }
-
- protected:
- // Remove |task| from the set of |pending_tasks_|.
- void RemoveTask(const DelayedSurveyTask& task);
-
- private:
- friend class DelayedSurveyTask;
- FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
-
- std::set<DelayedSurveyTask> pending_tasks_;
- base::WeakPtrFactory<HatsServiceAndroid> weak_ptr_factory_{this};
-};
-
-#endif // CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
index 1204fc6f..c3aeed2 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
@@ -106,7 +106,7 @@
/**
* Rolls a random number to see if the user was eligible for the survey. The user will skip the
- * roll if: 1. User is a first time user; 2. User has performed the roll for the survey today;
+ * roll if: 1. User is a first time user; 2. User has performed the roll for the survey today;
* 3. Max number is not setup correctly.
*
* @return Whether the user is eligible (i.e. the random number rolled was 0).
diff --git a/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc b/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
index 1d4989a..16dc7df1 100644
--- a/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
+++ b/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
@@ -6,15 +6,18 @@
#include "base/functional/bind.h"
#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/android/hats/hats_service_android.h"
+#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/android/hats/survey_client_android.h"
+#include "chrome/browser/ui/android/hats/survey_ui_delegate_android.h"
#include "chrome/browser/ui/android/hats/test/test_survey_utils_bridge.h"
-#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/messages/android/message_dispatcher_bridge.h"
+#include "components/messages/android/message_wrapper.h"
#include "components/messages/android/test/messages_test_helper.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/android/window_android.h"
namespace hats {
namespace {
@@ -42,29 +45,6 @@
content::WaiterHelper waiter_helper_;
};
-
-class SurveyObserver {
- public:
- SurveyObserver() = default;
- void Accept() { accepted_ = true; }
-
- void Dismiss() { dismissed_ = true; }
-
- bool IsAccepted() { return accepted_; }
-
- bool IsDismissed() { return dismissed_; }
-
- base::WeakPtr<SurveyObserver> GetWeakPtr() {
- return weak_ptr_factory_.GetWeakPtr();
- }
-
- private:
- bool accepted_ = false;
- bool dismissed_ = false;
-
- base::WeakPtrFactory<SurveyObserver> weak_ptr_factory_{this};
-};
-
} // namespace
class SurveyClientAndroidBrowserTest : public AndroidBrowserTest {
@@ -97,39 +77,63 @@
return web_contents()->GetTopLevelNativeWindow();
}
- HatsServiceAndroid* GetHatsService() {
- HatsServiceAndroid* service =
- static_cast<HatsServiceAndroid*>(HatsServiceFactory::GetForProfile(
- Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
- true));
- return service;
+ messages::MessageWrapper* createMessage() {
+ message_ = std::make_unique<messages::MessageWrapper>(
+ messages::MessageIdentifier::TEST_MESSAGE,
+ base::BindOnce(&SurveyClientAndroidBrowserTest::MessageAccepted,
+ base::Unretained(this)),
+ base::BindOnce(&SurveyClientAndroidBrowserTest::MessageDeclined,
+ base::Unretained(this)));
+ return message_.get();
}
+ bool message_accepted() { return message_accepted_; }
+
+ bool message_declined() { return message_declined_; }
+
protected:
+ void MessageAccepted() { message_accepted_ = true; }
+
+ void MessageDeclined(messages::DismissReason dismiss_reason) {
+ message_declined_ = true;
+ }
+
messages::MessagesTestHelper messages_test_helper_;
+
+ private:
+ std::unique_ptr<messages::MessageWrapper> message_;
+ bool message_accepted_;
+ bool message_declined_;
};
-IN_PROC_BROWSER_TEST_F(SurveyClientAndroidBrowserTest, LaunchSurvey) {
+IN_PROC_BROWSER_TEST_F(SurveyClientAndroidBrowserTest,
+ CreateSurveyWithMessage) {
EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
+ auto* message = createMessage();
+ std::unique_ptr<SurveyUiDelegateAndroid> delegate =
+ std::make_unique<SurveyUiDelegateAndroid>(message, window_android());
+ EXPECT_TRUE(delegate.get());
- SurveyObserver observer;
-
- auto* hatsService = GetHatsService();
+ // Create survey client with delegate.
+ std::unique_ptr<SurveyClientAndroid> survey_client =
+ std::make_unique<SurveyClientAndroid>(
+ kTestSurveyTrigger, delegate.get(),
+ ProfileManager::GetActiveUserProfile());
{
MessageWaiter waiter(messages_test_helper_);
- hatsService->LaunchSurveyForWebContents(
- kTestSurveyTrigger, web_contents(), kTestSurveyProductSpecificBitsData,
- kTestSurveyProductSpecificStringData,
- base::BindOnce(&SurveyObserver::Accept, observer.GetWeakPtr()),
- base::BindOnce(&SurveyObserver::Dismiss, observer.GetWeakPtr()));
+ survey_client->LaunchSurvey(window_android(),
+ kTestSurveyProductSpecificBitsData,
+ kTestSurveyProductSpecificStringData);
EXPECT_TRUE(waiter.Wait());
}
- hatsService->GetFirstTaskForTesting()
- .GetMessageForTesting()
- ->HandleActionClick(base::android::AttachCurrentThread());
+ EXPECT_EQ(1, messages_test_helper_.GetMessageCount(window_android()));
+ EXPECT_EQ(static_cast<int>(messages::MessageIdentifier::TEST_MESSAGE),
+ messages_test_helper_.GetMessageIdentifier(window_android(), 0));
- EXPECT_TRUE(observer.IsAccepted());
+ messages::MessageDispatcherBridge::Get()->DismissMessage(
+ message, messages::DismissReason::UNKNOWN);
+ EXPECT_TRUE(message_declined());
}
} // namespace hats
diff --git a/chrome/browser/ui/hats/DEPS b/chrome/browser/ui/hats/DEPS
index aa788a9..f207e6f 100644
--- a/chrome/browser/ui/hats/DEPS
+++ b/chrome/browser/ui/hats/DEPS
@@ -6,5 +6,5 @@
"trust_safety_sentiment_service_browsertest\.cc": [
"+chrome/browser/ui/views/page_info",
- ],
+ ]
}
diff --git a/chrome/browser/ui/hats/hats_service.cc b/chrome/browser/ui/hats/hats_service.cc
index 5964190..b15f911 100644
--- a/chrome/browser/ui/hats/hats_service.cc
+++ b/chrome/browser/ui/hats/hats_service.cc
@@ -4,12 +4,596 @@
#include "chrome/browser/ui/hats/hats_service.h"
+#include <memory>
+#include <utility>
+
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "base/json/values_util.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
+#include "base/ranges/algorithm.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/sessions/exit_type_service.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "components/metrics_services_manager/metrics_services_manager.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/network_change_notifier.h"
+
+constexpr char kHatsShouldShowSurveyReasonHistogram[] =
+ "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReason";
+
+namespace {
+
+// TODO(crbug.com/1160661): When the minimum time between any survey, and the
+// minimum time between a specific survey, are the same, the logic supporting
+// the latter check is superfluous.
+constexpr base::TimeDelta kMinimumTimeBetweenSurveyStarts = base::Days(180);
+
+constexpr base::TimeDelta kMinimumTimeBetweenAnySurveyStarts = base::Days(180);
+
+constexpr base::TimeDelta kMinimumTimeBetweenSurveyChecks = base::Days(1);
+
+constexpr base::TimeDelta kMinimumProfileAge = base::Days(30);
+
+// Preferences Data Model
+// The kHatsSurveyMetadata pref points to a dictionary.
+// The valid keys and value types for this dictionary are as follows:
+// [trigger].last_major_version ---> Integer
+// [trigger].last_survey_started_time ---> Time
+// [trigger].is_survey_full ---> Bool
+// [trigger].last_survey_check_time ---> Time
+// any_last_survey_started_time ---> Time
+
+std::string GetMajorVersionPath(const std::string& trigger) {
+ return trigger + ".last_major_version";
+}
+
+std::string GetLastSurveyStartedTime(const std::string& trigger) {
+ return trigger + ".last_survey_started_time";
+}
+
+std::string GetIsSurveyFull(const std::string& trigger) {
+ return trigger + ".is_survey_full";
+}
+
+std::string GetLastSurveyCheckTime(const std::string& trigger) {
+ return trigger + ".last_survey_check_time";
+}
+
+constexpr char kAnyLastSurveyStartedTimePath[] = "any_last_survey_started_time";
+
+} // namespace
+
HatsService::SurveyMetadata::SurveyMetadata() = default;
HatsService::SurveyMetadata::~SurveyMetadata() = default;
+HatsService::DelayedSurveyTask::DelayedSurveyTask(
+ HatsService* hats_service,
+ const std::string& trigger,
+ content::WebContents* web_contents,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data,
+ bool require_same_origin)
+ : hats_service_(hats_service),
+ trigger_(trigger),
+ product_specific_bits_data_(product_specific_bits_data),
+ product_specific_string_data_(product_specific_string_data),
+ require_same_origin_(require_same_origin) {
+ Observe(web_contents);
+}
+
+HatsService::DelayedSurveyTask::~DelayedSurveyTask() = default;
+
+base::WeakPtr<HatsService::DelayedSurveyTask>
+HatsService::DelayedSurveyTask::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+void HatsService::DelayedSurveyTask::Launch() {
+ hats_service_->LaunchSurveyForWebContents(trigger_, web_contents(),
+ product_specific_bits_data_,
+ product_specific_string_data_);
+ hats_service_->RemoveTask(*this);
+}
+
+void HatsService::DelayedSurveyTask::DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (!require_same_origin_ || !navigation_handle ||
+ !navigation_handle->IsInPrimaryMainFrame() ||
+ navigation_handle->IsSameDocument() ||
+ (navigation_handle->HasCommitted() &&
+ navigation_handle->IsSameOrigin())) {
+ return;
+ }
+
+ hats_service_->RemoveTask(*this);
+}
+
+void HatsService::DelayedSurveyTask::WebContentsDestroyed() {
+ hats_service_->RemoveTask(*this);
+}
+
HatsService::HatsService(Profile* profile) : profile_(profile) {
hats::GetActiveSurveyConfigs(survey_configs_by_triggers_);
}
HatsService::~HatsService() = default;
+
+// static
+void HatsService::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterDictionaryPref(
+ prefs::kHatsSurveyMetadata,
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+}
+
+void HatsService::LaunchSurvey(
+ const std::string& trigger,
+ base::OnceClosure success_callback,
+ base::OnceClosure failure_callback,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data) {
+ if (!ShouldShowSurvey(trigger)) {
+ std::move(failure_callback).Run();
+ return;
+ }
+
+ LaunchSurveyForBrowser(
+ chrome::FindLastActiveWithProfile(profile_), trigger,
+ std::move(success_callback), std::move(failure_callback),
+ product_specific_bits_data, product_specific_string_data);
+}
+
+bool HatsService::LaunchDelayedSurvey(
+ const std::string& trigger,
+ int timeout_ms,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data) {
+ return base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&HatsService::LaunchSurvey, weak_ptr_factory_.GetWeakPtr(),
+ trigger, base::DoNothing(), base::DoNothing(),
+ product_specific_bits_data, product_specific_string_data),
+ base::Milliseconds(timeout_ms));
+}
+
+bool HatsService::LaunchDelayedSurveyForWebContents(
+ const std::string& trigger,
+ content::WebContents* web_contents,
+ int timeout_ms,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data,
+ bool require_same_origin) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (!web_contents) {
+ return false;
+ }
+ auto result = pending_tasks_.emplace(
+ this, trigger, web_contents, product_specific_bits_data,
+ product_specific_string_data, require_same_origin);
+ if (!result.second) {
+ return false;
+ }
+ auto success =
+ base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(
+ &HatsService::DelayedSurveyTask::Launch,
+ const_cast<HatsService::DelayedSurveyTask&>(*(result.first))
+ .GetWeakPtr()),
+ base::Milliseconds(timeout_ms));
+ if (!success) {
+ pending_tasks_.erase(result.first);
+ }
+ return success;
+}
+
+void HatsService::RecordSurveyAsShown(std::string trigger_id) {
+ // Record the trigger associated with the trigger_id. This is recorded instead
+ // of the trigger ID itself, as the ID is specific to individual survey
+ // versions. There should be a cooldown before a user is prompted to take a
+ // survey from the same trigger, regardless of whether the survey was updated.
+ auto trigger_survey_config =
+ base::ranges::find(survey_configs_by_triggers_, trigger_id,
+ [](const SurveyConfigs::value_type& pair) {
+ return pair.second.trigger_id;
+ });
+
+ DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
+ std::string trigger = trigger_survey_config->first;
+
+ UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kYes);
+
+ ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
+ base::Value::Dict& pref_data = update.Get();
+ pref_data.SetByDottedPath(
+ GetMajorVersionPath(trigger),
+ static_cast<int>(version_info::GetVersion().components()[0]));
+ pref_data.SetByDottedPath(GetLastSurveyStartedTime(trigger),
+ base::TimeToValue(base::Time::Now()));
+ pref_data.SetByDottedPath(kAnyLastSurveyStartedTimePath,
+ base::TimeToValue(base::Time::Now()));
+}
+
+void HatsService::HatsNextDialogClosed() {
+ hats_next_dialog_exists_ = false;
+}
+
+void HatsService::SetSurveyMetadataForTesting(
+ const HatsService::SurveyMetadata& metadata) {
+ const std::string& trigger = kHatsSurveyTriggerSettings;
+ ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
+ base::Value::Dict& pref_data = update.Get();
+ if (!metadata.last_major_version.has_value() &&
+ !metadata.last_survey_started_time.has_value() &&
+ !metadata.is_survey_full.has_value() &&
+ !metadata.last_survey_check_time.has_value()) {
+ pref_data.RemoveByDottedPath(trigger);
+ }
+
+ if (metadata.last_major_version.has_value()) {
+ pref_data.SetByDottedPath(GetMajorVersionPath(trigger),
+ *metadata.last_major_version);
+ } else {
+ pref_data.RemoveByDottedPath(GetMajorVersionPath(trigger));
+ }
+
+ if (metadata.last_survey_started_time.has_value()) {
+ pref_data.SetByDottedPath(
+ GetLastSurveyStartedTime(trigger),
+ base::TimeToValue(*metadata.last_survey_started_time));
+ } else {
+ pref_data.RemoveByDottedPath(GetLastSurveyStartedTime(trigger));
+ }
+
+ if (metadata.any_last_survey_started_time.has_value()) {
+ pref_data.SetByDottedPath(
+ kAnyLastSurveyStartedTimePath,
+ base::TimeToValue(*metadata.any_last_survey_started_time));
+ } else {
+ pref_data.RemoveByDottedPath(kAnyLastSurveyStartedTimePath);
+ }
+
+ if (metadata.is_survey_full.has_value()) {
+ pref_data.SetByDottedPath(GetIsSurveyFull(trigger),
+ *metadata.is_survey_full);
+ } else {
+ pref_data.RemoveByDottedPath(GetIsSurveyFull(trigger));
+ }
+
+ if (metadata.last_survey_check_time.has_value()) {
+ pref_data.SetByDottedPath(
+ GetLastSurveyCheckTime(trigger),
+ base::TimeToValue(*metadata.last_survey_check_time));
+ } else {
+ pref_data.RemoveByDottedPath(GetLastSurveyCheckTime(trigger));
+ }
+}
+
+void HatsService::GetSurveyMetadataForTesting(
+ HatsService::SurveyMetadata* metadata) const {
+ const std::string& trigger = kHatsSurveyTriggerSettings;
+ ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
+ base::Value::Dict& pref_data = update.Get();
+
+ absl::optional<int> last_major_version =
+ pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
+ if (last_major_version.has_value()) {
+ metadata->last_major_version = last_major_version;
+ }
+
+ absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
+ pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
+ if (last_survey_started_time.has_value()) {
+ metadata->last_survey_started_time = last_survey_started_time;
+ }
+
+ absl::optional<base::Time> any_last_survey_started_time = base::ValueToTime(
+ pref_data.FindByDottedPath(kAnyLastSurveyStartedTimePath));
+ if (any_last_survey_started_time.has_value()) {
+ metadata->any_last_survey_started_time = any_last_survey_started_time;
+ }
+
+ absl::optional<bool> is_survey_full =
+ pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
+ if (is_survey_full.has_value()) {
+ metadata->is_survey_full = is_survey_full;
+ }
+
+ absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
+ pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
+ if (last_survey_check_time.has_value()) {
+ metadata->last_survey_check_time = last_survey_check_time;
+ }
+}
+
+void HatsService::RemoveTask(const DelayedSurveyTask& task) {
+ pending_tasks_.erase(task);
+}
+
+bool HatsService::HasPendingTasks() {
+ return !pending_tasks_.empty();
+}
+
+void HatsService::LaunchSurveyForWebContents(
+ const std::string& trigger,
+ content::WebContents* web_contents,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data) {
+ if (ShouldShowSurvey(trigger) && web_contents &&
+ web_contents->GetVisibility() == content::Visibility::VISIBLE) {
+ LaunchSurveyForBrowser(chrome::FindBrowserWithTab(web_contents), trigger,
+ base::DoNothing(), base::DoNothing(),
+ product_specific_bits_data,
+ product_specific_string_data);
+ }
+}
+
+void HatsService::LaunchSurveyForBrowser(
+ Browser* browser,
+ const std::string& trigger,
+ base::OnceClosure success_callback,
+ base::OnceClosure failure_callback,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data) {
+ if (!browser ||
+ (!browser->is_type_normal() && !browser->is_type_devtools()) ||
+ !profiles::IsRegularOrGuestSession(browser)) {
+ // Never show HaTS bubble for Incognito mode.
+ UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoNotRegularBrowser);
+ std::move(failure_callback).Run();
+ return;
+ }
+ if (IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) ==
+ policy::IncognitoModeAvailability::kDisabled) {
+ // Incognito mode needs to be enabled to create an off-the-record profile
+ // for HaTS dialog.
+ UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoIncognitoDisabled);
+ std::move(failure_callback).Run();
+ return;
+ }
+ // Checking survey's status could be costly due to a network request, so
+ // we check it at the last.
+ CheckSurveyStatusAndMaybeShow(browser, trigger, std::move(success_callback),
+ std::move(failure_callback),
+ product_specific_bits_data,
+ product_specific_string_data);
+}
+
+bool HatsService::CanShowSurvey(const std::string& trigger) const {
+ // Do not show if a survey dialog already exists.
+ if (hats_next_dialog_exists_) {
+ UMA_HISTOGRAM_ENUMERATION(
+ kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoSurveyAlreadyInProgress);
+ return false;
+ }
+
+ // Survey should not be loaded if the corresponding survey config is
+ // unavailable.
+ const auto config_iterator = survey_configs_by_triggers_.find(trigger);
+ if (config_iterator == survey_configs_by_triggers_.end()) {
+ UMA_HISTOGRAM_ENUMERATION(
+ kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoTriggerStringMismatch);
+ return false;
+ }
+ const hats::SurveyConfig config = config_iterator->second;
+
+ // Always show the survey in demo mode. This check is duplicated in
+ // CanShowAnySurvey, but because of the semantics of that function, must be
+ // included here.
+ if (base::FeatureList::IsEnabled(
+ features::kHappinessTrackingSurveysForDesktopDemo)) {
+ return true;
+ }
+
+ if (!CanShowAnySurvey(config.user_prompted)) {
+ return false;
+ }
+
+ // Survey can not be loaded and shown if there is no network connection.
+ if (net::NetworkChangeNotifier::IsOffline()) {
+ UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoOffline);
+ return false;
+ }
+
+ const base::Value::Dict& pref_data =
+ profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+ absl::optional<int> last_major_version =
+ pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
+ if (last_major_version.has_value() &&
+ static_cast<uint32_t>(*last_major_version) ==
+ version_info::GetVersion().components()[0]) {
+ UMA_HISTOGRAM_ENUMERATION(
+ kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone);
+ return false;
+ }
+
+ if (!config.user_prompted) {
+ absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
+ pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
+ if (last_survey_started_time.has_value()) {
+ base::TimeDelta elapsed_time_since_last_start =
+ base::Time::Now() - *last_survey_started_time;
+ if (elapsed_time_since_last_start < kMinimumTimeBetweenSurveyStarts) {
+ UMA_HISTOGRAM_ENUMERATION(
+ kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoLastSurveyTooRecent);
+ return false;
+ }
+ }
+ }
+
+ // If an attempt to check with the HaTS servers whether a survey should be
+ // delivered was made too recently, another survey cannot be shown.
+ absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
+ pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
+ if (last_survey_check_time.has_value()) {
+ base::TimeDelta elapsed_time_since_last_check =
+ base::Time::Now() - *last_survey_check_time;
+ if (elapsed_time_since_last_check < kMinimumTimeBetweenSurveyChecks) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool HatsService::CanShowAnySurvey(bool user_prompted) const {
+ // HaTS requires metrics consent to run. This is also how HaTS can be disabled
+ // by policy.
+ if (!g_browser_process->GetMetricsServicesManager()
+ ->IsMetricsConsentGiven()) {
+ return false;
+ }
+
+ // HaTs can also be disabled by policy if metrics consent is given.
+ if (!profile_->GetPrefs()->GetBoolean(
+ policy::policy_prefs::kFeedbackSurveysEnabled)) {
+ return false;
+ }
+
+ // Surveys can always be shown in Demo mode.
+ if (base::FeatureList::IsEnabled(
+ features::kHappinessTrackingSurveysForDesktopDemo)) {
+ return true;
+ }
+
+ // Do not show surveys if Chrome's last exit was a crash. This avoids
+ // biasing survey results unnecessarily.
+ if (ExitTypeService::GetLastSessionExitType(profile_) == ExitType::kCrashed) {
+ UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoLastSessionCrashed);
+ return false;
+ }
+
+ // Some surveys may be "user prompted", which means the user has already been
+ // asked in context if they would like to take a survey (in a less
+ // confrontational manner than the standard HaTS prompt). The bar for whether
+ // a user is eligible is thus lower for these types of surveys.
+ if (!user_prompted) {
+ const base::Value::Dict& pref_data =
+ profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+
+ // If the profile is too new, measured as the age of the profile directory,
+ // the user is ineligible.
+ base::Time now = base::Time::Now();
+ if ((now - profile_->GetCreationTime()) < kMinimumProfileAge) {
+ UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoProfileTooNew);
+ return false;
+ }
+
+ // If a user has received any HaTS survey too recently, they are also
+ // ineligible.
+ absl::optional<base::Time> last_any_started_time =
+ base::ValueToTime(pref_data.Find(kAnyLastSurveyStartedTimePath));
+ if (last_any_started_time.has_value()) {
+ base::TimeDelta elapsed_time_any_started = now - *last_any_started_time;
+ if (elapsed_time_any_started < kMinimumTimeBetweenAnySurveyStarts) {
+ UMA_HISTOGRAM_ENUMERATION(
+ kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool HatsService::ShouldShowSurvey(const std::string& trigger) const {
+ if (!CanShowSurvey(trigger)) {
+ return false;
+ }
+
+ auto probability = survey_configs_by_triggers_.at(trigger).probability;
+ bool should_show_survey = base::RandDouble() < probability;
+ if (!should_show_survey) {
+ UMA_HISTOGRAM_ENUMERATION(
+ kHatsShouldShowSurveyReasonHistogram,
+ ShouldShowSurveyReasons::kNoBelowProbabilityLimit);
+ }
+
+ return should_show_survey;
+}
+
+void HatsService::CheckSurveyStatusAndMaybeShow(
+ Browser* browser,
+ const std::string& trigger,
+ base::OnceClosure success_callback,
+ base::OnceClosure failure_callback,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data) {
+ // Check the survey status in profile first.
+ // We record the survey's over capacity information in user profile to avoid
+ // duplicated checks since the survey won't change once it is full.
+ const base::Value::Dict& pref_data =
+ profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+ absl::optional<int> is_full =
+ pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
+ if (is_full.has_value() && is_full) {
+ std::move(failure_callback).Run();
+ return;
+ }
+
+ CHECK(survey_configs_by_triggers_.find(trigger) !=
+ survey_configs_by_triggers_.end());
+ auto survey_config = survey_configs_by_triggers_[trigger];
+
+ // Check that the |product_specific_bits_data| matches the fields for this
+ // trigger. If fields are set for a trigger, they must be provided.
+ CHECK_EQ(product_specific_bits_data.size(),
+ survey_config.product_specific_bits_data_fields.size());
+ for (auto field_value : product_specific_bits_data) {
+ CHECK(base::Contains(survey_config.product_specific_bits_data_fields,
+ field_value.first));
+ }
+
+ // Check that the |product_specific_string_data| matches the fields for this
+ // trigger. If fields are set for a trigger, they must be provided.
+ CHECK_EQ(product_specific_string_data.size(),
+ survey_config.product_specific_string_data_fields.size());
+ for (auto field_value : product_specific_string_data) {
+ CHECK(base::Contains(survey_config.product_specific_string_data_fields,
+ field_value.first));
+ }
+
+ // As soon as the HaTS Next dialog is created it will attempt to contact
+ // the HaTS servers to check for a survey.
+ ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
+ update->SetByDottedPath(GetLastSurveyCheckTime(trigger),
+ base::TimeToValue(base::Time::Now()));
+
+ DCHECK(!hats_next_dialog_exists_);
+ browser->window()->ShowHatsDialog(
+ survey_configs_by_triggers_[trigger].trigger_id,
+ std::move(success_callback), std::move(failure_callback),
+ product_specific_bits_data, product_specific_string_data);
+ hats_next_dialog_exists_ = true;
+}
diff --git a/chrome/browser/ui/hats/hats_service.h b/chrome/browser/ui/hats/hats_service.h
index 381d2b2a..5eba9b9d 100644
--- a/chrome/browser/ui/hats/hats_service.h
+++ b/chrome/browser/ui/hats/hats_service.h
@@ -5,11 +5,13 @@
#ifndef CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_
#define CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_
+#include <memory>
+#include <set>
#include <string>
#include "base/containers/flat_map.h"
+#include "base/feature_list.h"
#include "base/functional/callback.h"
-#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
@@ -17,7 +19,6 @@
#include "base/time/time.h"
#include "chrome/browser/ui/hats/survey_config.h"
#include "components/keyed_service/core/keyed_service.h"
-#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
@@ -25,8 +26,17 @@
class WebContents;
}
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class Browser;
class Profile;
+// The name of the histogram which records if a survey was shown, or if not, the
+// reason why not.
+extern const char kHatsShouldShowSurveyReasonHistogram[];
+
// Key-value mapping type for survey's product specific bits data.
typedef std::map<std::string, bool> SurveyBitsData;
@@ -52,105 +62,200 @@
absl::optional<base::Time> any_last_survey_started_time;
};
+ class DelayedSurveyTask : public content::WebContentsObserver {
+ public:
+ DelayedSurveyTask(HatsService* hats_service,
+ const std::string& trigger,
+ content::WebContents* web_contents,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data,
+ bool require_same_origin);
+
+ // Not copyable or movable
+ DelayedSurveyTask(const DelayedSurveyTask&) = delete;
+ DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
+
+ ~DelayedSurveyTask() override;
+
+ // Asks |hats_service_| to launch the survey with id |trigger_| for tab
+ // |web_contents_|.
+ void Launch();
+
+ // content::WebContentsObserver
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ void WebContentsDestroyed() override;
+
+ // Returns a weak pointer to this object.
+ base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
+
+ bool operator<(const HatsService::DelayedSurveyTask& other) const {
+ return trigger_ < other.trigger_ ? true
+ : web_contents() < other.web_contents();
+ }
+
+ private:
+ raw_ptr<HatsService> hats_service_;
+ std::string trigger_;
+ SurveyBitsData product_specific_bits_data_;
+ SurveyStringData product_specific_string_data_;
+ bool require_same_origin_;
+ base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
+ };
+
+ // These values are persisted to logs. Entries should not be renumbered and
+ // numeric values should never be reused.
+ enum class ShouldShowSurveyReasons {
+ kYes = 0,
+ kNoOffline = 1,
+ kNoLastSessionCrashed = 2,
+ kNoReceivedSurveyInCurrentMilestone = 3,
+ kNoProfileTooNew = 4,
+ kNoLastSurveyTooRecent = 5,
+ kNoBelowProbabilityLimit = 6,
+ kNoTriggerStringMismatch = 7,
+ kNoNotRegularBrowser = 8,
+ kNoIncognitoDisabled = 9,
+ kNoCookiesBlocked = 10, // Unused.
+ kNoThirdPartyCookiesBlocked = 11, // Unused.
+ kNoSurveyUnreachable = 12,
+ kNoSurveyOverCapacity = 13,
+ kNoSurveyAlreadyInProgress = 14,
+ kNoAnyLastSurveyTooRecent = 15,
+ kNoRejectedByHatsService = 16,
+ kMaxValue = kNoRejectedByHatsService,
+ };
+
explicit HatsService(Profile* profile);
+
HatsService(const HatsService&) = delete;
HatsService& operator=(const HatsService&) = delete;
~HatsService() override;
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
// Launches survey with identifier |trigger| if appropriate.
// |success_callback| is called when the survey is shown to the user.
// |failure_callback| is called if the survey does not launch for any reason.
// |product_specific_bits_data| and |product_specific_string_data| must
// contain key-value pairs where the keys match the field names set for the
- // survey in survey_config.cc, and the values are those which will be
- // associated with the survey response.
+ // survey in hats_service.cc, and the values are those which will be
+ // associated with the survey response. Field's matches are CHECK enforced.
virtual void LaunchSurvey(
const std::string& trigger,
base::OnceClosure success_callback = base::DoNothing(),
base::OnceClosure failure_callback = base::DoNothing(),
const SurveyBitsData& product_specific_bits_data = {},
- const SurveyStringData& product_specific_string_data = {}) = 0;
+ const SurveyStringData& product_specific_string_data = {});
// Launches survey (with id |trigger|) with a timeout |timeout_ms| if
- // appropriate.
- // |product_specific_bits_data| and |product_specific_string_data| must
- // contain key-value pairs where the keys match the field names set for the
- // survey in survey_config.cc, and the values are those which will be
- // associated with the survey response.
- // |web_contents| specifies the `WebContents` where the survey should be
- // displayed. Returns false if the underlying task posting fails.
- virtual void LaunchSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- base::OnceClosure success_callback = base::DoNothing(),
- base::OnceClosure failure_callback = base::DoNothing()) = 0;
-
- // Launches survey (with id |trigger|) with a timeout |timeout_ms| if
- // appropriate.
- // |product_specific_bits_data| and |product_specific_string_data| must
- // contain key-value pairs where the keys match the field names set for the
- // survey in survey_config.cc, and the values are those which will be
- // associated with the survey response.
+ // appropriate. Survey will be shown at the active window/tab by the
+ // time of launching. Rejects (and returns false) if the underlying task
+ // posting fails.
virtual bool LaunchDelayedSurvey(
const std::string& trigger,
int timeout_ms,
const SurveyBitsData& product_specific_bits_data = {},
- const SurveyStringData& product_specific_string_data = {}) = 0;
+ const SurveyStringData& product_specific_string_data = {});
// Launches survey (with id |trigger|) with a timeout |timeout_ms| for tab
// |web_contents| if appropriate. |web_contents| required to be non-nullptr.
- // Launch is cancelled if |web_contents| killed before end of timeout.
+ // Launch is cancelled if |web_contents| killed before end of timeout. Launch
+ // is also cancelled if |web_contents| not visible at the time of launch.
// Rejects (and returns false) if there is already an identical delayed-task
// (same |trigger| and same |web_contents|) waiting to be fulfilled. Also
- // rejects if the underlying task posting fails.
- // If |require_same_origin| is set, additionally requires that |web_contents|
- // remain on the same origin.
- // |success_callback| is called when the survey is shown to the user.
- // |failure_callback| is called if the survey does not launch for any reason.
+ // rejects if the underlying task posting fails. If |require_same_origin| is
+ // set, additionally requires that |web_contents| remain on the same origin.
virtual bool LaunchDelayedSurveyForWebContents(
const std::string& trigger,
content::WebContents* web_contents,
int timeout_ms,
const SurveyBitsData& product_specific_bits_data = {},
const SurveyStringData& product_specific_string_data = {},
- bool require_same_origin = false,
- base::OnceClosure success_callback = base::DoNothing(),
- base::OnceClosure failure_callback = base::DoNothing()) = 0;
+ bool require_same_origin = false);
- // Whether the user is eligible for any survey (of the type |user_prompted|
- // or not) to be shown. A return value of false is always a true-negative,
- // and means the user is currently ineligible for all surveys. A return value
- // of true should not be interpreted as a guarantee that requests to show a
- // survey will succeed.
- virtual bool CanShowAnySurvey(bool user_prompted) const = 0;
+ // Updates the user preferences to record that the survey associated with
+ // |survey_id| was shown to the user. |trigger_id| is the HaTS next Trigger
+ // ID for the survey.
+ void RecordSurveyAsShown(std::string trigger_id);
+
+ // Indicates to the service that the HaTS Next dialog has been closed.
+ // Virtual to allow mocking in tests.
+ virtual void HatsNextDialogClosed();
+
+ void SetSurveyMetadataForTesting(const SurveyMetadata& metadata);
+ void GetSurveyMetadataForTesting(HatsService::SurveyMetadata* metadata) const;
+ bool HasPendingTasks();
// Whether the survey specified by |trigger| can be shown to the user. This
// is a pre-check that calculates as many conditions as possible, but could
// still return a false positive due to client-side rate limiting, a change
// in network conditions, or intervening calls to this API.
- virtual bool CanShowSurvey(const std::string& trigger) const = 0;
+ bool CanShowSurvey(const std::string& trigger) const;
- // Updates the user preferences to record that the survey associated with
- // |survey_id| was shown to the user. |trigger_id| is the HaTS next Trigger
- // ID for the survey.
- virtual void RecordSurveyAsShown(std::string trigger_id) = 0;
+ // Whether the user is eligible for any survey (of the type |user_prompted|
+ // or not) to be shown. A return value of false is always a true-negative, and
+ // means the user is currently ineligible for all surveys. A return value of
+ // true should not be interpreted as a guarantee that requests to show a
+ // survey will succeed. Virtual to allow mocking in tests.
+ virtual bool CanShowAnySurvey(bool user_prompted) const;
- protected:
- hats::SurveyConfigs survey_configs_by_triggers_;
- using SurveyConfigs = base::flat_map<std::string, hats::SurveyConfig>;
-
- Profile* profile() const { return profile_; }
+ // Returns whether a HaTS Next dialog currently exists, regardless of whether
+ // it is being shown or not.
+ bool hats_next_dialog_exists_for_testing() {
+ return hats_next_dialog_exists_;
+ }
private:
friend class DelayedSurveyTask;
FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
+ using SurveyConfigs = base::flat_map<std::string, hats::SurveyConfig>;
+
+ void LaunchSurveyForWebContents(
+ const std::string& trigger,
+ content::WebContents* web_contents,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data);
+
+ void LaunchSurveyForBrowser(
+ Browser* browser,
+ const std::string& trigger,
+ base::OnceClosure success_callback,
+ base::OnceClosure failure_callback,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data);
+
+ // Returns true is the survey trigger specified should be shown.
+ bool ShouldShowSurvey(const std::string& trigger) const;
+
+ // Check whether the survey is reachable and under capacity and show it.
+ // |success_callback| is called when the survey is shown to the user.
+ // |failure_callback| is called if the survey does not launch for any reason.
+ // The matches of field names with the `SurveyConfig` are CHECK enforced.
+ void CheckSurveyStatusAndMaybeShow(
+ Browser* browser,
+ const std::string& trigger,
+ base::OnceClosure success_callback,
+ base::OnceClosure failure_callback,
+ const SurveyBitsData& product_specific_bits_data,
+ const SurveyStringData& product_specific_string_data);
+
+ // Remove |task| from the set of |pending_tasks_|.
+ void RemoveTask(const DelayedSurveyTask& task);
+
// Profile associated with this service.
const raw_ptr<Profile> profile_;
+ std::set<DelayedSurveyTask> pending_tasks_;
+
+ SurveyConfigs survey_configs_by_triggers_;
+
+ // Whether a HaTS Next dialog currently exists (regardless of whether it
+ // is being shown to the user).
+ bool hats_next_dialog_exists_ = false;
+
base::WeakPtrFactory<HatsService> weak_ptr_factory_{this};
};
diff --git a/chrome/browser/ui/hats/hats_service_browsertest.cc b/chrome/browser/ui/hats/hats_service_browsertest.cc
index 12e84b4..8843038 100644
--- a/chrome/browser/ui/hats/hats_service_browsertest.cc
+++ b/chrome/browser/ui/hats/hats_service_browsertest.cc
@@ -17,7 +17,7 @@
#include "chrome/browser/profiles/profile_impl.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
+#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
@@ -80,9 +80,9 @@
~HatsServiceBrowserTestBase() override = default;
- HatsServiceDesktop* GetHatsService() {
- HatsServiceDesktop* service = static_cast<HatsServiceDesktop*>(
- HatsServiceFactory::GetForProfile(browser()->profile(), true));
+ HatsService* GetHatsService() {
+ HatsService* service =
+ HatsServiceFactory::GetForProfile(browser()->profile(), true);
return service;
}
@@ -218,21 +218,20 @@
IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, SameMajorVersionNoShow) {
SetMetricsConsent(true);
base::HistogramTester histogram_tester;
- HatsServiceDesktop::SurveyMetadata metadata;
+ HatsService::SurveyMetadata metadata;
metadata.last_major_version = version_info::GetVersion().components()[0];
GetHatsService()->SetSurveyMetadataForTesting(metadata);
GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
histogram_tester.ExpectUniqueSample(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::
- kNoReceivedSurveyInCurrentMilestone,
+ HatsService::ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone,
1);
EXPECT_FALSE(HatsNextDialogCreated());
}
IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, DifferentMajorVersionShow) {
SetMetricsConsent(true);
- HatsServiceDesktop::SurveyMetadata metadata;
+ HatsService::SurveyMetadata metadata;
metadata.last_major_version = 42;
ASSERT_NE(42u, version_info::GetVersion().components()[0]);
GetHatsService()->SetSurveyMetadataForTesting(metadata);
@@ -244,13 +243,13 @@
SurveyStartedBeforeRequiredElapsedTimeNoShow) {
SetMetricsConsent(true);
base::HistogramTester histogram_tester;
- HatsServiceDesktop::SurveyMetadata metadata;
+ HatsService::SurveyMetadata metadata;
metadata.last_survey_started_time = base::Time::Now();
GetHatsService()->SetSurveyMetadataForTesting(metadata);
GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
histogram_tester.ExpectUniqueSample(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::kNoLastSurveyTooRecent, 1);
+ HatsService::ShouldShowSurveyReasons::kNoLastSurveyTooRecent, 1);
EXPECT_FALSE(HatsNextDialogCreated());
}
@@ -258,15 +257,14 @@
SurveyStartedBeforeElapsedTimeBetweenAnySurveys) {
SetMetricsConsent(true);
base::HistogramTester histogram_tester;
- HatsServiceDesktop::SurveyMetadata metadata;
+ HatsService::SurveyMetadata metadata;
metadata.any_last_survey_started_time = base::Time::Now();
GetHatsService()->SetSurveyMetadataForTesting(metadata);
GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
EXPECT_FALSE(HatsNextDialogCreated());
histogram_tester.ExpectUniqueSample(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent,
- 1);
+ HatsService::ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent, 1);
}
IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, ProfileTooYoungToShow) {
@@ -278,7 +276,7 @@
GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
histogram_tester.ExpectUniqueSample(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::kNoProfileTooNew, 1);
+ HatsService::ShouldShowSurveyReasons::kNoProfileTooNew, 1);
EXPECT_FALSE(HatsNextDialogCreated());
}
@@ -307,7 +305,7 @@
IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, CheckedWithinADayNoShow) {
SetMetricsConsent(true);
- HatsServiceDesktop::SurveyMetadata metadata;
+ HatsService::SurveyMetadata metadata;
metadata.last_survey_check_time = base::Time::Now() - base::Hours(23);
GetHatsService()->SetSurveyMetadataForTesting(metadata);
GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -316,7 +314,7 @@
IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, CheckedAfterADayToShow) {
SetMetricsConsent(true);
- HatsServiceDesktop::SurveyMetadata metadata;
+ HatsService::SurveyMetadata metadata;
metadata.last_survey_check_time = base::Time::Now() - base::Days(1);
GetHatsService()->SetSurveyMetadataForTesting(metadata);
GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -325,7 +323,7 @@
IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, SurveyAlreadyFullNoShow) {
SetMetricsConsent(true);
- HatsServiceDesktop::SurveyMetadata metadata;
+ HatsService::SurveyMetadata metadata;
metadata.is_survey_full = true;
GetHatsService()->SetSurveyMetadataForTesting(metadata);
GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -516,7 +514,7 @@
GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
- HatsServiceDesktop::SurveyMetadata metadata;
+ HatsService::SurveyMetadata metadata;
GetHatsService()->GetSurveyMetadataForTesting(&metadata);
EXPECT_TRUE(metadata.last_survey_check_time.has_value());
}
diff --git a/chrome/browser/ui/hats/hats_service_desktop.cc b/chrome/browser/ui/hats/hats_service_desktop.cc
deleted file mode 100644
index e56b76f..0000000
--- a/chrome/browser/ui/hats/hats_service_desktop.cc
+++ /dev/null
@@ -1,637 +0,0 @@
-// Copyright 2023 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/ui/hats/hats_service_desktop.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/containers/contains.h"
-#include "base/feature_list.h"
-#include "base/functional/bind.h"
-#include "base/functional/callback_helpers.h"
-#include "base/json/values_util.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/rand_util.h"
-#include "base/ranges/algorithm.h"
-#include "base/task/sequenced_task_runner.h"
-#include "base/values.h"
-#include "base/version.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/prefs/incognito_mode_prefs.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profiles_state.h"
-#include "chrome/browser/sessions/exit_type_service.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/common/pref_names.h"
-#include "components/metrics_services_manager/metrics_services_manager.h"
-#include "components/policy/core/common/policy_pref_names.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/scoped_user_pref_update.h"
-#include "components/version_info/version_info.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-#include "net/base/network_change_notifier.h"
-
-constexpr char kHatsShouldShowSurveyReasonHistogram[] =
- "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReason";
-
-namespace {
-
-// TODO(crbug.com/1160661): When the minimum time between any survey, and the
-// minimum time between a specific survey, are the same, the logic supporting
-// the latter check is superfluous.
-constexpr base::TimeDelta kMinimumTimeBetweenSurveyStarts = base::Days(180);
-
-constexpr base::TimeDelta kMinimumTimeBetweenAnySurveyStarts = base::Days(180);
-
-constexpr base::TimeDelta kMinimumTimeBetweenSurveyChecks = base::Days(1);
-
-constexpr base::TimeDelta kMinimumProfileAge = base::Days(30);
-
-// Preferences Data Model
-// The kHatsSurveyMetadata pref points to a dictionary.
-// The valid keys and value types for this dictionary are as follows:
-// [trigger].last_major_version ---> Integer
-// [trigger].last_survey_started_time ---> Time
-// [trigger].is_survey_full ---> Bool
-// [trigger].last_survey_check_time ---> Time
-// any_last_survey_started_time ---> Time
-
-std::string GetMajorVersionPath(const std::string& trigger) {
- return trigger + ".last_major_version";
-}
-
-std::string GetLastSurveyStartedTime(const std::string& trigger) {
- return trigger + ".last_survey_started_time";
-}
-
-std::string GetIsSurveyFull(const std::string& trigger) {
- return trigger + ".is_survey_full";
-}
-
-std::string GetLastSurveyCheckTime(const std::string& trigger) {
- return trigger + ".last_survey_check_time";
-}
-
-constexpr char kAnyLastSurveyStartedTimePath[] = "any_last_survey_started_time";
-} // namespace
-
-HatsServiceDesktop::DelayedSurveyTask::DelayedSurveyTask(
- HatsServiceDesktop* hats_service,
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- bool require_same_origin,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback)
- : hats_service_(hats_service),
- trigger_(trigger),
- product_specific_bits_data_(product_specific_bits_data),
- product_specific_string_data_(product_specific_string_data),
- require_same_origin_(require_same_origin),
- success_callback_(std::move(success_callback)),
- failure_callback_(std::move(failure_callback)) {
- Observe(web_contents);
-}
-
-HatsServiceDesktop::DelayedSurveyTask::~DelayedSurveyTask() = default;
-
-base::WeakPtr<HatsServiceDesktop::DelayedSurveyTask>
-HatsServiceDesktop::DelayedSurveyTask::GetWeakPtr() {
- return weak_ptr_factory_.GetWeakPtr();
-}
-
-void HatsServiceDesktop::DelayedSurveyTask::Launch() {
- CHECK(web_contents());
- if (web_contents() &&
- web_contents()->GetVisibility() == content::Visibility::VISIBLE) {
- hats_service_->LaunchSurveyForWebContents(trigger_, web_contents(),
- product_specific_bits_data_,
- product_specific_string_data_);
- hats_service_->RemoveTask(*this);
- }
-}
-
-void HatsServiceDesktop::DelayedSurveyTask::DidFinishNavigation(
- content::NavigationHandle* navigation_handle) {
- if (!require_same_origin_ || !navigation_handle ||
- !navigation_handle->IsInPrimaryMainFrame() ||
- navigation_handle->IsSameDocument() ||
- (navigation_handle->HasCommitted() &&
- navigation_handle->IsSameOrigin())) {
- return;
- }
-
- if (!failure_callback_.is_null()) {
- std::move(failure_callback_).Run();
- }
- hats_service_->RemoveTask(*this);
-}
-
-void HatsServiceDesktop::DelayedSurveyTask::WebContentsDestroyed() {
- if (!failure_callback_.is_null()) {
- std::move(failure_callback_).Run();
- }
- hats_service_->RemoveTask(*this);
-}
-
-HatsServiceDesktop::HatsServiceDesktop(Profile* profile)
- : HatsService(profile) {}
-
-HatsServiceDesktop::~HatsServiceDesktop() = default;
-
-// static
-void HatsServiceDesktop::RegisterProfilePrefs(
- user_prefs::PrefRegistrySyncable* registry) {
- registry->RegisterDictionaryPref(
- prefs::kHatsSurveyMetadata,
- user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-}
-
-void HatsServiceDesktop::LaunchSurvey(
- const std::string& trigger,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data) {
- if (!ShouldShowSurvey(trigger)) {
- if (!failure_callback.is_null()) {
- std::move(failure_callback).Run();
- }
- return;
- }
- LaunchSurveyForBrowser(
- chrome::FindLastActiveWithProfile(profile()), trigger,
- std::move(success_callback), std::move(failure_callback),
- product_specific_bits_data, product_specific_string_data);
-}
-
-void HatsServiceDesktop::LaunchSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback) {
- if (ShouldShowSurvey(trigger) && web_contents &&
- web_contents->GetVisibility() == content::Visibility::VISIBLE) {
- LaunchSurveyForBrowser(
- chrome::FindBrowserWithTab(web_contents), trigger,
- std::move(success_callback), std::move(failure_callback),
- product_specific_bits_data, product_specific_string_data);
- }
-}
-
-bool HatsServiceDesktop::LaunchDelayedSurvey(
- const std::string& trigger,
- int timeout_ms,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data) {
- return base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&HatsServiceDesktop::LaunchSurvey,
- weak_ptr_factory_.GetWeakPtr(), trigger, base::DoNothing(),
- base::DoNothing(), product_specific_bits_data,
- product_specific_string_data),
- base::Milliseconds(timeout_ms));
-}
-
-bool HatsServiceDesktop::LaunchDelayedSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- int timeout_ms,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- bool require_same_origin,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- if (survey_configs_by_triggers_.find(trigger) ==
- survey_configs_by_triggers_.end()) {
- // Survey configuration is not available.
- if (!failure_callback.is_null()) {
- std::move(failure_callback).Run();
- }
- return false;
- }
- if (!web_contents) {
- if (!failure_callback.is_null()) {
- std::move(failure_callback).Run();
- }
- return false;
- }
- auto result = pending_tasks_.emplace(
- this, trigger, web_contents, product_specific_bits_data,
- product_specific_string_data, require_same_origin,
- std::move(success_callback), std::move(failure_callback));
- if (!result.second) {
- return false;
- }
- auto success =
- base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&HatsServiceDesktop::DelayedSurveyTask::Launch,
- const_cast<HatsServiceDesktop::DelayedSurveyTask&>(
- *(result.first))
- .GetWeakPtr()),
- base::Milliseconds(timeout_ms));
- if (!success) {
- pending_tasks_.erase(result.first);
- }
- return success;
-}
-
-void HatsServiceDesktop::SetSurveyMetadataForTesting(
- const HatsService::SurveyMetadata& metadata) {
- const std::string& trigger = kHatsSurveyTriggerSettings;
- ScopedDictPrefUpdate update(profile()->GetPrefs(),
- prefs::kHatsSurveyMetadata);
- base::Value::Dict& pref_data = update.Get();
- if (!metadata.last_major_version.has_value() &&
- !metadata.last_survey_started_time.has_value() &&
- !metadata.is_survey_full.has_value() &&
- !metadata.last_survey_check_time.has_value()) {
- pref_data.RemoveByDottedPath(trigger);
- }
-
- if (metadata.last_major_version.has_value()) {
- pref_data.SetByDottedPath(GetMajorVersionPath(trigger),
- *metadata.last_major_version);
- } else {
- pref_data.RemoveByDottedPath(GetMajorVersionPath(trigger));
- }
-
- if (metadata.last_survey_started_time.has_value()) {
- pref_data.SetByDottedPath(
- GetLastSurveyStartedTime(trigger),
- base::TimeToValue(*metadata.last_survey_started_time));
- } else {
- pref_data.RemoveByDottedPath(GetLastSurveyStartedTime(trigger));
- }
-
- if (metadata.any_last_survey_started_time.has_value()) {
- pref_data.SetByDottedPath(
- kAnyLastSurveyStartedTimePath,
- base::TimeToValue(*metadata.any_last_survey_started_time));
- } else {
- pref_data.RemoveByDottedPath(kAnyLastSurveyStartedTimePath);
- }
-
- if (metadata.is_survey_full.has_value()) {
- pref_data.SetByDottedPath(GetIsSurveyFull(trigger),
- *metadata.is_survey_full);
- } else {
- pref_data.RemoveByDottedPath(GetIsSurveyFull(trigger));
- }
-
- if (metadata.last_survey_check_time.has_value()) {
- pref_data.SetByDottedPath(
- GetLastSurveyCheckTime(trigger),
- base::TimeToValue(*metadata.last_survey_check_time));
- } else {
- pref_data.RemoveByDottedPath(GetLastSurveyCheckTime(trigger));
- }
-}
-
-void HatsServiceDesktop::GetSurveyMetadataForTesting(
- HatsService::SurveyMetadata* metadata) const {
- const std::string& trigger = kHatsSurveyTriggerSettings;
- ScopedDictPrefUpdate update(profile()->GetPrefs(),
- prefs::kHatsSurveyMetadata);
- base::Value::Dict& pref_data = update.Get();
-
- absl::optional<int> last_major_version =
- pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
- if (last_major_version.has_value()) {
- metadata->last_major_version = last_major_version;
- }
-
- absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
- pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
- if (last_survey_started_time.has_value()) {
- metadata->last_survey_started_time = last_survey_started_time;
- }
-
- absl::optional<base::Time> any_last_survey_started_time = base::ValueToTime(
- pref_data.FindByDottedPath(kAnyLastSurveyStartedTimePath));
- if (any_last_survey_started_time.has_value()) {
- metadata->any_last_survey_started_time = any_last_survey_started_time;
- }
-
- absl::optional<bool> is_survey_full =
- pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
- if (is_survey_full.has_value()) {
- metadata->is_survey_full = is_survey_full;
- }
-
- absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
- pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
- if (last_survey_check_time.has_value()) {
- metadata->last_survey_check_time = last_survey_check_time;
- }
-}
-
-bool HatsServiceDesktop::HasPendingTasks() {
- return !pending_tasks_.empty();
-}
-
-bool HatsServiceDesktop::CanShowSurvey(const std::string& trigger) const {
- // Survey should not be loaded if the corresponding survey config is
- // unavailable.
- const auto config_iterator = survey_configs_by_triggers_.find(trigger);
-
- if (config_iterator == survey_configs_by_triggers_.end()) {
- UMA_HISTOGRAM_ENUMERATION(
- kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoTriggerStringMismatch);
- return false;
- }
- const hats::SurveyConfig config = config_iterator->second;
-
- // Do not show if a survey dialog already exists.
- if (hats_next_dialog_exists_) {
- UMA_HISTOGRAM_ENUMERATION(
- kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoSurveyAlreadyInProgress);
- return false;
- }
-
- // Always show the survey in demo mode. This check is duplicated in
- // CanShowAnySurvey, but because of the semantics of that function, must be
- // included here.
- if (base::FeatureList::IsEnabled(
- features::kHappinessTrackingSurveysForDesktopDemo)) {
- return true;
- }
-
- if (!CanShowAnySurvey(config.user_prompted)) {
- return false;
- }
-
- // Survey can not be loaded and shown if there is no network connection.
- if (net::NetworkChangeNotifier::IsOffline()) {
- UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoOffline);
- return false;
- }
-
- const base::Value::Dict& pref_data =
- profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
- absl::optional<int> last_major_version =
- pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
- if (last_major_version.has_value() &&
- static_cast<uint32_t>(*last_major_version) ==
- version_info::GetVersion().components()[0]) {
- UMA_HISTOGRAM_ENUMERATION(
- kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone);
- return false;
- }
-
- if (!config.user_prompted) {
- absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
- pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
- if (last_survey_started_time.has_value()) {
- base::TimeDelta elapsed_time_since_last_start =
- base::Time::Now() - *last_survey_started_time;
- if (elapsed_time_since_last_start < kMinimumTimeBetweenSurveyStarts) {
- UMA_HISTOGRAM_ENUMERATION(
- kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoLastSurveyTooRecent);
- return false;
- }
- }
- }
-
- // If an attempt to check with the HaTS servers whether a survey should be
- // delivered was made too recently, another survey cannot be shown.
- absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
- pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
- if (last_survey_check_time.has_value()) {
- base::TimeDelta elapsed_time_since_last_check =
- base::Time::Now() - *last_survey_check_time;
- if (elapsed_time_since_last_check < kMinimumTimeBetweenSurveyChecks) {
- return false;
- }
- }
- return true;
-}
-
-bool HatsServiceDesktop::CanShowAnySurvey(bool user_prompted) const {
- // HaTS requires metrics consent to run. This is also how HaTS can be
- // disabled by policy.
- if (!g_browser_process->GetMetricsServicesManager()
- ->IsMetricsConsentGiven()) {
- return false;
- }
-
- // HaTs can also be disabled by policy if metrics consent is given.
- if (!profile()->GetPrefs()->GetBoolean(
- policy::policy_prefs::kFeedbackSurveysEnabled)) {
- return false;
- }
-
- // Surveys can always be shown in Demo mode.
- if (base::FeatureList::IsEnabled(
- features::kHappinessTrackingSurveysForDesktopDemo)) {
- return true;
- }
-
- // Do not show surveys if Chrome's last exit was a crash. This avoids
- // biasing survey results unnecessarily.
- if (ExitTypeService::GetLastSessionExitType(profile()) ==
- ExitType::kCrashed) {
- UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoLastSessionCrashed);
- return false;
- }
-
- // Some surveys may be "user prompted", which means the user has already
- // been asked in context if they would like to take a survey (in a less
- // confrontational manner than the standard HaTS prompt). The bar for
- // whether a user is eligible is thus lower for these types of surveys.
- if (!user_prompted) {
- const base::Value::Dict& pref_data =
- profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
-
- // If the profile is too new, measured as the age of the profile
- // directory, the user is ineligible.
- base::Time now = base::Time::Now();
- if ((now - profile()->GetCreationTime()) < kMinimumProfileAge) {
- UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoProfileTooNew);
- return false;
- }
-
- // If a user has received any HaTS survey too recently, they are also
- // ineligible.
- absl::optional<base::Time> last_any_started_time =
- base::ValueToTime(pref_data.Find(kAnyLastSurveyStartedTimePath));
- if (last_any_started_time.has_value()) {
- base::TimeDelta elapsed_time_any_started = now - *last_any_started_time;
- if (elapsed_time_any_started < kMinimumTimeBetweenAnySurveyStarts) {
- UMA_HISTOGRAM_ENUMERATION(
- kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent);
- return false;
- }
- }
- }
- return true;
-}
-
-void HatsServiceDesktop::RecordSurveyAsShown(std::string trigger_id) {
- // Record the trigger associated with the trigger_id. This is recorded
- // instead of the trigger ID itself, as the ID is specific to individual
- // survey versions. There should be a cooldown before a user is prompted to
- // take a survey from the same trigger, regardless of whether the survey was
- // updated.
- auto trigger_survey_config =
- base::ranges::find(survey_configs_by_triggers_, trigger_id,
- [](const SurveyConfigs::value_type& pair) {
- return pair.second.trigger_id;
- });
-
- DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
- std::string trigger = trigger_survey_config->first;
-
- UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kYes);
-
- ScopedDictPrefUpdate update(profile()->GetPrefs(),
- prefs::kHatsSurveyMetadata);
- base::Value::Dict& pref_data = update.Get();
- pref_data.SetByDottedPath(
- GetMajorVersionPath(trigger),
- static_cast<int>(version_info::GetVersion().components()[0]));
- pref_data.SetByDottedPath(GetLastSurveyStartedTime(trigger),
- base::TimeToValue(base::Time::Now()));
- pref_data.SetByDottedPath(kAnyLastSurveyStartedTimePath,
- base::TimeToValue(base::Time::Now()));
-}
-
-void HatsServiceDesktop::HatsNextDialogClosed() {
- hats_next_dialog_exists_ = false;
-}
-
-void HatsServiceDesktop::RemoveTask(const DelayedSurveyTask& task) {
- pending_tasks_.erase(task);
-}
-
-bool HatsServiceDesktop::ShouldShowSurvey(const std::string& trigger) const {
- if (!CanShowSurvey(trigger)) {
- return false;
- }
-
- auto probability = survey_configs_by_triggers_.at(trigger).probability;
- bool should_show_survey = base::RandDouble() < probability;
- if (!should_show_survey) {
- UMA_HISTOGRAM_ENUMERATION(
- kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoBelowProbabilityLimit);
- }
-
- return should_show_survey;
-}
-
-void HatsServiceDesktop::LaunchSurveyForBrowser(
- Browser* browser,
- const std::string& trigger,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data) {
- if (!browser ||
- (!browser->is_type_normal() && !browser->is_type_devtools()) ||
- !profiles::IsRegularOrGuestSession(browser)) {
- // Never show HaTS bubble for Incognito mode.
- UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoNotRegularBrowser);
- if (!failure_callback.is_null()) {
- std::move(failure_callback).Run();
- }
- return;
- }
- if (IncognitoModePrefs::GetAvailability(profile()->GetPrefs()) ==
- policy::IncognitoModeAvailability::kDisabled) {
- // Incognito mode needs to be enabled to create an off-the-record profile
- // for HaTS dialog.
- UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
- ShouldShowSurveyReasons::kNoIncognitoDisabled);
- if (!failure_callback.is_null()) {
- std::move(failure_callback).Run();
- }
- return;
- }
- // Checking survey's status could be costly due to a network request, so
- // we check it at the last.
- CheckSurveyStatusAndMaybeShow(browser, trigger, std::move(success_callback),
- std::move(failure_callback),
- product_specific_bits_data,
- product_specific_string_data);
-}
-
-void HatsServiceDesktop::CheckSurveyStatusAndMaybeShow(
- Browser* browser,
- const std::string& trigger,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data) {
- // Check the survey status in profile first.
- // We record the survey's over capacity information in user profile to avoid
- // duplicated checks since the survey won't change once it is full.
- const base::Value::Dict& pref_data =
- profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
- absl::optional<int> is_full =
- pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
- if (is_full.has_value() && is_full) {
- if (!failure_callback.is_null()) {
- std::move(failure_callback).Run();
- }
- return;
- }
-
- CHECK(survey_configs_by_triggers_.find(trigger) !=
- survey_configs_by_triggers_.end());
- auto survey_config = survey_configs_by_triggers_[trigger];
-
- // Check that the |product_specific_bits_data| matches the fields for this
- // trigger. If fields are set for a trigger, they must be provided.
- CHECK_EQ(product_specific_bits_data.size(),
- survey_config.product_specific_bits_data_fields.size());
- for (auto field_value : product_specific_bits_data) {
- CHECK(base::Contains(survey_config.product_specific_bits_data_fields,
- field_value.first));
- }
-
- // Check that the |product_specific_string_data| matches the fields for this
- // trigger. If fields are set for a trigger, they must be provided.
- CHECK_EQ(product_specific_string_data.size(),
- survey_config.product_specific_string_data_fields.size());
- for (auto field_value : product_specific_string_data) {
- CHECK(base::Contains(survey_config.product_specific_string_data_fields,
- field_value.first));
- }
-
- // As soon as the HaTS Next dialog is created it will attempt to contact
- // the HaTS servers to check for a survey.
- ScopedDictPrefUpdate update(profile()->GetPrefs(),
- prefs::kHatsSurveyMetadata);
- update->SetByDottedPath(GetLastSurveyCheckTime(trigger),
- base::TimeToValue(base::Time::Now()));
-
- DCHECK(!hats_next_dialog_exists_);
- browser->window()->ShowHatsDialog(
- survey_configs_by_triggers_[trigger].trigger_id,
- std::move(success_callback), std::move(failure_callback),
- product_specific_bits_data, product_specific_string_data);
- hats_next_dialog_exists_ = true;
-}
diff --git a/chrome/browser/ui/hats/hats_service_desktop.h b/chrome/browser/ui/hats/hats_service_desktop.h
deleted file mode 100644
index a8824b0..0000000
--- a/chrome/browser/ui/hats/hats_service_desktop.h
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
-#define CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
-
-#include <set>
-#include <string>
-
-#include "base/functional/callback.h"
-#include "base/functional/callback_forward.h"
-#include "base/functional/callback_helpers.h"
-#include "base/gtest_prod_util.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "content/public/browser/web_contents_observer.h"
-
-class Browser;
-
-// Key-value mapping type for survey's product specific bits data.
-typedef std::map<std::string, bool> SurveyBitsData;
-
-// Key-value mapping type for survey's product specific string data.
-typedef std::map<std::string, std::string> SurveyStringData;
-
-// The name of the histogram which records if a survey was shown, or if not, the
-// reason why not.
-extern const char kHatsShouldShowSurveyReasonHistogram[];
-
-// This class provides the client side logic for determining if a
-// survey should be shown for any trigger based on input from a finch
-// configuration. It is created on a per profile basis.
-class HatsServiceDesktop : public HatsService {
- public:
- class DelayedSurveyTask : public content::WebContentsObserver {
- public:
- DelayedSurveyTask(HatsServiceDesktop* hats_service,
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- bool require_same_origin,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback);
-
- // Not copyable or movable
- DelayedSurveyTask(const DelayedSurveyTask&) = delete;
- DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
-
- ~DelayedSurveyTask() override;
-
- // Asks |hats_service_| to launch the survey with id |trigger_| for tab
- // |web_contents_|.
- void Launch();
-
- // content::WebContentsObserver
- void DidFinishNavigation(
- content::NavigationHandle* navigation_handle) override;
- void WebContentsDestroyed() override;
-
- // Returns a weak pointer to this object.
- virtual base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
-
- bool operator<(const HatsServiceDesktop::DelayedSurveyTask& other) const {
- return trigger_ < other.trigger_ ? true
- : web_contents() < other.web_contents();
- }
-
- private:
- raw_ptr<HatsServiceDesktop> hats_service_;
-
- std::string trigger_;
- SurveyBitsData product_specific_bits_data_;
- SurveyStringData product_specific_string_data_;
- bool require_same_origin_;
- base::OnceClosure success_callback_;
- base::OnceClosure failure_callback_;
- base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
- };
-
- // These values are persisted to logs. Entries should not be renumbered and
- // numeric values should never be reused.
- enum class ShouldShowSurveyReasons {
- kYes = 0,
- kNoOffline = 1,
- kNoLastSessionCrashed = 2,
- kNoReceivedSurveyInCurrentMilestone = 3,
- kNoProfileTooNew = 4,
- kNoLastSurveyTooRecent = 5,
- kNoBelowProbabilityLimit = 6,
- kNoTriggerStringMismatch = 7,
- kNoNotRegularBrowser = 8,
- kNoIncognitoDisabled = 9,
- kNoCookiesBlocked = 10, // Unused.
- kNoThirdPartyCookiesBlocked = 11, // Unused.
- kNoSurveyUnreachable = 12,
- kNoSurveyOverCapacity = 13,
- kNoSurveyAlreadyInProgress = 14,
- kNoAnyLastSurveyTooRecent = 15,
- kNoRejectedByHatsService = 16,
- kMaxValue = kNoRejectedByHatsService,
- };
-
- explicit HatsServiceDesktop(Profile* profile);
-
- HatsServiceDesktop(const HatsServiceDesktop&) = delete;
- HatsServiceDesktop& operator=(const HatsServiceDesktop&) = delete;
-
- ~HatsServiceDesktop() override;
-
- static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
-
- void LaunchSurvey(
- const std::string& trigger,
- base::OnceClosure success_callback = base::DoNothing(),
- base::OnceClosure failure_callback = base::DoNothing(),
- const SurveyBitsData& product_specific_bits_data = {},
- const SurveyStringData& product_specific_string_data = {}) override;
-
- void LaunchSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data,
- base::OnceClosure success_callback = base::DoNothing(),
- base::OnceClosure failure_callback = base::DoNothing()) override;
-
- bool LaunchDelayedSurvey(
- const std::string& trigger,
- int timeout_ms,
- const SurveyBitsData& product_specific_bits_data = {},
- const SurveyStringData& product_specific_string_data = {}) override;
-
- bool LaunchDelayedSurveyForWebContents(
- const std::string& trigger,
- content::WebContents* web_contents,
- int timeout_ms,
- const SurveyBitsData& product_specific_bits_data = {},
- const SurveyStringData& product_specific_string_data = {},
- bool require_same_origin = false,
- base::OnceClosure success_callback = base::DoNothing(),
- base::OnceClosure failure_callback = base::DoNothing()) override;
-
- void SetSurveyMetadataForTesting(const HatsService::SurveyMetadata& metadata);
- void GetSurveyMetadataForTesting(HatsService::SurveyMetadata* metadata) const;
-
- bool HasPendingTasks();
-
- bool CanShowSurvey(const std::string& trigger) const override;
-
- bool CanShowAnySurvey(bool user_prompted) const override;
-
- void RecordSurveyAsShown(std::string trigger_id) override;
-
- // Indicates to the service that the HaTS Next dialog has been closed.
- virtual void HatsNextDialogClosed();
-
- // Returns whether a HaTS Next dialog currently exists, regardless of whether
- // it is being shown or not.
- bool hats_next_dialog_exists_for_testing() {
- return hats_next_dialog_exists_;
- }
-
- protected:
- private:
- FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
-
- // Remove |task| from the set of |pending_tasks_|.
- void RemoveTask(const DelayedSurveyTask& task);
-
- // Returns true is the survey trigger specified should be shown.
- bool ShouldShowSurvey(const std::string& trigger) const;
-
- void LaunchSurveyForBrowser(
- Browser* browser,
- const std::string& trigger,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data);
-
- // Check whether the survey is reachable and under capacity and show it.
- // |success_callback| is called when the survey is shown to the user.
- // |failure_callback| is called if the survey does not launch for any reason.
- // The matches of field names with the `SurveyConfig` are CHECK
- // enforced.
- void CheckSurveyStatusAndMaybeShow(
- Browser* browser,
- const std::string& trigger,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback,
- const SurveyBitsData& product_specific_bits_data,
- const SurveyStringData& product_specific_string_data);
-
- std::set<DelayedSurveyTask> pending_tasks_;
-
- // Whether a HaTS Next dialog currently exists (regardless of whether it
- // is being shown to the user).
- bool hats_next_dialog_exists_ = false;
-
- base::WeakPtrFactory<HatsServiceDesktop> weak_ptr_factory_{this};
-};
-
-#endif // CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
diff --git a/chrome/browser/ui/hats/hats_service_factory.cc b/chrome/browser/ui/hats/hats_service_factory.cc
index 59b45bc..6d3110d 100644
--- a/chrome/browser/ui/hats/hats_service_factory.cc
+++ b/chrome/browser/ui/hats/hats_service_factory.cc
@@ -7,14 +7,12 @@
#include "base/no_destructor.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/ui/android/hats/hats_service_android.h"
#include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
// static
HatsService* HatsServiceFactory::GetForProfile(Profile* profile,
bool create_if_necessary) {
- return static_cast<HatsServiceAndroid*>(
+ return static_cast<HatsService*>(
GetInstance()->GetServiceForBrowserContext(profile, create_if_necessary));
}
@@ -34,11 +32,7 @@
HatsServiceFactory::BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const {
Profile* profile = Profile::FromBrowserContext(context);
-#if BUILDFLAG(IS_ANDROID)
- return std::make_unique<HatsServiceAndroid>(profile);
-#else
- return std::make_unique<HatsServiceDesktop>(profile);
-#endif
+ return std::make_unique<HatsService>(profile);
}
HatsServiceFactory::~HatsServiceFactory() = default;
diff --git a/chrome/browser/ui/hats/hats_service_factory.h b/chrome/browser/ui/hats/hats_service_factory.h
index d2b67df..1227b43 100644
--- a/chrome/browser/ui/hats/hats_service_factory.h
+++ b/chrome/browser/ui/hats/hats_service_factory.h
@@ -7,8 +7,8 @@
#include "base/no_destructor.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
-#include "chrome/browser/ui/hats/hats_service.h"
+class HatsService;
class Profile;
class HatsServiceFactory : public ProfileKeyedServiceFactory {
@@ -17,7 +17,6 @@
HatsServiceFactory& operator=(const HatsServiceFactory&) = delete;
static HatsService* GetForProfile(Profile* profile, bool create_if_necessary);
-
static HatsServiceFactory* GetInstance();
private:
diff --git a/chrome/browser/ui/hats/mock_hats_service.cc b/chrome/browser/ui/hats/mock_hats_service.cc
index 16e7f95c..d0d09992 100644
--- a/chrome/browser/ui/hats/mock_hats_service.cc
+++ b/chrome/browser/ui/hats/mock_hats_service.cc
@@ -12,8 +12,7 @@
using ::testing::NiceMock;
-MockHatsService::MockHatsService(Profile* profile)
- : HatsServiceDesktop(profile) {}
+MockHatsService::MockHatsService(Profile* profile) : HatsService(profile) {}
MockHatsService::~MockHatsService() = default;
diff --git a/chrome/browser/ui/hats/mock_hats_service.h b/chrome/browser/ui/hats/mock_hats_service.h
index 73caaf5..a20db5b7 100644
--- a/chrome/browser/ui/hats/mock_hats_service.h
+++ b/chrome/browser/ui/hats/mock_hats_service.h
@@ -7,8 +7,7 @@
#include <memory>
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
-#include "content/public/browser/web_contents.h"
+#include "chrome/browser/ui/hats/hats_service.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace content {
@@ -18,7 +17,7 @@
class KeyedService;
class Profile;
-class MockHatsService : public HatsServiceDesktop {
+class MockHatsService : public HatsService {
public:
explicit MockHatsService(Profile* profile);
~MockHatsService() override;
@@ -31,15 +30,6 @@
(const SurveyBitsData&)survey_specific_bits_data,
(const SurveyStringData&)survey_specific_string_data),
(override));
- MOCK_METHOD(void,
- LaunchSurveyForWebContents,
- (const std::string& trigger,
- (content::WebContents*)web_contents,
- (const SurveyBitsData&)survey_specific_bits_data,
- (const SurveyStringData&)survey_specific_string_data,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback),
- (override));
MOCK_METHOD(bool,
LaunchDelayedSurvey,
(const std::string& trigger,
@@ -54,9 +44,7 @@
int timeout_ms,
(const SurveyBitsData&)survey_specific_bits_data,
(const SurveyStringData&)survey_specific_string_data,
- bool require_same_origin,
- base::OnceClosure success_callback,
- base::OnceClosure failure_callback),
+ bool require_same_origin),
(override));
MOCK_METHOD(void, HatsNextDialogClosed, (), (override));
MOCK_METHOD(bool, CanShowAnySurvey, (bool user_prompted), (const override));
diff --git a/chrome/browser/ui/hats/survey_config.cc b/chrome/browser/ui/hats/survey_config.cc
index 1b91f555..23f63ed 100644
--- a/chrome/browser/ui/hats/survey_config.cc
+++ b/chrome/browser/ui/hats/survey_config.cc
@@ -3,16 +3,15 @@
// found in the LICENSE file.
#include "survey_config.h"
-
#include "base/feature_list.h"
#include "base/features.h"
-#include "components/permissions/features.h"
-#include "components/permissions/permission_hats_trigger_helper.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/common/chrome_features.h"
#include "components/performance_manager/public/features.h" // nogncheck
#include "components/permissions/constants.h" // nogncheck
+#include "components/permissions/features.h" // nogncheck
+#include "components/permissions/permission_hats_trigger_helper.h" // nogncheck
#include "components/safe_browsing/core/common/features.h" // nogncheck
#include "components/safe_browsing/core/common/safebrowsing_constants.h" // nogncheck
#else
@@ -42,6 +41,7 @@
// The permission prompt trigger permits configuring multiple triggers
// simultaneously. Each trigger increments a counter at the end -->
// "permission-prompt0", "permission-prompt1", ...
+constexpr char kHatsSurveyTriggerPermissionsPrompt[] = "permissions-prompt";
constexpr char kHatsSurveyTriggerPrivacyGuide[] = "privacy-guide";
constexpr char kHatsSurveyTriggerPrivacySandbox[] = "privacy-sandbox";
constexpr char kHatsSurveyTriggerRedWarning[] = "red-warning";
@@ -116,8 +116,6 @@
constexpr char kHatsNextSurveyTriggerIDTesting[] =
"HLpeYy5Av0ugnJ3q1cK0XzzA8UHv";
-constexpr char kHatsSurveyTriggerPermissionsPrompt[] = "permissions-prompt";
-
namespace {
constexpr char kHatsSurveyProbability[] = "probability";
@@ -141,29 +139,6 @@
default_survey.product_specific_string_data_fields = {"Test Field 3"};
survey_configs.emplace_back(default_survey);
- // Permissions surveys.
- for (auto& trigger_id_pair : permissions::PermissionHatsTriggerHelper::
- GetPermissionPromptTriggerIdPairs(
- kHatsSurveyTriggerPermissionsPrompt)) {
- // trigger_id_pair has structure <trigger_name, trigger_id>. trigger_name is
- // a unique name used by the HaTS service integration, and trigger_id is an
- // ID that specifies a survey in the Listnr backend.
- survey_configs.emplace_back(
- &permissions::features::kPermissionsPromptSurvey, trigger_id_pair.first,
- trigger_id_pair.second,
- std::vector<std::string>{
- permissions::kPermissionsPromptSurveyHadGestureKey},
- std::vector<std::string>{
- permissions::kPermissionsPromptSurveyPromptDispositionKey,
- permissions::kPermissionsPromptSurveyPromptDispositionReasonKey,
- permissions::kPermissionsPromptSurveyActionKey,
- permissions::kPermissionsPromptSurveyRequestTypeKey,
- permissions::kPermissionsPromptSurveyReleaseChannelKey,
- permissions::kPermissionsPromptSurveyDisplayTimeKey,
- permissions::kPermissionPromptSurveyOneTimePromptsDecidedBucketKey,
- permissions::kPermissionPromptSurveyUrlKey});
- }
-
#if !BUILDFLAG(IS_ANDROID)
// Dev tools surveys.
survey_configs.emplace_back(&features::kHaTSDesktopDevToolsIssuesCOEP,
@@ -458,6 +433,29 @@
&features::kHappinessTrackingSurveysForDesktopWhatsNew,
kHatsSurveyTriggerWhatsNew);
+ // Permissions surveys.
+ for (auto& trigger_id_pair : permissions::PermissionHatsTriggerHelper::
+ GetPermissionPromptTriggerIdPairs(
+ kHatsSurveyTriggerPermissionsPrompt)) {
+ // trigger_id_pair has structure <trigger_name, trigger_id>. trigger_name is
+ // a unique name used by the HaTS service integration, and trigger_id is an
+ // ID that specifies a survey in the Listnr backend.
+ survey_configs.emplace_back(
+ &permissions::features::kPermissionsPromptSurvey, trigger_id_pair.first,
+ trigger_id_pair.second,
+ std::vector<std::string>{
+ permissions::kPermissionsPromptSurveyHadGestureKey},
+ std::vector<std::string>{
+ permissions::kPermissionsPromptSurveyPromptDispositionKey,
+ permissions::kPermissionsPromptSurveyPromptDispositionReasonKey,
+ permissions::kPermissionsPromptSurveyActionKey,
+ permissions::kPermissionsPromptSurveyRequestTypeKey,
+ permissions::kPermissionsPromptSurveyReleaseChannelKey,
+ permissions::kPermissionsPromptSurveyDisplayTimeKey,
+ permissions::kPermissionPromptSurveyOneTimePromptsDecidedBucketKey,
+ permissions::kPermissionPromptSurveyUrlKey});
+ }
+
// Performance Controls surveys.
survey_configs.emplace_back(
&performance_manager::features::kPerformanceControlsPerformanceSurvey,
diff --git a/chrome/browser/ui/hats/survey_config.h b/chrome/browser/ui/hats/survey_config.h
index e83e67c..734e2bc7 100644
--- a/chrome/browser/ui/hats/survey_config.h
+++ b/chrome/browser/ui/hats/survey_config.h
@@ -30,6 +30,7 @@
extern const char kHatsSurveyTriggerPerformanceControlsBatteryPerformance[];
extern const char kHatsSurveyTriggerPerformanceControlsHighEfficiencyOptOut[];
extern const char kHatsSurveyTriggerPerformanceControlsBatterySaverOptOut[];
+extern const char kHatsSurveyTriggerPermissionsPrompt[];
extern const char kHatsSurveyTriggerPrivacyGuide[];
extern const char kHatsSurveyTriggerPrivacySandbox[];
extern const char kHatsSurveyTriggerRedWarning[];
@@ -73,8 +74,6 @@
extern const char kHatsSurveyTriggerAndroidStartupSurvey[];
#endif
-extern const char kHatsSurveyTriggerPermissionsPrompt[];
-
extern const char kHatsSurveyTriggerTesting[];
// The Trigger ID for a test HaTS Next survey which is available for testing
// and demo purposes when the migration feature flag is enabled.
diff --git a/chrome/browser/ui/views/hats/hats_browsertest.cc b/chrome/browser/ui/views/hats/hats_browsertest.cc
index e5431d9..35f9eb8 100644
--- a/chrome/browser/ui/views/hats/hats_browsertest.cc
+++ b/chrome/browser/ui/views/hats/hats_browsertest.cc
@@ -18,7 +18,7 @@
#include "chrome/browser/devtools/devtools_window_testing.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
+#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/hats/mock_hats_service.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
@@ -49,7 +49,7 @@
// The locale expected by the test survey. This value is checked in
// hats_next_mock.html for tests that expect a loaded response.
-const char kTestLocale[] = "lt";
+const std::string kTestLocale = "lt";
} // namespace
@@ -212,7 +212,7 @@
// Because no loaded state was provided, only a rejection should be recorded.
histogram_tester.ExpectUniqueSample(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::kNoRejectedByHatsService, 1);
+ HatsService::ShouldShowSurveyReasons::kNoRejectedByHatsService, 1);
}
// Test that a survey which first reports as loaded, then reports closure, only
@@ -238,7 +238,7 @@
// The only recorded sample should indicate that the survey was shown.
histogram_tester.ExpectUniqueSample(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::kYes, 1);
+ HatsService::ShouldShowSurveyReasons::kYes, 1);
}
// Test that if the survey does not indicate it is ready for display before the
@@ -259,7 +259,7 @@
EXPECT_EQ(1, failure_count);
histogram_tester.ExpectUniqueSample(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::kNoSurveyUnreachable, 1);
+ HatsService::ShouldShowSurveyReasons::kNoSurveyUnreachable, 1);
}
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, UnknownURLFragment) {
diff --git a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
index 8b1ebe3..59849e7b 100644
--- a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
+++ b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
@@ -8,7 +8,6 @@
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
-#include "base/notreached.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/devtools/devtools_window.h"
@@ -16,7 +15,6 @@
#include "chrome/browser/profiles/profile_destroyer.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/app_menu_button.h"
@@ -215,7 +213,7 @@
// such as a survey still being in test mode, or an invalid survey ID.
base::UmaHistogramEnumeration(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::kNoRejectedByHatsService);
+ HatsService::ShouldShowSurveyReasons::kNoRejectedByHatsService);
std::move(failure_callback_).Run();
}
CloseWidget();
@@ -286,16 +284,12 @@
}
HatsNextWebDialog::~HatsNextWebDialog() {
-#if IS_ANDROID
- NOTIMPLEMENTED(); // This class is for desktop only. Enforce assumption.
-#endif
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (otr_profile_) {
otr_profile_->RemoveObserver(this);
ProfileDestroyer::DestroyOTRProfileWhenAppropriate(otr_profile_);
}
- HatsServiceDesktop* service = static_cast<HatsServiceDesktop*>(
- HatsServiceFactory::GetForProfile(browser_->profile(), false));
+ auto* service = HatsServiceFactory::GetForProfile(browser_->profile(), false);
DCHECK(service);
service->HatsNextDialogClosed();
@@ -346,7 +340,7 @@
void HatsNextWebDialog::LoadTimedOut() {
base::UmaHistogramEnumeration(
kHatsShouldShowSurveyReasonHistogram,
- HatsServiceDesktop::ShouldShowSurveyReasons::kNoSurveyUnreachable);
+ HatsService::ShouldShowSurveyReasons::kNoSurveyUnreachable);
CloseWidget();
std::move(failure_callback_).Run();
}
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
index 45b419a..9d07fc7 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
@@ -960,7 +960,7 @@
{});
EXPECT_CALL(*mock_hats_service(),
- LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
+ LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
.Times(1);
const std::vector<std::string> module_ids = {"recipe_tasks", "cart"};
handler_->OnModulesLoadedWithData(module_ids);
@@ -977,7 +977,7 @@
{});
EXPECT_CALL(*mock_hats_service(),
- LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
+ LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
.Times(0);
const std::vector<std::string> module_ids = {"recipe_tasks"};
handler_->OnModulesLoadedWithData(module_ids);
diff --git a/chrome/browser/ui/webui/settings/hats_handler_unittest.cc b/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
index f9d5cd2..ca7cef7a 100644
--- a/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
@@ -110,7 +110,7 @@
EXPECT_CALL(*mock_hats_service_,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerSettingsPrivacy, web_contents(), 15000,
- expected_product_specific_data, _, true, _, _))
+ expected_product_specific_data, _, true))
.Times(2);
base::Value::List args;
args.Append(
@@ -128,7 +128,7 @@
// Check that completing a privacy guide triggers a privacy guide hats.
EXPECT_CALL(*mock_hats_service_, LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerPrivacyGuide,
- web_contents(), 15000, _, _, true, _, _))
+ web_contents(), 15000, _, _, true))
.Times(1);
base::Value::List args;
args.Append(static_cast<int>(
@@ -159,7 +159,7 @@
EXPECT_CALL(*mock_hats_service_,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerSettingsSecurity, web_contents(), 15000, _,
- expected_product_specific_data, true, _, _))
+ expected_product_specific_data, true))
.Times(1);
base::Value::List args;
@@ -196,7 +196,7 @@
// Enable targeting for users who have not seen the Privacy Sandbox page and
// ensure the handler does not attempt to launch the survey.
EXPECT_CALL(*mock_hats_service_,
- LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
+ LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
.Times(0);
profile()->GetPrefs()->SetBoolean(prefs::kPrivacySandboxPageViewed, true);
@@ -221,7 +221,7 @@
EXPECT_CALL(*mock_hats_service_,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerPrivacySandbox, web_contents(), 10000,
- expected_product_specific_data, _, true, _, _));
+ expected_product_specific_data, _, true));
base::Value::List args;
args.Append(static_cast<int>(
HatsHandler::TrustSafetyInteraction::OPENED_PRIVACY_SANDBOX));
@@ -302,7 +302,7 @@
EXPECT_CALL(*mock_hats_service_,
LaunchDelayedSurveyForWebContents(
survey, web_contents(), 20000,
- expected_product_specific_data, _, true, _, _));
+ expected_product_specific_data, _, true));
base::Value::List args;
args.Append(static_cast<int>(interaction));
handler()->HandleTrustSafetyInteractionOccurred(args);
diff --git a/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc b/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
index 8489f63..37a5250 100644
--- a/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
@@ -56,7 +56,7 @@
browser()->profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service_,
LaunchDelayedSurveyForWebContents(kHatsSurveyTriggerSettings, _,
- _, _, _, _, _, _));
+ _, _, _, _));
ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));
base::RunLoop().RunUntilIdle();
}