feature_promo_controller.cc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  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. #include "components/user_education/common/feature_promo_controller.h"
  5. #include <string>
  6. #include "base/auto_reset.h"
  7. #include "base/bind.h"
  8. #include "base/callback_forward.h"
  9. #include "base/callback_list.h"
  10. #include "base/feature_list.h"
  11. #include "base/metrics/histogram_functions.h"
  12. #include "base/notreached.h"
  13. #include "components/feature_engagement/public/event_constants.h"
  14. #include "components/feature_engagement/public/feature_constants.h"
  15. #include "components/strings/grit/components_strings.h"
  16. #include "components/user_education/common/feature_promo_registry.h"
  17. #include "components/user_education/common/feature_promo_snooze_service.h"
  18. #include "components/user_education/common/help_bubble_factory_registry.h"
  19. #include "components/user_education/common/help_bubble_params.h"
  20. #include "components/user_education/common/tutorial.h"
  21. #include "components/user_education/common/tutorial_service.h"
  22. #include "ui/accessibility/ax_mode.h"
  23. #include "ui/accessibility/platform/ax_platform_node.h"
  24. #include "ui/base/interaction/element_tracker.h"
  25. #include "ui/base/l10n/l10n_util.h"
  26. namespace user_education {
  27. FeaturePromoController::FeaturePromoController() = default;
  28. FeaturePromoController::~FeaturePromoController() = default;
  29. FeaturePromoControllerCommon::FeaturePromoControllerCommon(
  30. feature_engagement::Tracker* feature_engagement_tracker,
  31. FeaturePromoRegistry* registry,
  32. HelpBubbleFactoryRegistry* help_bubble_registry,
  33. FeaturePromoSnoozeService* snooze_service,
  34. TutorialService* tutorial_service)
  35. : registry_(registry),
  36. feature_engagement_tracker_(feature_engagement_tracker),
  37. bubble_factory_registry_(help_bubble_registry),
  38. snooze_service_(snooze_service),
  39. tutorial_service_(tutorial_service) {
  40. DCHECK(feature_engagement_tracker_);
  41. DCHECK(bubble_factory_registry_);
  42. DCHECK(snooze_service_);
  43. }
  44. FeaturePromoControllerCommon::~FeaturePromoControllerCommon() = default;
  45. bool FeaturePromoControllerCommon::MaybeShowPromo(
  46. const base::Feature& iph_feature,
  47. FeaturePromoSpecification::StringReplacements body_text_replacements,
  48. BubbleCloseCallback close_callback) {
  49. const FeaturePromoSpecification* spec =
  50. registry()->GetParamsForFeature(iph_feature);
  51. if (!spec)
  52. return false;
  53. DCHECK_EQ(&iph_feature, spec->feature());
  54. DCHECK(spec->anchor_element_id());
  55. // Fetch the anchor element. For now, assume all elements are Views.
  56. ui::TrackedElement* const anchor_element =
  57. spec->GetAnchorElement(GetAnchorContext());
  58. if (!anchor_element)
  59. return false;
  60. return MaybeShowPromoFromSpecification(*spec, anchor_element,
  61. std::move(body_text_replacements),
  62. std::move(close_callback));
  63. }
  64. bool FeaturePromoControllerCommon::MaybeShowPromoForDemoPage(
  65. const base::Feature* iph_feature,
  66. FeaturePromoSpecification::StringReplacements body_text_replacements,
  67. BubbleCloseCallback close_callback) {
  68. if (current_iph_feature_ && promo_bubble_)
  69. CloseBubble(*current_iph_feature_);
  70. iph_feature_bypassing_tracker_ = iph_feature;
  71. bool showed_promo = MaybeShowPromo(*iph_feature);
  72. if (!showed_promo && iph_feature_bypassing_tracker_)
  73. iph_feature_bypassing_tracker_ = nullptr;
  74. return showed_promo;
  75. }
  76. bool FeaturePromoControllerCommon::MaybeShowPromoFromSpecification(
  77. const FeaturePromoSpecification& spec,
  78. ui::TrackedElement* anchor_element,
  79. FeaturePromoSpecification::StringReplacements body_text_replacements,
  80. BubbleCloseCallback close_callback) {
  81. CHECK(anchor_element);
  82. if (promos_blocked_for_testing_)
  83. return false;
  84. // A normal promo cannot show if a critical promo is displayed. These
  85. // are not registered with |tracker_| so check here.
  86. if (critical_promo_bubble_)
  87. return false;
  88. // Some contexts and anchors are not appropriate for showing normal promos.
  89. if (!CanShowPromo(anchor_element))
  90. return false;
  91. // Some checks should not be done in demo mode, because we absolutely want to
  92. // trigger the bubble if possible. Put any checks that should be bypassed in
  93. // demo mode in this block.
  94. const base::Feature* feature = spec.feature();
  95. bool feature_is_bypassing_tracker = feature == iph_feature_bypassing_tracker_;
  96. if (!(base::FeatureList::IsEnabled(feature_engagement::kIPHDemoMode) ||
  97. feature_is_bypassing_tracker) &&
  98. snooze_service_->IsBlocked(*feature))
  99. return false;
  100. // Can't show a standard promo if another help bubble is visible.
  101. if (bubble_factory_registry_->is_any_bubble_showing())
  102. return false;
  103. // TODO(crbug.com/1258216): Currently this must be called before
  104. // ShouldTriggerHelpUI() below. See bug for details.
  105. const bool screen_reader_available = CheckScreenReaderPromptAvailable();
  106. if (!feature_is_bypassing_tracker &&
  107. !feature_engagement_tracker_->ShouldTriggerHelpUI(*feature))
  108. return false;
  109. // If the tracker says we should trigger, but we have a promo
  110. // currently showing, there is a bug somewhere in here.
  111. DCHECK(!current_iph_feature_);
  112. current_iph_feature_ = feature;
  113. // Try to show the bubble and bail out if we cannot.
  114. promo_bubble_ = ShowPromoBubbleImpl(
  115. spec, anchor_element, std::move(body_text_replacements),
  116. screen_reader_available, /* is_critical_promo =*/false);
  117. if (!promo_bubble_) {
  118. current_iph_feature_ = nullptr;
  119. if (!feature_is_bypassing_tracker)
  120. feature_engagement_tracker_->Dismissed(*feature);
  121. return false;
  122. }
  123. bubble_closed_callback_ = std::move(close_callback);
  124. // Record count of previous snoozes when an IPH triggers.
  125. if (!feature_is_bypassing_tracker) {
  126. int snooze_count = snooze_service_->GetSnoozeCount(*feature);
  127. base::UmaHistogramExactLinear(
  128. "InProductHelp.Promos.SnoozeCountAtTrigger." +
  129. std::string(feature->name),
  130. snooze_count, FeaturePromoSnoozeService::kUmaMaxSnoozeCount);
  131. snooze_service_->OnPromoShown(*feature);
  132. }
  133. return true;
  134. }
  135. std::unique_ptr<HelpBubble> FeaturePromoControllerCommon::ShowCriticalPromo(
  136. const FeaturePromoSpecification& spec,
  137. ui::TrackedElement* anchor_element,
  138. FeaturePromoSpecification::StringReplacements body_text_replacements) {
  139. if (promos_blocked_for_testing_)
  140. return nullptr;
  141. // Don't preempt an existing critical promo.
  142. if (critical_promo_bubble_)
  143. return nullptr;
  144. // If a normal bubble is showing, close it. Won't affect a promo continued
  145. // after its bubble has closed.
  146. if (current_iph_feature_)
  147. CloseBubble(*current_iph_feature_);
  148. // Snooze and tutorial are not supported for critical promos.
  149. DCHECK_NE(FeaturePromoSpecification::PromoType::kSnooze, spec.promo_type());
  150. DCHECK_NE(FeaturePromoSpecification::PromoType::kTutorial, spec.promo_type());
  151. // Create the bubble.
  152. auto bubble = ShowPromoBubbleImpl(
  153. spec, anchor_element, std::move(body_text_replacements),
  154. CheckScreenReaderPromptAvailable(), /* is_critical_promo =*/true);
  155. critical_promo_bubble_ = bubble.get();
  156. return bubble;
  157. }
  158. bool FeaturePromoControllerCommon::IsPromoActive(
  159. const base::Feature& iph_feature,
  160. bool include_continued_promos) const {
  161. if (current_iph_feature_ != &iph_feature)
  162. return false;
  163. return include_continued_promos ||
  164. (promo_bubble_ && promo_bubble_->is_open());
  165. }
  166. bool FeaturePromoControllerCommon::CloseBubble(
  167. const base::Feature& iph_feature) {
  168. if (current_iph_feature_ != &iph_feature)
  169. return false;
  170. const bool was_open = promo_bubble_ && promo_bubble_->is_open();
  171. if (promo_bubble_)
  172. promo_bubble_->Close();
  173. if (!continuing_after_bubble_closed_ &&
  174. iph_feature_bypassing_tracker_ == &iph_feature) {
  175. iph_feature_bypassing_tracker_ = nullptr;
  176. }
  177. return was_open;
  178. }
  179. bool FeaturePromoControllerCommon::DismissNonCriticalBubbleInRegion(
  180. const gfx::Rect& screen_bounds) {
  181. if (promo_bubble_ && promo_bubble_->is_open() &&
  182. promo_bubble_->GetBoundsInScreen().Intersects(screen_bounds)) {
  183. const bool result = CloseBubble(*current_iph_feature_);
  184. DCHECK(result);
  185. return result;
  186. }
  187. return false;
  188. }
  189. FeaturePromoHandle FeaturePromoControllerCommon::CloseBubbleAndContinuePromo(
  190. const base::Feature& iph_feature) {
  191. DCHECK_EQ(current_iph_feature_, &iph_feature);
  192. continuing_after_bubble_closed_ = true;
  193. const bool result = CloseBubble(iph_feature);
  194. DCHECK(result);
  195. return FeaturePromoHandle(GetAsWeakPtr(), &iph_feature);
  196. }
  197. base::WeakPtr<FeaturePromoController>
  198. FeaturePromoControllerCommon::GetAsWeakPtr() {
  199. return weak_ptr_factory_.GetWeakPtr();
  200. }
  201. FeaturePromoControllerCommon::TestLock
  202. FeaturePromoControllerCommon::BlockPromosForTesting() {
  203. if (current_iph_feature_)
  204. CloseBubble(*current_iph_feature_);
  205. return std::make_unique<base::AutoReset<bool>>(&promos_blocked_for_testing_,
  206. true);
  207. }
  208. bool FeaturePromoControllerCommon::CheckScreenReaderPromptAvailable() const {
  209. if (!ui::AXPlatformNode::GetAccessibilityMode().has_mode(
  210. ui::AXMode::kScreenReader)) {
  211. return false;
  212. }
  213. // If we're in demo mode and screen reader is on, always play the demo
  214. // without querying the FE backend, since the backend will return false for
  215. // all promos other than the one that's being demoed. If we didn't have this
  216. // code the screen reader prompt would never play.
  217. if (base::FeatureList::IsEnabled(feature_engagement::kIPHDemoMode) ||
  218. iph_feature_bypassing_tracker_)
  219. return true;
  220. const base::Feature* const prompt_feature =
  221. GetScreenReaderPromptPromoFeature();
  222. if (!prompt_feature ||
  223. !feature_engagement_tracker_->ShouldTriggerHelpUI(*prompt_feature))
  224. return false;
  225. // TODO(crbug.com/1258216): Once we have our answer, immediately dismiss
  226. // so that this doesn't interfere with actually showing the bubble. This
  227. // dismiss can be moved elsewhere once we support concurrency.
  228. feature_engagement_tracker_->Dismissed(*prompt_feature);
  229. return true;
  230. }
  231. std::unique_ptr<HelpBubble> FeaturePromoControllerCommon::ShowPromoBubbleImpl(
  232. const FeaturePromoSpecification& spec,
  233. ui::TrackedElement* anchor_element,
  234. FeaturePromoSpecification::StringReplacements body_text_replacements,
  235. bool screen_reader_prompt_available,
  236. bool is_critical_promo) {
  237. HelpBubbleParams create_params;
  238. create_params.body_text = l10n_util::GetStringFUTF16(
  239. spec.bubble_body_string_id(), std::move(body_text_replacements), nullptr);
  240. create_params.title_text = spec.bubble_title_text();
  241. if (spec.screen_reader_string_id()) {
  242. create_params.screenreader_text =
  243. spec.screen_reader_accelerator()
  244. ? l10n_util::GetStringFUTF16(
  245. spec.screen_reader_string_id(),
  246. spec.screen_reader_accelerator()
  247. .GetAccelerator(GetAcceleratorProvider())
  248. .GetShortcutText())
  249. : l10n_util::GetStringUTF16(spec.screen_reader_string_id());
  250. }
  251. create_params.body_icon = spec.bubble_icon();
  252. if (spec.bubble_body_string_id())
  253. create_params.body_icon_alt_text = GetBodyIconAltText();
  254. create_params.arrow = spec.bubble_arrow();
  255. // Critical promos don't time out.
  256. if (is_critical_promo)
  257. create_params.timeout = base::Seconds(0);
  258. // Feature isn't present for some critical promos.
  259. if (spec.feature()) {
  260. create_params.dismiss_callback = base::BindOnce(
  261. &FeaturePromoControllerCommon::OnHelpBubbleDismissed,
  262. weak_ptr_factory_.GetWeakPtr(), base::Unretained(spec.feature()));
  263. }
  264. switch (spec.promo_type()) {
  265. case FeaturePromoSpecification::PromoType::kSnooze:
  266. CHECK(spec.feature());
  267. create_params.buttons = CreateSnoozeButtons(*spec.feature());
  268. break;
  269. case FeaturePromoSpecification::PromoType::kTutorial:
  270. CHECK(spec.feature());
  271. create_params.buttons =
  272. CreateTutorialButtons(*spec.feature(), spec.tutorial_id());
  273. create_params.force_close_button = true;
  274. create_params.close_button_alt_text =
  275. l10n_util::GetStringUTF16(IDS_CLOSE_PROMO);
  276. create_params.dismiss_callback = base::BindOnce(
  277. &FeaturePromoControllerCommon::OnTutorialHelpBubbleDismissed,
  278. weak_ptr_factory_.GetWeakPtr(), base::Unretained(spec.feature()),
  279. spec.tutorial_id());
  280. break;
  281. case FeaturePromoSpecification::PromoType::kCustomAction:
  282. CHECK(spec.feature());
  283. create_params.buttons = CreateCustomActionButtons(
  284. *spec.feature(), spec.custom_action_caption(),
  285. spec.custom_action_callback(), spec.custom_action_is_default());
  286. break;
  287. case FeaturePromoSpecification::PromoType::kUnspecifiied:
  288. case FeaturePromoSpecification::PromoType::kToast:
  289. case FeaturePromoSpecification::PromoType::kLegacy:
  290. break;
  291. }
  292. bool had_screen_reader_promo = false;
  293. if (spec.promo_type() == FeaturePromoSpecification::PromoType::kTutorial) {
  294. create_params.keyboard_navigation_hint = GetTutorialScreenReaderHint();
  295. } else if (CheckScreenReaderPromptAvailable()) {
  296. create_params.keyboard_navigation_hint = GetFocusHelpBubbleScreenReaderHint(
  297. spec.promo_type(), anchor_element, is_critical_promo);
  298. had_screen_reader_promo = !create_params.keyboard_navigation_hint.empty();
  299. }
  300. auto help_bubble = bubble_factory_registry_->CreateHelpBubble(
  301. anchor_element, std::move(create_params));
  302. if (help_bubble) {
  303. // Record that the focus help message was actually read to the user. See the
  304. // note in MaybeShowPromoImpl().
  305. // TODO(crbug.com/1258216): Rewrite this when we have the ability for FE
  306. // promos to ignore other active promos.
  307. if (had_screen_reader_promo) {
  308. feature_engagement_tracker_->NotifyEvent(
  309. GetScreenReaderPromptPromoEventName());
  310. }
  311. // Listen for the bubble being closed.
  312. bubble_closed_subscription_ = help_bubble->AddOnCloseCallback(
  313. base::BindOnce(&FeaturePromoControllerCommon::OnHelpBubbleClosed,
  314. base::Unretained(this)));
  315. }
  316. return help_bubble;
  317. }
  318. void FeaturePromoControllerCommon::FinishContinuedPromo(
  319. const base::Feature& iph_feature) {
  320. DCHECK(continuing_after_bubble_closed_);
  321. if (iph_feature_bypassing_tracker_ != &iph_feature)
  322. feature_engagement_tracker_->Dismissed(iph_feature);
  323. else
  324. iph_feature_bypassing_tracker_ = nullptr;
  325. if (current_iph_feature_ == &iph_feature) {
  326. current_iph_feature_ = nullptr;
  327. continuing_after_bubble_closed_ = false;
  328. }
  329. }
  330. void FeaturePromoControllerCommon::OnHelpBubbleClosed(HelpBubble* bubble) {
  331. // Since we're in the middle of processing callbacks we can't reset our
  332. // subscription but since it's a weak pointer (internally) and since we should
  333. // should only get called here once, it's not a big deal if we don't reset
  334. // it.
  335. if (bubble == critical_promo_bubble_) {
  336. critical_promo_bubble_ = nullptr;
  337. } else if (bubble == promo_bubble_.get()) {
  338. if (!continuing_after_bubble_closed_) {
  339. if (iph_feature_bypassing_tracker_.get() != current_iph_feature_)
  340. feature_engagement_tracker_->Dismissed(*current_iph_feature_);
  341. else
  342. iph_feature_bypassing_tracker_ = nullptr;
  343. current_iph_feature_ = nullptr;
  344. }
  345. promo_bubble_.reset();
  346. } else {
  347. NOTREACHED();
  348. }
  349. if (bubble_closed_callback_)
  350. std::move(bubble_closed_callback_).Run();
  351. }
  352. void FeaturePromoControllerCommon::OnHelpBubbleSnoozed(
  353. const base::Feature* feature) {
  354. if (iph_feature_bypassing_tracker_ != feature)
  355. snooze_service_->OnUserSnooze(*feature);
  356. }
  357. void FeaturePromoControllerCommon::OnHelpBubbleDismissed(
  358. const base::Feature* feature) {
  359. if (snooze_service_ && iph_feature_bypassing_tracker_ != feature)
  360. snooze_service_->OnUserDismiss(*feature);
  361. }
  362. void FeaturePromoControllerCommon::OnCustomAction(
  363. const base::Feature* feature,
  364. FeaturePromoSpecification::CustomActionCallback callback) {
  365. callback.Run(GetAnchorContext(), CloseBubbleAndContinuePromo(*feature));
  366. }
  367. void FeaturePromoControllerCommon::OnTutorialHelpBubbleSnoozed(
  368. const base::Feature* iph_feature,
  369. TutorialIdentifier tutorial_id) {
  370. OnHelpBubbleSnoozed(iph_feature);
  371. tutorial_service_->LogIPHLinkClicked(tutorial_id, false);
  372. }
  373. void FeaturePromoControllerCommon::OnTutorialHelpBubbleDismissed(
  374. const base::Feature* iph_feature,
  375. TutorialIdentifier tutorial_id) {
  376. OnHelpBubbleDismissed(iph_feature);
  377. tutorial_service_->LogIPHLinkClicked(tutorial_id, false);
  378. }
  379. void FeaturePromoControllerCommon::OnTutorialStarted(
  380. const base::Feature* iph_feature,
  381. TutorialIdentifier tutorial_id) {
  382. if (!promo_bubble_ || !promo_bubble_->is_open()) {
  383. NOTREACHED();
  384. } else {
  385. DCHECK_EQ(current_iph_feature_, iph_feature);
  386. tutorial_promo_handle_ = CloseBubbleAndContinuePromo(*iph_feature);
  387. DCHECK(tutorial_promo_handle_.is_valid());
  388. if (tutorial_service_->StartTutorial(
  389. tutorial_id, GetAnchorContext(),
  390. base::BindOnce(&FeaturePromoControllerCommon::OnTutorialComplete,
  391. weak_ptr_factory_.GetWeakPtr(),
  392. base::Unretained(iph_feature)),
  393. base::BindOnce(&FeaturePromoControllerCommon::OnTutorialAborted,
  394. weak_ptr_factory_.GetWeakPtr(),
  395. base::Unretained(iph_feature))))
  396. tutorial_service_->LogIPHLinkClicked(tutorial_id, true);
  397. }
  398. }
  399. void FeaturePromoControllerCommon::OnTutorialComplete(
  400. const base::Feature* iph_feature) {
  401. tutorial_promo_handle_.Release();
  402. if (snooze_service_)
  403. snooze_service_->OnUserDismiss(*iph_feature);
  404. }
  405. void FeaturePromoControllerCommon::OnTutorialAborted(
  406. const base::Feature* iph_feature) {
  407. tutorial_promo_handle_.Release();
  408. if (snooze_service_)
  409. snooze_service_->OnUserSnooze(*iph_feature);
  410. }
  411. std::vector<HelpBubbleButtonParams>
  412. FeaturePromoControllerCommon::CreateSnoozeButtons(
  413. const base::Feature& feature) {
  414. std::vector<HelpBubbleButtonParams> buttons;
  415. HelpBubbleButtonParams snooze_button;
  416. snooze_button.text = l10n_util::GetStringUTF16(IDS_PROMO_SNOOZE_BUTTON);
  417. snooze_button.is_default = false;
  418. snooze_button.callback = base::BindOnce(
  419. &FeaturePromoControllerCommon::OnHelpBubbleSnoozed,
  420. weak_ptr_factory_.GetWeakPtr(), base::Unretained(&feature));
  421. buttons.push_back(std::move(snooze_button));
  422. HelpBubbleButtonParams dismiss_button;
  423. dismiss_button.text = l10n_util::GetStringUTF16(IDS_PROMO_DISMISS_BUTTON);
  424. dismiss_button.is_default = true;
  425. dismiss_button.callback = base::BindOnce(
  426. &FeaturePromoControllerCommon::OnHelpBubbleDismissed,
  427. weak_ptr_factory_.GetWeakPtr(), base::Unretained(&feature));
  428. buttons.push_back(std::move(dismiss_button));
  429. return buttons;
  430. }
  431. std::vector<HelpBubbleButtonParams>
  432. FeaturePromoControllerCommon::CreateCustomActionButtons(
  433. const base::Feature& feature,
  434. const std::u16string& custom_action_caption,
  435. FeaturePromoSpecification::CustomActionCallback custom_action_callback,
  436. bool custom_action_is_default) {
  437. std::vector<HelpBubbleButtonParams> buttons;
  438. CHECK(!custom_action_callback.is_null());
  439. HelpBubbleButtonParams action_button;
  440. action_button.text = custom_action_caption;
  441. action_button.is_default = custom_action_is_default;
  442. action_button.callback =
  443. base::BindOnce(&FeaturePromoControllerCommon::OnCustomAction,
  444. weak_ptr_factory_.GetWeakPtr(), base::Unretained(&feature),
  445. custom_action_callback);
  446. buttons.push_back(std::move(action_button));
  447. HelpBubbleButtonParams dismiss_button;
  448. dismiss_button.text = l10n_util::GetStringUTF16(IDS_PROMO_DISMISS_BUTTON);
  449. dismiss_button.is_default = !custom_action_is_default;
  450. dismiss_button.callback = base::BindOnce(
  451. &FeaturePromoControllerCommon::OnHelpBubbleDismissed,
  452. weak_ptr_factory_.GetWeakPtr(), base::Unretained(&feature));
  453. buttons.push_back(std::move(dismiss_button));
  454. return buttons;
  455. }
  456. std::vector<HelpBubbleButtonParams>
  457. FeaturePromoControllerCommon::CreateTutorialButtons(
  458. const base::Feature& feature,
  459. TutorialIdentifier tutorial_id) {
  460. std::vector<HelpBubbleButtonParams> buttons;
  461. HelpBubbleButtonParams snooze_button;
  462. snooze_button.text = l10n_util::GetStringUTF16(IDS_PROMO_SNOOZE_BUTTON);
  463. snooze_button.is_default = false;
  464. snooze_button.callback = base::BindRepeating(
  465. &FeaturePromoControllerCommon::OnTutorialHelpBubbleSnoozed,
  466. weak_ptr_factory_.GetWeakPtr(), base::Unretained(&feature), tutorial_id);
  467. buttons.push_back(std::move(snooze_button));
  468. HelpBubbleButtonParams tutorial_button;
  469. tutorial_button.text =
  470. l10n_util::GetStringUTF16(IDS_PROMO_SHOW_TUTORIAL_BUTTON);
  471. tutorial_button.is_default = true;
  472. tutorial_button.callback = base::BindRepeating(
  473. &FeaturePromoControllerCommon::OnTutorialStarted,
  474. weak_ptr_factory_.GetWeakPtr(), base::Unretained(&feature), tutorial_id);
  475. buttons.push_back(std::move(tutorial_button));
  476. return buttons;
  477. }
  478. // static
  479. bool FeaturePromoControllerCommon::active_window_check_blocked_ = false;
  480. // static
  481. FeaturePromoControllerCommon::TestLock
  482. FeaturePromoControllerCommon::BlockActiveWindowCheckForTesting() {
  483. return std::make_unique<base::AutoReset<bool>>(&active_window_check_blocked_,
  484. true);
  485. }
  486. } // namespace user_education