| // Copyright 2021 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/views/user_education/browser_user_education_service.h" |
| |
| #include <vector> |
| |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/search/search.h" |
| #include "chrome/browser/themes/theme_properties.h" |
| #include "chrome/browser/ui/browser_element_identifiers.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/chrome_pages.h" |
| #include "chrome/browser/ui/color/chrome_color_id.h" |
| #include "chrome/browser/ui/performance_controls/performance_controls_metrics.h" |
| #include "chrome/browser/ui/user_education/user_education_service_factory.h" |
| #include "chrome/browser/ui/views/chrome_typography.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h" |
| #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/grit/chromium_strings.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/grit/google_chrome_strings.h" |
| #include "components/feature_engagement/public/feature_constants.h" |
| #include "components/performance_manager/public/user_tuning/prefs.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/user_education/common/feature_promo_handle.h" |
| #include "components/user_education/common/feature_promo_registry.h" |
| #include "components/user_education/common/feature_promo_specification.h" |
| #include "components/user_education/common/help_bubble_factory_registry.h" |
| #include "components/user_education/common/help_bubble_params.h" |
| #include "components/user_education/common/tutorial_description.h" |
| #include "components/user_education/common/tutorial_registry.h" |
| #include "components/user_education/views/help_bubble_delegate.h" |
| #include "components/user_education/views/help_bubble_factory_views.h" |
| #include "components/user_education/webui/floating_webui_help_bubble_factory.h" |
| #include "components/user_education/webui/help_bubble_handler.h" |
| #include "components/user_education/webui/help_bubble_webui.h" |
| #include "components/user_education/webui/tracked_element_webui.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "ui/base/interaction/element_identifier.h" |
| #include "ui/base/interaction/element_tracker.h" |
| #include "ui/base/interaction/framework_specific_implementation.h" |
| #include "ui/base/interaction/interaction_sequence.h" |
| #include "ui/color/color_id.h" |
| #include "ui/views/interaction/element_tracker_views.h" |
| #include "ui/views/view.h" |
| #include "ui/views/view_utils.h" |
| #include "ui/views/widget/widget.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/user_education/views/help_bubble_factory_views_ash.h" |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "components/user_education/views/help_bubble_factory_mac.h" |
| #endif // BUILDFLAG(IS_MAC) |
| |
| namespace { |
| |
| const char kTabGroupTutorialMetricPrefix[] = "TabGroup"; |
| const char kTabGroupWithGroupTutorialMetricPrefix[] = "TabGroupWithGroup"; |
| const char kSidePanelReadingListTutorialMetricPrefix[] = "SidePanelReadingList"; |
| const char kCustomizeChromeTutorialMetricPrefix[] = "CustomizeChromeSidePanel"; |
| const char kSideSearchTutorialMetricPrefix[] = "SideSearch"; |
| constexpr char kTabGroupHeaderElementName[] = "TabGroupHeader"; |
| constexpr char kReadingListItemElementName[] = "ReadingListItem"; |
| constexpr char kChromeThemeBackElementName[] = "ChromeThemeBackElement"; |
| |
| class BrowserHelpBubbleDelegate : public user_education::HelpBubbleDelegate { |
| public: |
| BrowserHelpBubbleDelegate() = default; |
| ~BrowserHelpBubbleDelegate() override = default; |
| |
| std::vector<ui::Accelerator> GetPaneNavigationAccelerators( |
| ui::TrackedElement* anchor_element) const override { |
| std::vector<ui::Accelerator> result; |
| if (anchor_element->IsA<views::TrackedElementViews>()) { |
| auto* widget = anchor_element->AsA<views::TrackedElementViews>() |
| ->view() |
| ->GetWidget(); |
| if (widget) { |
| auto* const client_view = |
| widget->GetPrimaryWindowWidget()->client_view(); |
| if (client_view && views::IsViewClass<BrowserView>(client_view)) { |
| auto* const browser_view = static_cast<BrowserView*>(client_view); |
| ui::Accelerator accel; |
| if (browser_view->GetAccelerator(IDC_FOCUS_NEXT_PANE, &accel)) |
| result.push_back(accel); |
| if (browser_view->GetAccelerator(IDC_FOCUS_PREVIOUS_PANE, &accel)) |
| result.push_back(accel); |
| if (browser_view->GetAccelerator( |
| IDC_FOCUS_INACTIVE_POPUP_FOR_ACCESSIBILITY, &accel)) { |
| result.push_back(accel); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| int GetTitleTextContext() const override { |
| return ChromeTextContext::CONTEXT_IPH_BUBBLE_TITLE; |
| } |
| int GetBodyTextContext() const override { |
| return ChromeTextContext::CONTEXT_IPH_BUBBLE_BODY; |
| } |
| |
| // These methods return color codes that will be handled by the app's theming |
| // system. |
| ui::ColorId GetHelpBubbleBackgroundColorId() const override { |
| return kColorFeaturePromoBubbleBackground; |
| } |
| ui::ColorId GetHelpBubbleForegroundColorId() const override { |
| return kColorFeaturePromoBubbleForeground; |
| } |
| ui::ColorId GetHelpBubbleDefaultButtonBackgroundColorId() const override { |
| return kColorFeaturePromoBubbleDefaultButtonBackground; |
| } |
| ui::ColorId GetHelpBubbleDefaultButtonForegroundColorId() const override { |
| return kColorFeaturePromoBubbleDefaultButtonForeground; |
| } |
| ui::ColorId GetHelpBubbleButtonBorderColorId() const override { |
| return kColorFeaturePromoBubbleButtonBorder; |
| } |
| ui::ColorId GetHelpBubbleCloseButtonInkDropColorId() const override { |
| return kColorFeaturePromoBubbleCloseButtonInkDrop; |
| } |
| }; |
| |
| // Help bubble factory that can show a floating (Views-based) help bubble on a |
| // WebUI element, but only for non-tab WebUI. |
| class FloatingWebUIHelpBubbleFactoryBrowser |
| : public user_education::FloatingWebUIHelpBubbleFactory { |
| public: |
| explicit FloatingWebUIHelpBubbleFactoryBrowser( |
| const user_education::HelpBubbleDelegate* delegate) |
| : FloatingWebUIHelpBubbleFactory(delegate) {} |
| ~FloatingWebUIHelpBubbleFactoryBrowser() override = default; |
| |
| DECLARE_FRAMEWORK_SPECIFIC_METADATA() |
| |
| // HelpBubbleFactoryWebUIViews: |
| bool CanBuildBubbleForTrackedElement( |
| const ui::TrackedElement* element) const override { |
| if (!element->IsA<user_education::TrackedElementWebUI>()) { |
| return false; |
| } |
| |
| // If this is a WebUI in a tab, then don't use this factory. |
| const auto* contents = element->AsA<user_education::TrackedElementWebUI>() |
| ->handler() |
| ->GetWebContents(); |
| // Note: this checks all tabs for their WebContents. |
| if (chrome::FindBrowserWithWebContents(contents)) { |
| return false; |
| } |
| |
| // Ensure that this WebUI fulfils the requirements for a floating help |
| // bubble. |
| return FloatingWebUIHelpBubbleFactory::CanBuildBubbleForTrackedElement( |
| element); |
| } |
| }; |
| |
| DEFINE_FRAMEWORK_SPECIFIC_METADATA(FloatingWebUIHelpBubbleFactoryBrowser) |
| |
| } // namespace |
| |
| const char kSidePanelCustomizeChromeTutorialId[] = |
| "Side Panel Customize Chrome Tutorial"; |
| const char kTabGroupTutorialId[] = "Tab Group Tutorial"; |
| const char kTabGroupWithExistingGroupTutorialId[] = |
| "Tab Group With Existing Group Tutorial"; |
| const char kSidePanelReadingListTutorialId[] = |
| "Side Panel Reading List Tutorial"; |
| |
| const char kSideSearchTutorialId[] = "Side Search Tutorial"; |
| |
| user_education::HelpBubbleDelegate* GetHelpBubbleDelegate() { |
| static base::NoDestructor<BrowserHelpBubbleDelegate> delegate; |
| return delegate.get(); |
| } |
| |
| void RegisterChromeHelpBubbleFactories( |
| user_education::HelpBubbleFactoryRegistry& registry) { |
| const user_education::HelpBubbleDelegate* const delegate = |
| GetHelpBubbleDelegate(); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // TODO(http://b/277994050): Move registration after Lacros launch. |
| // Try to create an Ash-specific help bubble first. Note that an Ash-specific |
| // help bubble will only take precedence over a standard Views-specific help |
| // bubble if the tracked element's help bubble context is explicitly set to |
| // `ash::HelpBubbleContext::kAsh`. |
| registry.MaybeRegister<ash::HelpBubbleFactoryViewsAsh>(delegate); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| registry.MaybeRegister<user_education::HelpBubbleFactoryViews>(delegate); |
| // Try to create a floating bubble first, if it's allowed. |
| registry.MaybeRegister<FloatingWebUIHelpBubbleFactoryBrowser>(delegate); |
| // Fall back to in-WebUI help bubble if the floating bubble doesn't apply. |
| registry.MaybeRegister<user_education::HelpBubbleFactoryWebUI>(); |
| #if BUILDFLAG(IS_MAC) |
| registry.MaybeRegister<user_education::HelpBubbleFactoryMac>(delegate); |
| #endif |
| } |
| |
| void MaybeRegisterChromeFeaturePromos( |
| user_education::FeaturePromoRegistry& registry) { |
| using user_education::FeaturePromoSpecification; |
| using user_education::HelpBubbleArrow; |
| |
| // Verify that we haven't already registered the expected features. |
| // TODO(dfried): figure out if we should do something more sophisticated here. |
| if (registry.IsFeatureRegistered( |
| feature_engagement::kIPHDesktopPwaInstallFeature)) |
| return; |
| |
| // TODO(1432894): Use toast or snooze instead of legacy promo. |
| // kIPHAutofillExternalAccountProfileSuggestionFeature: |
| registry.RegisterFeature( |
| std::move(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement:: |
| kIPHAutofillExternalAccountProfileSuggestionFeature, |
| kAutofillSuggestionElementId, |
| IDS_AUTOFILL_IPH_EXTERNAL_ACCOUNT_PROFILE_SUGGESTION) |
| .SetBubbleArrow(HelpBubbleArrow::kLeftCenter))); |
| |
| // kIPHAutofillVirtualCardSuggestionFeature: |
| registry.RegisterFeature(std::move( |
| FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHAutofillVirtualCardSuggestionFeature, |
| kAutofillCreditCardSuggestionEntryElementId, |
| IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_IPH_BUBBLE_LABEL) |
| .SetBubbleArrow(HelpBubbleArrow::kLeftCenter))); |
| |
| // kIPHDesktopPwaInstallFeature: |
| registry.RegisterFeature( |
| user_education::FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHDesktopPwaInstallFeature, |
| kInstallPwaElementId, IDS_DESKTOP_PWA_INSTALL_PROMO)); |
| |
| // kIPHDesktopTabGroupsNewGroupFeature: |
| registry.RegisterFeature( |
| std::move(FeaturePromoSpecification::CreateForTutorialPromo( |
| feature_engagement::kIPHDesktopTabGroupsNewGroupFeature, |
| kTabStripRegionElementId, IDS_TAB_GROUPS_NEW_GROUP_PROMO, |
| kTabGroupTutorialId) |
| .SetBubbleArrow(HelpBubbleArrow::kNone) |
| .SetBubbleIcon(&vector_icons::kLightbulbOutlineIcon))); |
| |
| // kIPHDesktopCustomizeChromeFeature: |
| registry.RegisterFeature(std::move( |
| FeaturePromoSpecification::CreateForCustomAction( |
| feature_engagement::kIPHDesktopCustomizeChromeFeature, |
| kTopContainerElementId, |
| IDS_TUTORIAL_CUSTOMIZE_CHROME_START_TUTORIAL_IPH, |
| IDS_PROMO_SHOW_TUTORIAL_BUTTON, |
| base::BindRepeating( |
| [](ui::ElementContext ctx, |
| user_education::FeaturePromoHandle promo_handle) { |
| auto* browser = chrome::FindBrowserWithUiElementContext(ctx); |
| if (!browser) { |
| return; |
| } |
| if (!search::DefaultSearchProviderIsGoogle( |
| browser->profile())) { |
| return; |
| } |
| auto* service = UserEducationServiceFactory::GetForProfile( |
| browser->profile()); |
| user_education::TutorialService* tutorial_service = |
| service ? &service->tutorial_service() : nullptr; |
| if (!tutorial_service) { |
| return; |
| } |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| if (tab_strip_model) { |
| content::WebContents* web_contents = |
| tab_strip_model->GetActiveWebContents(); |
| if (web_contents && |
| web_contents->GetURL() != browser->GetNewTabURL()) { |
| NavigateParams params(browser->profile(), |
| GURL(chrome::kChromeUINewTabPageURL), |
| ui::PAGE_TRANSITION_LINK); |
| params.disposition = |
| WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| Navigate(¶ms); |
| } |
| } |
| user_education::TutorialIdentifier tutorial_id = |
| kSidePanelCustomizeChromeTutorialId; |
| |
| tutorial_service->StartTutorial(tutorial_id, ctx); |
| tutorial_service->LogIPHLinkClicked(tutorial_id, true); |
| })) |
| .SetBubbleArrow(HelpBubbleArrow::kNone) |
| .SetBubbleIcon(&vector_icons::kLightbulbOutlineIcon) |
| .SetCustomActionIsDefault(true) |
| .SetCustomActionDismissText(IDS_PROMO_SNOOZE_BUTTON))); |
| |
| // kIPHLiveCaptionFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForToastPromo( |
| feature_engagement::kIPHLiveCaptionFeature, kMediaButtonElementId, |
| IDS_LIVE_CAPTION_PROMO, IDS_LIVE_CAPTION_PROMO_SCREENREADER, |
| FeaturePromoSpecification::AcceleratorInfo())); |
| |
| // kIPHTabAudioMutingFeature: |
| registry.RegisterFeature(std::move( |
| FeaturePromoSpecification::CreateForToastPromo( |
| feature_engagement::kIPHTabAudioMutingFeature, |
| kTabAlertIndicatorButtonElementId, IDS_TAB_AUDIO_MUTING_PROMO, |
| IDS_LIVE_CAPTION_PROMO_SCREENREADER, |
| FeaturePromoSpecification::AcceleratorInfo()) |
| .SetBubbleArrow(HelpBubbleArrow::kTopCenter))); |
| |
| // kIPHGMCCastStartStopFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHGMCCastStartStopFeature, kMediaButtonElementId, |
| IDS_GLOBAL_MEDIA_CONTROLS_CONTROL_CAST_SESSIONS_PROMO)); |
| |
| // kIPHPasswordsAccountStorageFeature: |
| registry.RegisterFeature(std::move( |
| FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHPasswordsAccountStorageFeature, |
| kSavePasswordComboboxElementId, |
| IDS_PASSWORD_MANAGER_IPH_BODY_SAVE_TO_ACCOUNT) |
| .SetBubbleTitleText(IDS_PASSWORD_MANAGER_IPH_TITLE_SAVE_TO_ACCOUNT) |
| .SetBubbleArrow(HelpBubbleArrow::kRightCenter))); |
| |
| // kIPHPasswordsManagementBubbleAfterSaveFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForToastPromo( |
| feature_engagement::kIPHPasswordsManagementBubbleAfterSaveFeature, |
| kPasswordsOmniboxKeyIconElementId, |
| IDS_PASSWORD_MANAGER_IPH_MANAGEMENT_BUBBLE_AFTER_SAVE, |
| IDS_PASSWORD_MANAGER_IPH_MANAGEMENT_BUBBLE_AFTER_SAVE_SCREENREADER, |
| FeaturePromoSpecification::AcceleratorInfo())); |
| |
| // kIPHPasswordsManagementBubbleDuringSigninFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForToastPromo( |
| feature_engagement::kIPHPasswordsManagementBubbleDuringSigninFeature, |
| kPasswordsOmniboxKeyIconElementId, |
| IDS_PASSWORD_MANAGER_IPH_MANAGEMENT_BUBBLE_DURING_SIGNIN, |
| IDS_PASSWORD_MANAGER_IPH_MANAGEMENT_BUBBLE_DURING_SIGNIN_SCREENREADER, |
| FeaturePromoSpecification::AcceleratorInfo())); |
| |
| // kIPHPowerBookmarksSidePanelFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForSnoozePromo( |
| feature_engagement::kIPHPowerBookmarksSidePanelFeature, |
| kSidePanelButtonElementId, IDS_POWER_BOOKMARKS_SIDE_PANEL_PROMO)); |
| |
| // kIPHCompanionSidePanelFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForSnoozePromo( |
| feature_engagement::kIPHCompanionSidePanelFeature, |
| kSidePanelCompanionToolbarButtonElementId, |
| IDS_SIDE_PANEL_COMPANION_PROMO)); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| // kIPHSwitchProfileFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForToastPromo( |
| feature_engagement::kIPHProfileSwitchFeature, kAvatarButtonElementId, |
| IDS_PROFILE_SWITCH_PROMO, IDS_PROFILE_SWITCH_PROMO_SCREENREADER, |
| FeaturePromoSpecification::AcceleratorInfo(IDC_SHOW_AVATAR_MENU))); |
| |
| // kIPHPasswordsWebAppProfileSwitchFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForToastPromo( |
| feature_engagement::kIPHPasswordsWebAppProfileSwitchFeature, |
| kAvatarButtonElementId, |
| IDS_PASSWORD_MANAGER_IPH_BODY_WEB_APP_PROFILE_SWITCH, |
| IDS_PROFILE_SWITCH_PROMO_SCREENREADER, |
| FeaturePromoSpecification::AcceleratorInfo())); |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // kIPHReadingListDiscoveryFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHReadingListDiscoveryFeature, |
| kSidePanelButtonElementId, IDS_READING_LIST_DISCOVERY_PROMO)); |
| |
| // kIPHReadingListEntryPointFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForSnoozePromo( |
| feature_engagement::kIPHReadingListEntryPointFeature, |
| kBookmarkStarViewElementId, IDS_READING_LIST_ENTRY_POINT_PROMO)); |
| |
| // kIPHReadingListInSidePanelFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHReadingListInSidePanelFeature, |
| kSidePanelButtonElementId, IDS_READING_LIST_IN_SIDE_PANEL_PROMO)); |
| |
| // kIPHReopenTabFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForToastPromo( |
| feature_engagement::kIPHReopenTabFeature, kAppMenuButtonElementId, |
| IDS_REOPEN_TAB_PROMO, IDS_REOPEN_TAB_PROMO_SCREENREADER, |
| FeaturePromoSpecification::AcceleratorInfo(IDC_RESTORE_TAB))); |
| |
| // kIPHSideSearchFeature: |
| registry.RegisterFeature(std::move( |
| FeaturePromoSpecification::CreateForTutorialPromo( |
| feature_engagement::kIPHSideSearchFeature, kSideSearchButtonElementId, |
| IDS_SIDE_SEARCH_PROMO, kSideSearchTutorialId) |
| .SetBubbleArrow(HelpBubbleArrow::kBottomCenter) |
| .SetBubbleIcon(&vector_icons::kLightbulbOutlineIcon))); |
| |
| // kIPHTabSearchFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHTabSearchFeature, kTabSearchButtonElementId, |
| IDS_TAB_SEARCH_PROMO)); |
| |
| // kIPHWebUITabStripFeature: |
| #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP) |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHWebUITabStripFeature, kTabCounterButtonElementId, |
| IDS_WEBUI_TAB_STRIP_PROMO)); |
| #endif // BUILDFLAG(ENABLE_WEBUI_TAB_STRIP) |
| |
| // kIPHDesktopSharedHighlightingFeature: |
| registry.RegisterFeature( |
| std::move(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHDesktopSharedHighlightingFeature, |
| kTopContainerElementId, IDS_SHARED_HIGHLIGHTING_PROMO) |
| .SetBubbleArrow(HelpBubbleArrow::kNone))); |
| |
| // kIPHWebUiHelpBubbleTestFeature |
| registry.RegisterFeature(std::move( |
| FeaturePromoSpecification::CreateForSnoozePromo( |
| feature_engagement::kIPHWebUiHelpBubbleTestFeature, |
| kWebUIIPHDemoElementIdentifier, |
| IDS_PASSWORD_MANAGER_IPH_BODY_SAVE_TO_ACCOUNT) |
| .SetBubbleTitleText(IDS_PASSWORD_MANAGER_IPH_TITLE_SAVE_TO_ACCOUNT) |
| .SetInAnyContext(true) |
| .SetBubbleArrow(HelpBubbleArrow::kBottomRight) |
| .SetBubbleIcon(&vector_icons::kCelebrationIcon))); |
| |
| // kIPHBatterySaverModeFeature: |
| registry.RegisterFeature(std::move( |
| FeaturePromoSpecification::CreateForCustomAction( |
| feature_engagement::kIPHBatterySaverModeFeature, |
| kBatterySaverButtonElementId, IDS_BATTERY_SAVER_MODE_PROMO_TEXT, |
| IDS_BATTERY_SAVER_MODE_PROMO_ACTION_TEXT, |
| base::BindRepeating( |
| [](ui::ElementContext ctx, |
| user_education::FeaturePromoHandle promo_handle) { |
| auto* browser = chrome::FindBrowserWithUiElementContext(ctx); |
| if (browser) |
| chrome::ShowSettingsSubPage(browser, |
| chrome::kPerformanceSubPage); |
| RecordBatterySaverIPHOpenSettings(browser != nullptr); |
| })) |
| .SetBubbleTitleText(IDS_BATTERY_SAVER_MODE_PROMO_TITLE) |
| .SetBubbleArrow(HelpBubbleArrow::kTopRight))); |
| |
| // kIPHHighEfficiencyModeFeature: |
| registry.RegisterFeature(std::move( |
| FeaturePromoSpecification::CreateForCustomAction( |
| feature_engagement::kIPHHighEfficiencyModeFeature, |
| kAppMenuButtonElementId, IDS_HIGH_EFFICIENCY_MODE_PROMO_TEXT, |
| IDS_HIGH_EFFICIENCY_MODE_PROMO_ACTION_TEXT, |
| base::BindRepeating( |
| [](ui::ElementContext context, |
| user_education::FeaturePromoHandle promo_handle) { |
| performance_manager::user_tuning::UserPerformanceTuningManager:: |
| GetInstance() |
| ->SetHighEfficiencyModeEnabled(true); |
| RecordHighEfficiencyIPHEnableMode(true); |
| })) |
| .SetCustomActionIsDefault(true) |
| .SetCustomActionDismissText(IDS_NO_THANKS) |
| .SetBubbleTitleText(IDS_HIGH_EFFICIENCY_MODE_PROMO_TITLE))); |
| |
| // kIPHPriceTrackingInSidePanelFeature; |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHPriceTrackingInSidePanelFeature, |
| kSidePanelButtonElementId, IDS_PRICE_TRACKING_SIDE_PANEL_IPH)); |
| |
| // kIPHDownloadToolbarButtonFeature: |
| registry.RegisterFeature( |
| std::move(FeaturePromoSpecification::CreateForSnoozePromo( |
| feature_engagement::kIPHDownloadToolbarButtonFeature, |
| kDownloadToolbarButtonElementId, IDS_DOWNLOAD_BUBBLE_PROMO) |
| .SetBubbleArrow(HelpBubbleArrow::kTopRight) |
| .SetBubbleTitleText(IDS_DOWNLOAD_BUBBLE_PROMO_TITLE))); |
| |
| // kIPHBackNavigationMenuFeature: |
| registry.RegisterFeature( |
| std::move(FeaturePromoSpecification::CreateForSnoozePromo( |
| feature_engagement::kIPHBackNavigationMenuFeature, |
| kBackButtonElementId, IDS_BACK_NAVIGATION_MENU_PROMO, |
| IDS_BACK_NAVIGATION_MENU_PROMO_ACCESSIBLE_TEXT, |
| FeaturePromoSpecification::AcceleratorInfo()) |
| .SetBubbleArrow(HelpBubbleArrow::kTopLeft))); |
| |
| // kIPHPriceTrackingChipFeature: |
| registry.RegisterFeature(FeaturePromoSpecification::CreateForLegacyPromo( |
| &feature_engagement::kIPHPriceTrackingChipFeature, |
| kPriceTrackingChipElementId, IDS_PRICE_TRACKING_CHIP_IPH)); |
| } |
| |
| void MaybeRegisterChromeTutorials( |
| user_education::TutorialRegistry& tutorial_registry) { |
| using user_education::HelpBubbleArrow; |
| using user_education::TutorialDescription; |
| |
| // TODO (dfried): we might want to do something more sophisticated in the |
| // future. |
| if (tutorial_registry.IsTutorialRegistered(kTabGroupTutorialId)) |
| return; |
| |
| { // Tab Group Tutorials |
| |
| // The Description for kTabGroupTutorialId. |
| TutorialDescription without_group_description; |
| |
| // The Description for kTabGroupWithExistingGroupTutorialId. |
| TutorialDescription with_group_description; |
| |
| // The initial step. This is the only step that differs between |
| // kTabGroupTutorialId and kTabGroupWithExistingGroupTutorialId. |
| TutorialDescription::Step create_tabgroup_step( |
| 0, IDS_TUTORIAL_TAB_GROUP_ADD_TAB_TO_GROUP, |
| ui::InteractionSequence::StepType::kShown, kTabStripRegionElementId, |
| std::string(), HelpBubbleArrow::kNone); |
| without_group_description.steps.emplace_back(create_tabgroup_step); |
| |
| TutorialDescription::Step create_tabgroup_with_existing_group_step( |
| 0, IDS_TUTORIAL_ADD_TAB_TO_GROUP_WITH_EXISTING_GROUP_IN_TAB_STRIP, |
| ui::InteractionSequence::StepType::kShown, kTabStripRegionElementId, |
| std::string(), HelpBubbleArrow::kNone); |
| with_group_description.steps.emplace_back( |
| create_tabgroup_with_existing_group_step); |
| |
| // Getting the new tab group (hidden step). |
| TutorialDescription::Step new_tab_group_step( |
| 0, 0, ui::InteractionSequence::StepType::kShown, |
| kTabGroupHeaderElementId, std::string(), HelpBubbleArrow::kNone, |
| ui::CustomElementEventType(), /* must_remain_visible =*/true, |
| /* transition_only_on_event =*/true, |
| base::BindRepeating( |
| [](ui::InteractionSequence* sequence, ui::TrackedElement* element) { |
| sequence->NameElement( |
| element, base::StringPiece(kTabGroupHeaderElementName)); |
| return true; |
| })); |
| without_group_description.steps.emplace_back(new_tab_group_step); |
| with_group_description.steps.emplace_back(new_tab_group_step); |
| |
| // The menu step. |
| TutorialDescription::Step bubble_menu_edit_step( |
| 0, IDS_TUTORIAL_TAB_GROUP_EDIT_BUBBLE, |
| ui::InteractionSequence::StepType::kShown, kTabGroupEditorBubbleId, |
| std::string(), HelpBubbleArrow::kLeftCenter, |
| ui::CustomElementEventType(), |
| /*must_remain_visible =*/false); |
| without_group_description.steps.emplace_back(bubble_menu_edit_step); |
| with_group_description.steps.emplace_back(bubble_menu_edit_step); |
| |
| TutorialDescription::Step bubble_menu_edit_ended_step( |
| 0, 0, ui::InteractionSequence::StepType::kHidden, |
| kTabGroupEditorBubbleId, std::string(), HelpBubbleArrow::kNone, |
| ui::CustomElementEventType(), |
| /*must_remain_visible =*/false); |
| without_group_description.steps.emplace_back(bubble_menu_edit_ended_step); |
| with_group_description.steps.emplace_back(bubble_menu_edit_ended_step); |
| |
| // Drag tab into the group. |
| TutorialDescription::Step drag_tab_into_group_step( |
| 0, IDS_TUTORIAL_TAB_GROUP_DRAG_TAB, |
| ui::InteractionSequence::StepType::kShown, kTabStripRegionElementId, |
| std::string(), HelpBubbleArrow::kNone); |
| without_group_description.steps.emplace_back(drag_tab_into_group_step); |
| with_group_description.steps.emplace_back(drag_tab_into_group_step); |
| |
| TutorialDescription::Step successfully_drag_tab_into_group_step( |
| 0, 0, ui::InteractionSequence::StepType::kCustomEvent, |
| ui::ElementIdentifier(), std::string(), HelpBubbleArrow::kTopCenter, |
| kTabGroupedCustomEventId, /*must_remain_visible =*/true); |
| without_group_description.steps.emplace_back( |
| successfully_drag_tab_into_group_step); |
| with_group_description.steps.emplace_back( |
| successfully_drag_tab_into_group_step); |
| |
| // Click to collapse the tab group. |
| TutorialDescription::Step collapse_step( |
| 0, IDS_TUTORIAL_TAB_GROUP_COLLAPSE, |
| ui::InteractionSequence::StepType::kShown, ui::ElementIdentifier(), |
| kTabGroupHeaderElementName, HelpBubbleArrow::kTopCenter); |
| without_group_description.steps.emplace_back(collapse_step); |
| with_group_description.steps.emplace_back(collapse_step); |
| |
| TutorialDescription::Step detect_collapse_step( |
| 0, 0, ui::InteractionSequence::StepType::kActivated, |
| kTabGroupHeaderElementId, std::string(), HelpBubbleArrow::kNone); |
| without_group_description.steps.emplace_back(detect_collapse_step); |
| with_group_description.steps.emplace_back(detect_collapse_step); |
| |
| // Completion of the tutorial. |
| TutorialDescription::Step success_step( |
| IDS_TUTORIAL_GENERIC_SUCCESS_TITLE, |
| IDS_TUTORIAL_TAB_GROUP_SUCCESS_DESCRIPTION, |
| ui::InteractionSequence::StepType::kShown, kTabStripRegionElementId, |
| std::string(), HelpBubbleArrow::kNone); |
| without_group_description.steps.emplace_back(success_step); |
| with_group_description.steps.emplace_back(success_step); |
| |
| without_group_description.histograms = |
| user_education::MakeTutorialHistograms<kTabGroupTutorialMetricPrefix>( |
| without_group_description.steps.size()); |
| tutorial_registry.AddTutorial(kTabGroupTutorialId, |
| std::move(without_group_description)); |
| |
| with_group_description.histograms = user_education::MakeTutorialHistograms< |
| kTabGroupWithGroupTutorialMetricPrefix>( |
| with_group_description.steps.size()); |
| tutorial_registry.AddTutorial(kTabGroupWithExistingGroupTutorialId, |
| std::move(with_group_description)); |
| } |
| |
| { // Side panel customize chrome |
| |
| // The Description for kSidePanelCustomizeChromeTutorialId |
| TutorialDescription customize_chrome_description; |
| |
| // Bubble step - customize chrome button |
| TutorialDescription::Step open_customize_chrome_step( |
| 0, IDS_TUTORIAL_CUSTOMIZE_CHROME_OPEN_SIDE_PANEL, |
| ui::InteractionSequence::StepType::kShown, |
| NewTabPageUI::kCustomizeChromeButtonElementId, std::string(), |
| HelpBubbleArrow::kBottomRight, ui::CustomElementEventType(), |
| absl::nullopt, |
| /* transition_only_on_event =*/false, |
| user_education::TutorialDescription::NameElementsCallback(), |
| TutorialDescription::ContextMode::kAny); |
| customize_chrome_description.steps.emplace_back(open_customize_chrome_step); |
| |
| // Bubble step - change theme button |
| TutorialDescription::Step change_chrome_theme_step( |
| 0, IDS_TUTORIAL_CUSTOMIZE_CHROME_CHANGE_THEME, |
| ui::InteractionSequence::StepType::kShown, |
| CustomizeChromeUI::kChangeChromeThemeButtonElementId, std::string(), |
| HelpBubbleArrow::kRightCenter, ui::CustomElementEventType(), |
| /* must_remain_visible =*/false, |
| /* transition_only_on_event =*/false, |
| user_education::TutorialDescription::NameElementsCallback(), |
| TutorialDescription::ContextMode::kAny); |
| customize_chrome_description.steps.emplace_back(change_chrome_theme_step); |
| |
| // Bubble step - select collection |
| TutorialDescription::Step select_collection_step( |
| 0, IDS_TUTORIAL_CUSTOMIZE_CHROME_SELECT_COLLECTION, |
| ui::InteractionSequence::StepType::kShown, |
| CustomizeChromeUI::kChromeThemeCollectionElementId, std::string(), |
| HelpBubbleArrow::kRightCenter, ui::CustomElementEventType(), |
| /* must_remain_visible =*/false, |
| /* transition_only_on_event =*/false, |
| user_education::TutorialDescription::NameElementsCallback(), |
| TutorialDescription::ContextMode::kAny); |
| customize_chrome_description.steps.emplace_back(select_collection_step); |
| |
| // Bubble step - select theme |
| TutorialDescription::Step select_theme_step( |
| 0, IDS_TUTORIAL_CUSTOMIZE_CHROME_APPLY_THEME, |
| ui::InteractionSequence::StepType::kShown, |
| CustomizeChromeUI::kChromeThemeElementId, std::string(), |
| HelpBubbleArrow::kRightCenter, ui::CustomElementEventType(), |
| /* must_remain_visible =*/false, |
| /* transition_only_on_event =*/false, |
| user_education::TutorialDescription::NameElementsCallback(), |
| TutorialDescription::ContextMode::kAny); |
| customize_chrome_description.steps.emplace_back(select_theme_step); |
| |
| // Event step - select theme event |
| TutorialDescription::Step select_theme_event_step( |
| 0, 0, ui::InteractionSequence::StepType::kCustomEvent, |
| kBrowserViewElementId, std::string(), HelpBubbleArrow::kNone, |
| kBrowserThemeChangedEventId, |
| /* must_remain_visible =*/false); |
| select_theme_event_step.must_be_visible = false; |
| customize_chrome_description.steps.emplace_back(select_theme_event_step); |
| |
| // Bubble step - back button |
| TutorialDescription::Step back_button_step( |
| 0, IDS_TUTORIAL_CUSTOMIZE_CHROME_CLICK_BACK_ARROW, |
| ui::InteractionSequence::StepType::kShown, |
| CustomizeChromeUI::kChromeThemeBackElementId, std::string(), |
| HelpBubbleArrow::kRightCenter, ui::CustomElementEventType(), |
| /* must_remain_visible =*/false, |
| /* transition_only_on_event =*/false, |
| base::BindRepeating( |
| [](ui::InteractionSequence* sequence, ui::TrackedElement* element) { |
| sequence->NameElement( |
| element, base::StringPiece(kChromeThemeBackElementName)); |
| return true; |
| }), |
| TutorialDescription::ContextMode::kAny); |
| customize_chrome_description.steps.emplace_back(back_button_step); |
| |
| // Hidden step - back button |
| TutorialDescription::Step back_button_hidden_step( |
| 0, 0, ui::InteractionSequence::StepType::kHidden, |
| ui::ElementIdentifier(), kChromeThemeBackElementName, |
| HelpBubbleArrow::kNone); |
| customize_chrome_description.steps.emplace_back(back_button_hidden_step); |
| |
| // Completion of the tutorial. |
| TutorialDescription::Step success_step( |
| IDS_TUTORIAL_GENERIC_SUCCESS_TITLE, |
| IDS_TUTORIAL_CUSTOMIZE_CHROME_SUCCESS_BODY, |
| ui::InteractionSequence::StepType::kShown, kTopContainerElementId, |
| std::string(), HelpBubbleArrow::kNone, ui::CustomElementEventType(), |
| /* must_remain_visible =*/false, |
| /* transition_only_on_event =*/false, |
| user_education::TutorialDescription::NameElementsCallback(), |
| TutorialDescription::ContextMode::kAny); |
| customize_chrome_description.steps.emplace_back(success_step); |
| |
| customize_chrome_description.histograms = |
| user_education::MakeTutorialHistograms< |
| kCustomizeChromeTutorialMetricPrefix>( |
| customize_chrome_description.steps.size()); |
| |
| tutorial_registry.AddTutorial(kSidePanelCustomizeChromeTutorialId, |
| std::move(customize_chrome_description)); |
| } |
| |
| { // Side panel reading list tutorial |
| |
| // The Description for kSidePanelReadingListTutorialId |
| TutorialDescription side_panel_description; |
| |
| // Open side panel |
| TutorialDescription::Step open_side_panel_step( |
| 0, IDS_TUTORIAL_SIDE_PANEL_READING_LIST_OPEN_SIDE_PANEL, |
| ui::InteractionSequence::StepType::kShown, kSidePanelButtonElementId, |
| std::string(), HelpBubbleArrow::kTopRight); |
| side_panel_description.steps.emplace_back(open_side_panel_step); |
| |
| // Click "Add current tab" |
| TutorialDescription::Step add_current_tab_step( |
| 0, IDS_TUTORIAL_SIDE_PANEL_READING_LIST_ADD_TAB, |
| ui::InteractionSequence::StepType::kShown, |
| kAddCurrentTabToReadingListElementId, std::string(), |
| HelpBubbleArrow::kRightTop, ui::CustomElementEventType(), absl::nullopt, |
| /* transition_only_on_event =*/false, |
| user_education::TutorialDescription::NameElementsCallback(), |
| TutorialDescription::ContextMode::kAny); |
| side_panel_description.steps.emplace_back(add_current_tab_step); |
| |
| // When shown, name the element |
| TutorialDescription::Step new_reading_list_item_step( |
| 0, 0, ui::InteractionSequence::StepType::kShown, |
| kSidePanelReadingListUnreadElementId, std::string(), |
| HelpBubbleArrow::kNone, ui::CustomElementEventType(), |
| /* must_remain_visible =*/true, |
| /* transition_only_on_event =*/true, |
| base::BindRepeating( |
| [](ui::InteractionSequence* sequence, ui::TrackedElement* element) { |
| sequence->NameElement( |
| element, base::StringPiece(kReadingListItemElementName)); |
| return true; |
| }), |
| TutorialDescription::ContextMode::kAny); |
| side_panel_description.steps.emplace_back(new_reading_list_item_step); |
| |
| // Mark as read |
| TutorialDescription::Step mark_as_read_step( |
| 0, IDS_TUTORIAL_SIDE_PANEL_READING_LIST_MARK_READ, |
| ui::InteractionSequence::StepType::kShown, ui::ElementIdentifier(), |
| kReadingListItemElementName, HelpBubbleArrow::kRightTop); |
| side_panel_description.steps.emplace_back(mark_as_read_step); |
| |
| TutorialDescription::Step detect_mark_as_read_step( |
| 0, 0, ui::InteractionSequence::StepType::kCustomEvent, |
| ui::ElementIdentifier(), kReadingListItemElementName, |
| HelpBubbleArrow::kNone, kSidePanelReadingMarkedAsReadEventId); |
| side_panel_description.steps.emplace_back(detect_mark_as_read_step); |
| |
| // Click drop down |
| TutorialDescription::Step click_dropdown_step( |
| 0, IDS_TUTORIAL_SIDE_PANEL_READING_LIST_CLICK_DROPDOWN, |
| ui::InteractionSequence::StepType::kShown, kSidePanelComboboxElementId, |
| std::string(), HelpBubbleArrow::kTopLeft); |
| side_panel_description.steps.emplace_back(click_dropdown_step); |
| |
| TutorialDescription::Step detect_click_dropdown_step( |
| 0, 0, ui::InteractionSequence::StepType::kCustomEvent, |
| kSidePanelComboboxElementId, std::string(), HelpBubbleArrow::kNone, |
| kSidePanelComboboxChangedCustomEventId); |
| side_panel_description.steps.emplace_back(detect_click_dropdown_step); |
| |
| // Completion of the tutorial. |
| TutorialDescription::Step success_step( |
| IDS_TUTORIAL_GENERIC_SUCCESS_TITLE, |
| IDS_TUTORIAL_SIDE_PANEL_READING_LIST_SUCCESS_BODY, |
| ui::InteractionSequence::StepType::kShown, kTabStripRegionElementId, |
| std::string(), HelpBubbleArrow::kNone); |
| side_panel_description.steps.emplace_back(success_step); |
| |
| side_panel_description.histograms = user_education::MakeTutorialHistograms< |
| kSidePanelReadingListTutorialMetricPrefix>( |
| side_panel_description.steps.size()); |
| tutorial_registry.AddTutorial(kSidePanelReadingListTutorialId, |
| std::move(side_panel_description)); |
| } |
| |
| { |
| TutorialDescription side_search_description; |
| |
| // 1st bubble appears and prompts users to open side search |
| TutorialDescription::Step open_side_search_in_panel_step( |
| 0, IDS_SIDE_SEARCH_TUTORIAL_OPEN_SIDE_PANEL, |
| ui::InteractionSequence::StepType::kShown, kSideSearchButtonElementId, |
| std::string(), HelpBubbleArrow::kBottomCenter); |
| side_search_description.steps.emplace_back(open_side_search_in_panel_step); |
| |
| // 2nd bubble appears and prompts users to open a link |
| TutorialDescription::Step see_side_search( |
| 0, IDS_SIDE_SEARCH_TUTORIAL_OPEN_A_LINK_TO_TAB, |
| ui::InteractionSequence::StepType::kShown, kSideSearchWebViewElementId, |
| std::string(), HelpBubbleArrow::kLeftCenter); |
| side_search_description.steps.emplace_back(see_side_search); |
| |
| // Hidden step that detects a link is pressed |
| TutorialDescription::Step detect_side_search_result_clicked( |
| 0, 0, ui::InteractionSequence::StepType::kCustomEvent, |
| kSideSearchWebViewElementId, std::string(), HelpBubbleArrow::kNone, |
| kSideSearchResultsClickedCustomEventId); |
| side_search_description.steps.emplace_back( |
| detect_side_search_result_clicked); |
| |
| // 3rd bubble appears and prompts users to press close button |
| TutorialDescription::Step click_close( |
| 0, IDS_SIDE_SEARCH_TUTORIAL_CLOSE_SIDE_PANEL, |
| ui::InteractionSequence::StepType::kShown, |
| kSidePanelCloseButtonElementId, std::string(), |
| HelpBubbleArrow::kTopRight); |
| side_search_description.steps.emplace_back(click_close); |
| |
| // Completion of the tutorial. |
| TutorialDescription::Step success_step( |
| IDS_TUTORIAL_GENERIC_SUCCESS_TITLE, IDS_SIDE_SEARCH_PROMO, |
| ui::InteractionSequence::StepType::kShown, kSideSearchButtonElementId, |
| std::string(), HelpBubbleArrow::kTopRight); |
| side_search_description.steps.emplace_back(success_step); |
| |
| side_search_description.histograms = |
| user_education::MakeTutorialHistograms<kSideSearchTutorialMetricPrefix>( |
| side_search_description.steps.size()); |
| side_search_description.can_be_restarted = true; |
| tutorial_registry.AddTutorial(kSideSearchTutorialId, |
| std::move(side_search_description)); |
| } |
| } |