| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // 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/toolbar/chrome_labs_view_controller.h" |
| |
| #include "base/callback_list.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "chrome/browser/about_flags.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/flag_descriptions.h" |
| #include "chrome/browser/lifetime/application_lifetime.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/toolbar/chrome_labs_prefs.h" |
| #include "chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.h" |
| #include "chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.h" |
| #include "chrome/browser/ui/views/toolbar/chrome_labs_item_view.h" |
| #include "chrome/browser/ui/views/toolbar/chrome_labs_utils.h" |
| #include "components/flags_ui/feature_entry.h" |
| #include "components/flags_ui/flags_state.h" |
| #include "components/flags_ui/flags_storage.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "chrome/browser/ash/settings/about_flags.h" |
| #endif |
| |
| namespace { |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class ChromeLabsSelectedLab { |
| kUnspecifiedSelected = 0, |
| // kReadLaterSelected = 1, |
| // kTabSearchSelected = 2, |
| kTabScrollingSelected = 3, |
| kSidePanelSelected = 4, |
| kLensRegionSearchSelected = 5, |
| kWebUITabStripSelected = 6, |
| kMaxValue = kWebUITabStripSelected, |
| }; |
| |
| void EmitToHistogram(const std::u16string& selected_lab_state, |
| const std::string& internal_name) { |
| const auto get_histogram_name = [](const std::u16string& selected_lab_state) { |
| if (selected_lab_state == base::ASCIIToUTF16(base::StringPiece( |
| flags_ui::kGenericExperimentChoiceDefault))) { |
| return "Toolbar.ChromeLabs.DefaultLabAction"; |
| } else if (selected_lab_state == |
| base::ASCIIToUTF16(base::StringPiece( |
| flags_ui::kGenericExperimentChoiceEnabled))) { |
| return "Toolbar.ChromeLabs.EnableLabAction"; |
| } else if (selected_lab_state == |
| base::ASCIIToUTF16(base::StringPiece( |
| flags_ui::kGenericExperimentChoiceDisabled))) { |
| return "Toolbar.ChromeLabs.DisableLabAction"; |
| } else { |
| return ""; |
| } |
| }; |
| |
| const auto get_enum = [](const std::string& internal_name) { |
| if (internal_name == flag_descriptions::kScrollableTabStripFlagId) { |
| return ChromeLabsSelectedLab::kTabScrollingSelected; |
| } else if (internal_name == flag_descriptions::kSidePanelFlagId) { |
| return ChromeLabsSelectedLab::kSidePanelSelected; |
| } else if (internal_name == |
| flag_descriptions::kEnableLensRegionSearchFlagId) { |
| return ChromeLabsSelectedLab::kLensRegionSearchSelected; |
| #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP) && \ |
| (defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)) |
| } else if (internal_name == flag_descriptions::kWebUITabStripFlagId) { |
| return ChromeLabsSelectedLab::kWebUITabStripSelected; |
| #endif |
| } else { |
| return ChromeLabsSelectedLab::kUnspecifiedSelected; |
| } |
| }; |
| |
| const std::string histogram_name = get_histogram_name(selected_lab_state); |
| if (!histogram_name.empty()) |
| base::UmaHistogramEnumeration(histogram_name, get_enum(internal_name)); |
| } |
| |
| // Returns the number of days since epoch (1970-01-01) in the local timezone. |
| uint32_t GetCurrentDay() { |
| base::TimeDelta delta = base::Time::Now() - base::Time::UnixEpoch(); |
| return base::saturated_cast<uint32_t>(delta.InDays()); |
| } |
| |
| } // namespace |
| |
| ChromeLabsViewController::ChromeLabsViewController( |
| const ChromeLabsBubbleViewModel* model, |
| ChromeLabsBubbleView* chrome_labs_bubble_view, |
| Browser* browser, |
| flags_ui::FlagsState* flags_state, |
| flags_ui::FlagsStorage* flags_storage) |
| : model_(model), |
| chrome_labs_bubble_view_(chrome_labs_bubble_view), |
| browser_(browser), |
| flags_state_(flags_state), |
| flags_storage_(flags_storage) { |
| ParseModelDataAndAddLabs(); |
| SetRestartCallback(); |
| } |
| |
| int ChromeLabsViewController::GetIndexOfEnabledLabState( |
| const flags_ui::FeatureEntry* entry, |
| flags_ui::FlagsState* flags_state, |
| flags_ui::FlagsStorage* flags_storage) { |
| std::set<std::string> enabled_entries; |
| flags_state->GetSanitizedEnabledFlags(flags_storage, &enabled_entries); |
| for (int i = 0; i < entry->NumOptions(); i++) { |
| const std::string name = entry->NameForOption(i); |
| if (enabled_entries.count(name) > 0) |
| return i; |
| } |
| return 0; |
| } |
| |
| void ChromeLabsViewController::ParseModelDataAndAddLabs() { |
| // Create each lab item. |
| const std::vector<LabInfo>& all_labs = model_->GetLabInfo(); |
| for (const auto& lab : all_labs) { |
| const flags_ui::FeatureEntry* entry = |
| flags_state_->FindFeatureEntryByName(lab.internal_name); |
| if (IsChromeLabsFeatureValid(lab, browser_->profile())) { |
| bool valid_entry_type = |
| entry->type == flags_ui::FeatureEntry::FEATURE_VALUE || |
| entry->type == flags_ui::FeatureEntry::FEATURE_WITH_PARAMS_VALUE; |
| DCHECK(valid_entry_type); |
| int default_index = |
| GetIndexOfEnabledLabState(entry, flags_state_, flags_storage_); |
| ChromeLabsItemView* lab_item = chrome_labs_bubble_view_->AddLabItem( |
| lab, default_index, entry, browser_, |
| base::BindRepeating( |
| [](ChromeLabsBubbleView* bubble_view, std::string internal_name, |
| flags_ui::FlagsStorage* flags_storage, |
| ChromeLabsItemView* item_view) { |
| int selected_index = item_view->GetSelectedIndex(); |
| about_flags::SetFeatureEntryEnabled( |
| flags_storage, |
| internal_name + flags_ui::kMultiSeparatorChar + |
| base::NumberToString(selected_index), |
| true); |
| |
| bubble_view->ShowRelaunchPrompt(); |
| EmitToHistogram( |
| item_view->GetFeatureEntry()->DescriptionForOption( |
| selected_index), |
| internal_name); |
| }, |
| chrome_labs_bubble_view_.get(), lab.internal_name, |
| flags_storage_)); |
| if (ShouldLabShowNewBadge(browser_->profile(), lab)) { |
| lab_item->ShowNewBadge(); |
| } |
| } |
| } |
| } |
| |
| void ChromeLabsViewController::RestartToApplyFlags() { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // On Chrome OS be less intrusive and restart inside the user session after |
| // we apply the newly selected flags. |
| VLOG(1) << "Restarting to apply per-session flags..."; |
| ash::about_flags::FeatureFlagsUpdate( |
| *flags_storage_, browser_->profile()->GetOriginalProfile()->GetPrefs()) |
| .UpdateSessionManager(); |
| #endif |
| chrome::AttemptRestart(); |
| } |
| |
| void ChromeLabsViewController::SetRestartCallback() { |
| restart_callback_ = chrome_labs_bubble_view_->RegisterRestartCallback( |
| base::BindRepeating(&ChromeLabsViewController::RestartToApplyFlags, |
| base::Unretained(this))); |
| } |
| |
| bool ChromeLabsViewController::ShouldLabShowNewBadge(Profile* profile, |
| const LabInfo& lab) { |
| // This experiment was added before adding the new badge and is not new. |
| if (lab.internal_name == flag_descriptions::kScrollableTabStripFlagId) { |
| return false; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| DictionaryPrefUpdate update( |
| profile->GetPrefs(), chrome_labs_prefs::kChromeLabsNewBadgeDictAshChrome); |
| #else |
| DictionaryPrefUpdate update(g_browser_process->local_state(), |
| chrome_labs_prefs::kChromeLabsNewBadgeDict); |
| #endif |
| |
| base::Value* new_badge_prefs = update.Get(); |
| |
| DCHECK(new_badge_prefs->FindIntKey(lab.internal_name)); |
| int start_day = *new_badge_prefs->FindIntKey(lab.internal_name); |
| if (start_day == chrome_labs_prefs::kChromeLabsNewExperimentPrefValue) { |
| // Set the dictionary value of this experiment to the number of days since |
| // epoch (1970-01-01). This value is the first day the user sees the new |
| // experiment in Chrome Labs and will be used to determine whether or not to |
| // show the new badge. |
| new_badge_prefs->SetIntKey(lab.internal_name, GetCurrentDay()); |
| return true; |
| } |
| int days_elapsed = GetCurrentDay() - start_day; |
| // Show the new badge for 7 days. If the users sets the clock such that the |
| // current day is now before |start_day| don’t show the new badge. |
| return (days_elapsed < 7) && (days_elapsed >= 0); |
| } |
| |
| void ChromeLabsViewController::RestartToApplyFlagsForTesting() { |
| RestartToApplyFlags(); |
| } |