feature_promo_controller.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. // Copyright 2020 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. #ifndef COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_CONTROLLER_H_
  5. #define COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_CONTROLLER_H_
  6. #include <memory>
  7. #include <string>
  8. #include <vector>
  9. #include "base/auto_reset.h"
  10. #include "base/callback.h"
  11. #include "base/callback_list.h"
  12. #include "base/memory/raw_ptr.h"
  13. #include "base/memory/weak_ptr.h"
  14. #include "components/feature_engagement/public/tracker.h"
  15. #include "components/user_education/common/feature_promo_handle.h"
  16. #include "components/user_education/common/feature_promo_registry.h"
  17. #include "components/user_education/common/feature_promo_specification.h"
  18. #include "components/user_education/common/help_bubble.h"
  19. #include "components/user_education/common/help_bubble_params.h"
  20. #include "components/user_education/common/tutorial_identifier.h"
  21. namespace base {
  22. struct Feature;
  23. }
  24. namespace ui {
  25. class AcceleratorProvider;
  26. class TrackedElement;
  27. } // namespace ui
  28. // Declaring these in the global namespace for testing purposes.
  29. class BrowserFeaturePromoControllerTest;
  30. class FeaturePromoSnoozeInteractiveTest;
  31. namespace user_education {
  32. class FeaturePromoSnoozeService;
  33. class HelpBubbleFactoryRegistry;
  34. class TutorialService;
  35. // Mostly virtual base class for feature promos; used to mock the interface in
  36. // tests.
  37. class FeaturePromoController {
  38. public:
  39. using BubbleCloseCallback = base::OnceClosure;
  40. FeaturePromoController();
  41. FeaturePromoController(const FeaturePromoController& other) = delete;
  42. virtual ~FeaturePromoController();
  43. void operator=(const FeaturePromoController& other) = delete;
  44. // Starts the promo if possible. Returns whether it started.
  45. // |iph_feature| must be an IPH feature defined in
  46. // components/feature_engagement/public/feature_list.cc and registered
  47. // with |FeaturePromoRegistry|. Note that this is different than the
  48. // feature that the IPH is showing for.
  49. //
  50. // If the body text is parameterized, pass text replacements in
  51. // |body_text_replacements|.
  52. //
  53. // If a bubble was shown and |close_callback| was provided, it will be
  54. // called when the bubble closes. |close_callback| must be valid as
  55. // long as the bubble shows.
  56. //
  57. // For users that can't register their parameters with
  58. // FeaturePromoRegistry, see
  59. // |FeaturePromoControllerViews::MaybeShowPromoWithParams()|. Prefer
  60. // statically registering params with FeaturePromoRegistry and using
  61. // this method when possible.
  62. virtual bool MaybeShowPromo(
  63. const base::Feature& iph_feature,
  64. FeaturePromoSpecification::StringReplacements body_text_replacements = {},
  65. BubbleCloseCallback close_callback = BubbleCloseCallback()) = 0;
  66. // Returns whether a bubble is showing for the given promo. If
  67. // `include_continued_promos` is set, also returns true if a promo bubble has
  68. // been hidden with CloseBubbleAndContinuePromo() but the promo is still
  69. // active in the background.
  70. virtual bool IsPromoActive(const base::Feature& iph_feature,
  71. bool include_continued_promos) const = 0;
  72. // Starts a promo with the settings for skipping any logging or filtering
  73. // provided by the implementation for MaybeShowPromo.
  74. virtual bool MaybeShowPromoForDemoPage(
  75. const base::Feature* iph_feature,
  76. FeaturePromoSpecification::StringReplacements body_text_replacements = {},
  77. BubbleCloseCallback close_callback = BubbleCloseCallback()) = 0;
  78. // If a bubble is showing for |iph_feature| close it and end the
  79. // promo. Does nothing otherwise. Returns true if a bubble was closed
  80. // and false otherwise.
  81. //
  82. // Calling this has no effect if |CloseBubbleAndContinuePromo()| was
  83. // called for |iph_feature|.
  84. virtual bool CloseBubble(const base::Feature& iph_feature) = 0;
  85. // Like CloseBubble() but does not end the promo yet. The caller takes
  86. // ownership of the promo (e.g. to show a highlight in a menu or on a
  87. // button). The returned FeaturePromoHandle represents this ownership.
  88. virtual FeaturePromoHandle CloseBubbleAndContinuePromo(
  89. const base::Feature& iph_feature) = 0;
  90. // Returns a weak pointer to this object.
  91. virtual base::WeakPtr<FeaturePromoController> GetAsWeakPtr() = 0;
  92. protected:
  93. friend class FeaturePromoHandle;
  94. // Called when FeaturePromoHandle is destroyed to finish the promo.
  95. virtual void FinishContinuedPromo(const base::Feature& iph_feature) = 0;
  96. };
  97. // Manages display of in-product help promos. All IPH displays in Top
  98. // Chrome should go through here.
  99. class FeaturePromoControllerCommon : public FeaturePromoController {
  100. public:
  101. using TestLock = std::unique_ptr<base::AutoReset<bool>>;
  102. FeaturePromoControllerCommon(
  103. feature_engagement::Tracker* feature_engagement_tracker,
  104. FeaturePromoRegistry* registry,
  105. HelpBubbleFactoryRegistry* help_bubble_registry,
  106. FeaturePromoSnoozeService* snooze_service,
  107. TutorialService* tutorial_service);
  108. ~FeaturePromoControllerCommon() override;
  109. // Only for security or privacy critical promos. Immediately shows a
  110. // promo with |params|, cancelling any normal promo and blocking any
  111. // further promos until it's done.
  112. //
  113. // Returns an ID that can be passed to CloseBubbleForCriticalPromo()
  114. // if successful. This can fail if another critical promo is showing.
  115. std::unique_ptr<HelpBubble> ShowCriticalPromo(
  116. const FeaturePromoSpecification& spec,
  117. ui::TrackedElement* anchor_element,
  118. FeaturePromoSpecification::StringReplacements body_text_replacements =
  119. {});
  120. // For systems where there are rendering issues of e.g. displaying the
  121. // omnibox and a bubble in the same region on the screen, dismisses a non-
  122. // critical promo bubble which overlaps a given screen region. Returns true
  123. // if a bubble is closed as a result.
  124. bool DismissNonCriticalBubbleInRegion(const gfx::Rect& screen_bounds);
  125. // Blocks further promos and closes any existing non-critical ones.
  126. [[nodiscard]] TestLock BlockPromosForTesting();
  127. // Returns the associated feature engagement tracker.
  128. feature_engagement::Tracker* feature_engagement_tracker() {
  129. return feature_engagement_tracker_;
  130. }
  131. // FeaturePromoController:
  132. bool MaybeShowPromo(
  133. const base::Feature& iph_feature,
  134. FeaturePromoSpecification::StringReplacements body_text_replacements = {},
  135. BubbleCloseCallback close_callback = BubbleCloseCallback()) override;
  136. bool IsPromoActive(const base::Feature& iph_feature,
  137. bool include_continued_promos = false) const override;
  138. bool MaybeShowPromoForDemoPage(
  139. const base::Feature* iph_feature,
  140. FeaturePromoSpecification::StringReplacements body_text_replacements = {},
  141. BubbleCloseCallback close_callback = BubbleCloseCallback()) override;
  142. bool CloseBubble(const base::Feature& iph_feature) override;
  143. FeaturePromoHandle CloseBubbleAndContinuePromo(
  144. const base::Feature& iph_feature) override;
  145. base::WeakPtr<FeaturePromoController> GetAsWeakPtr() override;
  146. HelpBubbleFactoryRegistry* bubble_factory_registry() {
  147. return bubble_factory_registry_;
  148. }
  149. HelpBubble* promo_bubble_for_testing() { return promo_bubble(); }
  150. HelpBubble* critical_promo_bubble_for_testing() {
  151. return critical_promo_bubble();
  152. }
  153. TutorialService* tutorial_service_for_testing() { return tutorial_service_; }
  154. // Blocks a check whether the IPH would be created in an inactive window or
  155. // app before showing the IPH. Intended for browser and unit tests.
  156. // The actual implementation of the check is in the platform-specific
  157. // implementation of CanShowPromo().
  158. [[nodiscard]] static TestLock BlockActiveWindowCheckForTesting();
  159. protected:
  160. friend BrowserFeaturePromoControllerTest;
  161. friend FeaturePromoSnoozeInteractiveTest;
  162. // For IPH not registered with |FeaturePromoRegistry|. Only use this
  163. // if it is infeasible to pre-register your IPH.
  164. bool MaybeShowPromoFromSpecification(
  165. const FeaturePromoSpecification& spec,
  166. ui::TrackedElement* anchor_element,
  167. FeaturePromoSpecification::StringReplacements body_text_replacements,
  168. BubbleCloseCallback close_callback);
  169. FeaturePromoSnoozeService* snooze_service() { return snooze_service_; }
  170. HelpBubble* promo_bubble() { return promo_bubble_.get(); }
  171. const HelpBubble* promo_bubble() const { return promo_bubble_.get(); }
  172. HelpBubble* critical_promo_bubble() { return critical_promo_bubble_; }
  173. const HelpBubble* critical_promo_bubble() const {
  174. return critical_promo_bubble_;
  175. }
  176. // Gets the context in which to locate the anchor view.
  177. virtual ui::ElementContext GetAnchorContext() const = 0;
  178. // Determine if the current context and anchor element allow showing a promo.
  179. // This lets us rule out e.g. inactive and incognito windows/apps for
  180. // non-critical promos.
  181. //
  182. // Note: Implementations should make sure to check
  183. // active_window_check_blocked().
  184. virtual bool CanShowPromo(ui::TrackedElement* anchor_element) const = 0;
  185. // Get the accelerator provider to use to look up accelerators.
  186. virtual const ui::AcceleratorProvider* GetAcceleratorProvider() const = 0;
  187. // Gets the alt text to use for body icons.
  188. virtual std::u16string GetBodyIconAltText() const = 0;
  189. // Gets the feature associated with prompting the user how to navigate to help
  190. // bubbles via the keyboard. It is its own promo, and will stop playing in
  191. // most cases when the user has made use of it enough times.
  192. //
  193. // If null is returned, no attempt will be made to play a prompt.
  194. virtual const base::Feature* GetScreenReaderPromptPromoFeature() const = 0;
  195. // This is the associated event with the promo feature above. The event is
  196. // recorded only if and when the promo is actually played to the user.
  197. virtual const char* GetScreenReaderPromptPromoEventName() const = 0;
  198. // Returns the special prompt to play with the initial bubble of a tutorial;
  199. // instead of the general navigation help prompt returned by
  200. // GetFocusHelpBubbleScreenReaderHint().
  201. virtual std::u16string GetTutorialScreenReaderHint() const = 0;
  202. // This method returns an appropriate prompt for promoting using a navigation
  203. // accelerator to focus the help bubble.
  204. virtual std::u16string GetFocusHelpBubbleScreenReaderHint(
  205. FeaturePromoSpecification::PromoType promo_type,
  206. ui::TrackedElement* anchor_element,
  207. bool is_critical_promo) const = 0;
  208. FeaturePromoRegistry* registry() { return registry_; }
  209. static bool active_window_check_blocked() {
  210. return active_window_check_blocked_;
  211. }
  212. private:
  213. // FeaturePromoController:
  214. void FinishContinuedPromo(const base::Feature& iph_feature) override;
  215. // Returns whether we can play a screen reader prompt for the "focus help
  216. // bubble" promo.
  217. // TODO(crbug.com/1258216): This must be called *before* we ask if the bubble
  218. // will show because a limitation in the current FE backend causes
  219. // ShouldTriggerHelpUI() to always return false if another promo is being
  220. // displayed. Once we have machinery to allow concurrency in the FE system
  221. // all of this logic can be rewritten.
  222. bool CheckScreenReaderPromptAvailable() const;
  223. // Method that creates the bubble for a feature promo. May return null if the
  224. // bubble cannot be shown.
  225. std::unique_ptr<HelpBubble> ShowPromoBubbleImpl(
  226. const FeaturePromoSpecification& spec,
  227. ui::TrackedElement* anchor_element,
  228. FeaturePromoSpecification::StringReplacements body_text_replacements,
  229. bool screen_reader_prompt_available,
  230. bool is_critical_promo);
  231. // Callback that cleans up a help bubble when it is closed.
  232. void OnHelpBubbleClosed(HelpBubble* bubble);
  233. // Callback for snoozed features.
  234. void OnHelpBubbleSnoozed(const base::Feature* feature);
  235. // Callback for snoozed tutorial features. .
  236. void OnTutorialHelpBubbleSnoozed(const base::Feature* iph_feature,
  237. TutorialIdentifier tutorial_id);
  238. // Callback when a feature's help bubble is dismissed by any means other than
  239. // snoozing (including "OK" or "Got it!" buttons).
  240. void OnHelpBubbleDismissed(const base::Feature* feature);
  241. // Callback when the dismiss button for IPH for tutorials is clicked.
  242. void OnTutorialHelpBubbleDismissed(const base::Feature* iph_feature,
  243. TutorialIdentifier tutorial_id);
  244. // Callback when a tutorial triggered from a promo is actually started.
  245. void OnTutorialStarted(const base::Feature* iph_feature,
  246. TutorialIdentifier tutorial_id);
  247. // Called when a tutorial launched via StartTutorial() completes.
  248. void OnTutorialComplete(const base::Feature* iph_feature);
  249. // Called when a tutorial launched via StartTutorial() aborts.
  250. void OnTutorialAborted(const base::Feature* iph_feature);
  251. // Called when the user opts to take a custom action.
  252. void OnCustomAction(const base::Feature* iph_feature,
  253. FeaturePromoSpecification::CustomActionCallback callback);
  254. // Create appropriate buttons for a snoozable promo on the current platform.
  255. std::vector<HelpBubbleButtonParams> CreateSnoozeButtons(
  256. const base::Feature& feature);
  257. // Create appropriate buttons for a tutorial promo on the current platform.
  258. std::vector<HelpBubbleButtonParams> CreateTutorialButtons(
  259. const base::Feature& feature,
  260. TutorialIdentifier tutorial_id);
  261. // Create appropriate buttons for a custom action promo.
  262. std::vector<HelpBubbleButtonParams> CreateCustomActionButtons(
  263. const base::Feature& feature,
  264. const std::u16string& custom_action_caption,
  265. FeaturePromoSpecification::CustomActionCallback custom_action_callback,
  266. bool custom_action_is_default);
  267. // The feature promo registry to use.
  268. const raw_ptr<FeaturePromoRegistry> registry_;
  269. // Non-null as long as a promo is showing. Corresponds to an IPH
  270. // feature registered with |feature_engagement_tracker_|.
  271. raw_ptr<const base::Feature> current_iph_feature_ = nullptr;
  272. bool continuing_after_bubble_closed_ = false;
  273. // The help bubble, if a feature promo bubble is showing.
  274. std::unique_ptr<HelpBubble> promo_bubble_;
  275. // Has a value if a critical promo is showing. If this has a value,
  276. // |current_iph_feature_| will usually be null. There is one edge case
  277. // where this may not be true: when a critical promo is requested
  278. // between a normal promo's CloseBubbleAndContinuePromo() call and its
  279. // end.
  280. raw_ptr<HelpBubble> critical_promo_bubble_ = nullptr;
  281. // Promo that is being continued during a tutorial launched from the promo
  282. // bubble.
  283. FeaturePromoHandle tutorial_promo_handle_;
  284. base::OnceClosure bubble_closed_callback_;
  285. base::CallbackListSubscription bubble_closed_subscription_;
  286. const raw_ptr<feature_engagement::Tracker> feature_engagement_tracker_;
  287. const raw_ptr<HelpBubbleFactoryRegistry> bubble_factory_registry_;
  288. const raw_ptr<FeaturePromoSnoozeService> snooze_service_;
  289. const raw_ptr<TutorialService> tutorial_service_;
  290. // When set to true, promos will never be shown.
  291. bool promos_blocked_for_testing_ = false;
  292. // In the case where the user education demo page wants to bypass the feature
  293. // engagement tracker, the current iph feature will be set and then checked
  294. // against to verify the right feature is bypassing. this page is located at
  295. // internals/user-education.
  296. raw_ptr<const base::Feature> iph_feature_bypassing_tracker_ = nullptr;
  297. base::WeakPtrFactory<FeaturePromoControllerCommon> weak_ptr_factory_{this};
  298. // Whether IPH should be allowed to show in an inactive window or app.
  299. // Should be checked in implementations of CanShowPromo(). Typically only
  300. // modified in tests.
  301. static bool active_window_check_blocked_;
  302. };
  303. } // namespace user_education
  304. #endif // COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_CONTROLLER_H_