1 // Copyright 2018 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_ 6 #define CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_ 7 8 #include <memory> 9 #include <set> 10 #include <string> 11 12 #include "base/callback.h" 13 #include "base/containers/flat_map.h" 14 #include "base/macros.h" 15 #include "base/memory/weak_ptr.h" 16 #include "base/optional.h" 17 #include "base/time/time.h" 18 #include "chrome/browser/ui/hats/hats_survey_status_checker.h" 19 #include "components/keyed_service/core/keyed_service.h" 20 #include "content/public/browser/web_contents_observer.h" 21 22 namespace content { 23 class WebContents; 24 } 25 26 class Browser; 27 class PrefRegistrySimple; 28 class Profile; 29 30 // Trigger identifiers currently used; duplicates not allowed. 31 extern const char kHatsSurveyTriggerTesting[]; 32 extern const char kHatsSurveyTriggerSatisfaction[]; 33 extern const char kHatsSurveyTriggerSettings[]; 34 extern const char kHatsSurveyTriggerSettingsPrivacy[]; 35 extern const char kHatsSurveyTriggerDevToolsIssuesCookiesSameSite[]; 36 37 // The Trigger ID for a test HaTS Next survey which is available for testing 38 // and demo purposes when the migration feature flag is enabled. 39 extern const char kHatsNextSurveyTriggerIDTesting[]; 40 41 // The name of the histogram which records if a survey was shown, or if not, the 42 // reason why not. 43 extern const char kHatsShouldShowSurveyReasonHistogram[]; 44 45 // This class provides the client side logic for determining if a 46 // survey should be shown for any trigger based on input from a finch 47 // configuration. It is created on a per profile basis. 48 class HatsService : public KeyedService { 49 public: 50 struct SurveyConfig { SurveyConfigSurveyConfig51 SurveyConfig(double probability, std::string en_site_id, bool user_prompted) 52 : probability_(probability), 53 en_site_id_(std::move(en_site_id)), 54 user_prompted_(user_prompted) {} 55 56 SurveyConfig() = default; 57 58 // Probability [0,1] of how likely a chosen user will see the survey. 59 double probability_; 60 61 // Site ID for the survey. 62 std::string en_site_id_; 63 64 // The survey will prompt every time because the user has explicitly decided 65 // to take the survey e.g. clicking a link. 66 bool user_prompted_; 67 }; 68 69 struct SurveyMetadata { 70 SurveyMetadata(); 71 ~SurveyMetadata(); 72 73 // Trigger specific metadata. 74 base::Optional<int> last_major_version; 75 base::Optional<base::Time> last_survey_started_time; 76 base::Optional<bool> is_survey_full; 77 base::Optional<base::Time> last_survey_check_time; 78 79 // Metadata affecting all triggers. 80 base::Optional<base::Time> any_last_survey_started_time; 81 }; 82 83 class DelayedSurveyTask : public content::WebContentsObserver { 84 public: 85 DelayedSurveyTask(HatsService* hats_service, 86 const std::string& trigger, 87 content::WebContents* web_contents); 88 89 // Not copyable or movable 90 DelayedSurveyTask(const DelayedSurveyTask&) = delete; 91 DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete; 92 93 ~DelayedSurveyTask() override; 94 95 // Asks |hats_service_| to launch the survey with id |trigger_| for tab 96 // |web_contents_|. 97 void Launch(); 98 99 // content::WebContentsObserver 100 void WebContentsDestroyed() override; 101 102 // Returns a weak pointer to this object. 103 base::WeakPtr<DelayedSurveyTask> GetWeakPtr(); 104 105 bool operator<(const HatsService::DelayedSurveyTask& other) const { 106 return trigger_ < other.trigger_ ? true 107 : web_contents() < other.web_contents(); 108 } 109 110 private: 111 HatsService* hats_service_; 112 std::string trigger_; 113 base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this}; 114 }; 115 116 // These values are persisted to logs. Entries should not be renumbered and 117 // numeric values should never be reused. 118 enum class ShouldShowSurveyReasons { 119 kYes = 0, 120 kNoOffline = 1, 121 kNoLastSessionCrashed = 2, 122 kNoReceivedSurveyInCurrentMilestone = 3, 123 kNoProfileTooNew = 4, 124 kNoLastSurveyTooRecent = 5, 125 kNoBelowProbabilityLimit = 6, 126 kNoTriggerStringMismatch = 7, 127 kNoNotRegularBrowser = 8, 128 kNoIncognitoDisabled = 9, 129 kNoCookiesBlocked = 10, // Unused. 130 kNoThirdPartyCookiesBlocked = 11, // Unused. 131 kNoSurveyUnreachable = 12, 132 kNoSurveyOverCapacity = 13, 133 kNoSurveyAlreadyInProgress = 14, 134 kNoAnyLastSurveyTooRecent = 15, 135 kNoRejectedByHatsService = 16, 136 kMaxValue = kNoRejectedByHatsService, 137 }; 138 139 ~HatsService() override; 140 141 explicit HatsService(Profile* profile); 142 143 static void RegisterProfilePrefs(PrefRegistrySimple* registry); 144 145 // Launches survey with identifier |trigger| if appropriate. 146 // |success_callback| is called when the survey is shown to the user. 147 // |failure_callback| is called if the survey does not launch for any reason. 148 virtual void LaunchSurvey( 149 const std::string& trigger, 150 base::OnceClosure success_callback = base::DoNothing(), 151 base::OnceClosure failure_callback = base::DoNothing()); 152 153 // Launches survey (with id |trigger|) with a timeout |timeout_ms| if 154 // appropriate. Survey will be shown at the active window/tab by the 155 // time of launching. Rejects (and returns false) if the underlying task 156 // posting fails. 157 virtual bool LaunchDelayedSurvey(const std::string& trigger, int timeout_ms); 158 159 // Launches survey (with id |trigger|) with a timeout |timeout_ms| for tab 160 // |web_contents| if appropriate. |web_contents| required to be non-nullptr. 161 // Launch is cancelled if |web_contents| killed before end of timeout. Launch 162 // is also cancelled if |web_contents| not visible at the time of launch. 163 // Rejects (and returns false) if there is already an identical delayed-task 164 // (same |trigger| and same |web_contents|) waiting to be fulfilled. Also 165 // rejects if the underlying task posting fails. 166 virtual bool LaunchDelayedSurveyForWebContents( 167 const std::string& trigger, 168 content::WebContents* web_contents, 169 int timeout_ms); 170 171 // Updates the user preferences to record that the survey associated with 172 // |survey_id| was shown to the user. |survey_id| is the unique_id provided 173 // to the HaTS Service to identify a survey. This is the trigger ID for HaTS 174 // Next, and the site ID for HaTS v1. 175 void RecordSurveyAsShown(std::string survey_id); 176 177 // Indicates to the service that the HaTS Next dialog has been closed. 178 // Virtual to allow mocking in tests. 179 virtual void HatsNextDialogClosed(); 180 181 void SetSurveyMetadataForTesting(const SurveyMetadata& metadata); 182 void GetSurveyMetadataForTesting(HatsService::SurveyMetadata* metadata) const; 183 void SetSurveyCheckerForTesting( 184 std::unique_ptr<HatsSurveyStatusChecker> checker); 185 bool HasPendingTasks(); 186 187 // Whether the survey specified by |trigger| can be shown to the user. This 188 // is a pre-check that calculates as many conditions as possible, but could 189 // still return a false positive due to client-side rate limiting, a change 190 // in network conditions, or intervening calls to this API. 191 bool CanShowSurvey(const std::string& trigger) const; 192 193 private: 194 friend class DelayedSurveyTask; 195 FRIEND_TEST_ALL_PREFIXES(HatsServiceHatsNext, SingleHatsNextDialog); 196 197 void LaunchSurveyForWebContents(const std::string& trigger, 198 content::WebContents* web_contents); 199 200 void LaunchSurveyForBrowser(Browser* browser, 201 const std::string& trigger, 202 base::OnceClosure success_callback, 203 base::OnceClosure failure_callback); 204 205 // Returns true is the survey trigger specified should be shown. 206 bool ShouldShowSurvey(const std::string& trigger) const; 207 208 // Check whether the survey is reachable and under capacity and show it. 209 // |success_callback| is called when the survey is shown to the user. 210 // |failure_callback| is called if the survey does not launch for any reason. 211 void CheckSurveyStatusAndMaybeShow(Browser* browser, 212 const std::string& trigger, 213 base::OnceClosure success_callback, 214 base::OnceClosure failure_callback); 215 216 // Callbacks for survey capacity checking. 217 void ShowSurvey(Browser* browser, const std::string& trigger); 218 219 void OnSurveyStatusError(const std::string& trigger, 220 HatsSurveyStatusChecker::Status error); 221 void RemoveTask(const DelayedSurveyTask& task); 222 223 // Profile associated with this service. 224 Profile* const profile_; 225 226 std::unique_ptr<HatsSurveyStatusChecker> checker_; 227 228 std::set<DelayedSurveyTask> pending_tasks_; 229 230 base::flat_map<std::string, SurveyConfig> survey_configs_by_triggers_; 231 232 // Whether a HaTS Next dialog currently exists (regardless of whether it 233 // is being shown to the user). 234 bool hats_next_dialog_exists_ = false; 235 236 base::WeakPtrFactory<HatsService> weak_ptr_factory_{this}; 237 238 DISALLOW_COPY_AND_ASSIGN(HatsService); 239 }; 240 241 #endif // CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_ 242