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