diff options
30 files changed, 554 insertions, 143 deletions
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 366e173..6c81dc3 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -111,13 +111,6 @@ const Experiment kExperiments[] = { kOsMac, switches::kEnablePredictiveInstant }, - { - "verbatim-instant", // Do not change; see above. - IDS_FLAGS_VERBATIM_INSTANT_NAME, - IDS_FLAGS_VERBATIM_INSTANT_DESCRIPTION, - kOsMac | kOsLinux | kOsWin, - switches::kEnableVerbatimInstant - }, // FIXME(scheib): Add Flags entry for accelerated Compositing, // or pull it and the strings in generated_resources.grd by Dec 2010 // { diff --git a/chrome/browser/gtk/instant_confirm_dialog_gtk.cc b/chrome/browser/gtk/instant_confirm_dialog_gtk.cc index c313cec..ac4529a 100644 --- a/chrome/browser/gtk/instant_confirm_dialog_gtk.cc +++ b/chrome/browser/gtk/instant_confirm_dialog_gtk.cc @@ -10,10 +10,9 @@ #include "chrome/browser/gtk/gtk_chrome_link_button.h" #include "chrome/browser/gtk/gtk_util.h" #include "chrome/browser/instant/instant_confirm_dialog.h" -#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/profile.h" #include "chrome/browser/show_options_url.h" -#include "chrome/common/pref_names.h" #include "googleurl/src/gurl.h" #include "grit/generated_resources.h" @@ -66,13 +65,8 @@ InstantConfirmDialogGtk::~InstantConfirmDialogGtk() { void InstantConfirmDialogGtk::OnDialogResponse(GtkWidget* dialog, int response) { - if (response == GTK_RESPONSE_ACCEPT) { - PrefService* service = profile_->GetPrefs(); - if (service) { - service->SetBoolean(prefs::kInstantEnabled, true); - service->SetBoolean(prefs::kInstantConfirmDialogShown, true); - } - } + if (response == GTK_RESPONSE_ACCEPT) + InstantController::Enable(profile_); delete this; } diff --git a/chrome/browser/gtk/options/general_page_gtk.cc b/chrome/browser/gtk/options/general_page_gtk.cc index 01aef9e..3ce8f45 100644 --- a/chrome/browser/gtk/options/general_page_gtk.cc +++ b/chrome/browser/gtk/options/general_page_gtk.cc @@ -20,6 +20,7 @@ #include "chrome/browser/gtk/options/options_layout_gtk.h" #include "chrome/browser/gtk/options/url_picker_dialog_gtk.h" #include "chrome/browser/instant/instant_confirm_dialog.h" +#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/session_startup_pref.h" @@ -555,7 +556,7 @@ void GeneralPageGtk::OnInstantToggled(GtkWidget* toggle_button) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(instant_checkbox_), false); browser::ShowInstantConfirmDialogIfNecessary(GetWindow(), profile()); } else { - instant_.SetValue(enabled); + InstantController::Disable(profile()); } // TODO(estade): UMA? diff --git a/chrome/browser/instant/instant_confirm_dialog.cc b/chrome/browser/instant/instant_confirm_dialog.cc index 5c6c535..e59a30b 100644 --- a/chrome/browser/instant/instant_confirm_dialog.cc +++ b/chrome/browser/instant/instant_confirm_dialog.cc @@ -4,6 +4,8 @@ #include "chrome/browser/instant/instant_confirm_dialog.h" +#include "chrome/browser/instant/instant_controller.h" +#include "chrome/browser/instant/promo_counter.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/common/pref_names.h" @@ -19,8 +21,12 @@ void ShowInstantConfirmDialogIfNecessary(gfx::NativeWindow parent, if (!prefs) return; + PromoCounter* promo_counter = profile->GetInstantPromoCounter(); + if (promo_counter) + promo_counter->Hide(); + if (prefs->GetBoolean(prefs::kInstantConfirmDialogShown)) { - prefs->SetBoolean(prefs::kInstantEnabled, true); + InstantController::Enable(profile); return; } diff --git a/chrome/browser/instant/instant_confirm_dialog.h b/chrome/browser/instant/instant_confirm_dialog.h index 434d65a..bdd5554 100644 --- a/chrome/browser/instant/instant_confirm_dialog.h +++ b/chrome/browser/instant/instant_confirm_dialog.h @@ -22,9 +22,8 @@ void ShowInstantConfirmDialogIfNecessary(gfx::NativeWindow parent, Profile* profile); // Shows the platform specific dialog to confirm if the user really wants to -// enable instant. If the user accepts the dialog implementations must set -// both |prefs::kInstantEnabled| and |prefs::kInstantConfirmDialogShown| to -// true. +// enable instant. If the user accepts the dialog invoke +// InstantController::Enable. void ShowInstantConfirmDialog(gfx::NativeWindow parent, Profile* profile); diff --git a/chrome/browser/instant/instant_controller.cc b/chrome/browser/instant/instant_controller.cc index 001e238..591fd3b 100644 --- a/chrome/browser/instant/instant_controller.cc +++ b/chrome/browser/instant/instant_controller.cc @@ -5,10 +5,13 @@ #include "chrome/browser/instant/instant_controller.h" #include "base/command_line.h" +#include "base/metrics/histogram.h" +#include "base/rand_util.h" #include "chrome/browser/autocomplete/autocomplete_match.h" #include "chrome/browser/instant/instant_delegate.h" #include "chrome/browser/instant/instant_loader.h" #include "chrome/browser/instant/instant_loader_manager.h" +#include "chrome/browser/instant/promo_counter.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" @@ -24,10 +27,61 @@ // Number of ms to delay between loading urls. static const int kUpdateDelayMS = 200; +static InstantController::Type GetType(Profile* profile) { + return InstantController::IsEnabled(profile, + InstantController::PREDICTIVE_TYPE) ? + InstantController::PREDICTIVE_TYPE : InstantController::VERBATIM_TYPE; +} + +InstantController::InstantController(Profile* profile, + InstantDelegate* delegate) + : delegate_(delegate), + tab_contents_(NULL), + is_active_(false), + commit_on_mouse_up_(false), + last_transition_type_(PageTransition::LINK), + type_(GetType(profile)) { + PrefService* service = profile->GetPrefs(); + if (service) { + // kInstantWasEnabledOnce was added after instant, set it now to make sure + // it is correctly set. + service->SetBoolean(prefs::kInstantEnabledOnce, true); + } +} + +InstantController::~InstantController() { +} + // static void InstantController::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false); prefs->RegisterBooleanPref(prefs::kInstantEnabled, false); + prefs->RegisterBooleanPref(prefs::kInstantEnabledOnce, false); + prefs->RegisterInt64Pref(prefs::kInstantEnabledTime, false); + prefs->RegisterIntegerPref(prefs::kInstantType, 0); + PromoCounter::RegisterUserPrefs(prefs, prefs::kInstantPromo); +} + +// static +void InstantController::RecordMetrics(Profile* profile) { + if (!IsEnabled(profile)) + return; + + PrefService* service = profile->GetPrefs(); + if (service) { + int64 enable_time = service->GetInt64(prefs::kInstantEnabledTime); + if (!enable_time) { + service->SetInt64(prefs::kInstantEnabledTime, + base::Time::Now().ToInternalValue()); + } else { + base::TimeDelta delta = + base::Time::Now() - base::Time::FromInternalValue(enable_time); + std::string name = IsEnabled(profile, PREDICTIVE_TYPE) ? + "Instant.EnabledTime.Predictive" : "Instant.EnabledTime.Verbatim"; + // Histogram from 1 hour to 30 days. + UMA_HISTOGRAM_CUSTOM_COUNTS(name, delta.InHours(), 1, 30 * 24, 50); + } + } } // static @@ -38,34 +92,70 @@ bool InstantController::IsEnabled(Profile* profile) { // static bool InstantController::IsEnabled(Profile* profile, Type type) { + // CommandLine takes precedence. CommandLine* cl = CommandLine::ForCurrentProcess(); - if (type == PREDICTIVE_TYPE) { - return (cl->HasSwitch(switches::kEnablePredictiveInstant) || - (profile->GetPrefs() && - profile->GetPrefs()->GetBoolean(prefs::kInstantEnabled))); + if (type == PREDICTIVE_TYPE && + cl->HasSwitch(switches::kEnablePredictiveInstant)) { + return true; + } + if (type == VERBATIM_TYPE && + cl->HasSwitch(switches::kEnableVerbatimInstant)) { + return true; } - return cl->HasSwitch(switches::kEnableVerbatimInstant); -} -static InstantController::Type GetType(Profile* profile) { - return InstantController::IsEnabled(profile, - InstantController::PREDICTIVE_TYPE) ? - InstantController::PREDICTIVE_TYPE : InstantController::VERBATIM_TYPE; + // Then prefs. + PrefService* prefs = profile->GetPrefs(); + if (!prefs->GetBoolean(prefs::kInstantEnabled)) + return false; + + Type pref_type = prefs->GetInteger(prefs::kInstantType) == + static_cast<int>(PREDICTIVE_TYPE) ? PREDICTIVE_TYPE : VERBATIM_TYPE; + return pref_type == type; } -InstantController::InstantController(Profile* profile, - InstantDelegate* delegate) - : delegate_(delegate), - tab_contents_(NULL), - is_active_(false), - commit_on_mouse_up_(false), - last_transition_type_(PageTransition::LINK), - type_(GetType(profile)) { +// static +void InstantController::Enable(Profile* profile) { + PromoCounter* promo_counter = profile->GetInstantPromoCounter(); + if (promo_counter) + promo_counter->Hide(); + + PrefService* service = profile->GetPrefs(); + if (!service) + return; + + service->SetBoolean(prefs::kInstantEnabled, true); + service->SetBoolean(prefs::kInstantConfirmDialogShown, true); + service->SetInt64(prefs::kInstantEnabledTime, + base::Time::Now().ToInternalValue()); + service->SetBoolean(prefs::kInstantEnabledOnce, true); + // Randomly pick a type. We're doing this to get feedback as to which variant + // folks prefer. + service->SetInteger(prefs::kInstantType, + base::RandInt(static_cast<int>(PREDICTIVE_TYPE), + static_cast<int>(LAST_TYPE))); } -InstantController::~InstantController() { +// static +void InstantController::Disable(Profile* profile) { + PrefService* service = profile->GetPrefs(); + if (!service) + return; + + service->SetBoolean(prefs::kInstantEnabled, false); + + int64 enable_time = service->GetInt64(prefs::kInstantEnabledTime); + if (!enable_time) + return; + + base::TimeDelta delta = + base::Time::Now() - base::Time::FromInternalValue(enable_time); + std::string name = IsEnabled(profile, PREDICTIVE_TYPE) ? + "Instant.TimeToDisable.Predictive" : "Instant.TimeToDisable.Verbatim"; + // histogram from 1 minute to 10 days. + UMA_HISTOGRAM_CUSTOM_COUNTS(name, delta.InMinutes(), 1, 60 * 24 * 10, 50); } + void InstantController::Update(TabContents* tab_contents, const AutocompleteMatch& match, const string16& user_text, diff --git a/chrome/browser/instant/instant_controller.h b/chrome/browser/instant/instant_controller.h index afd6a2d..1d989cb 100644 --- a/chrome/browser/instant/instant_controller.h +++ b/chrome/browser/instant/instant_controller.h @@ -40,12 +40,16 @@ class InstantController : public InstantLoaderDelegate { public: // Variations of instant support. enum Type { + // NOTE: these values are persisted to prefs. Don't change them! + // Search results are shown for the best guess of what we think the user was // planning on typing. - PREDICTIVE_TYPE, + PREDICTIVE_TYPE = 0, // Search results are shown for exactly what was typed. VERBATIM_TYPE, + + LAST_TYPE = VERBATIM_TYPE, }; InstantController(Profile* profile, InstantDelegate* delegate); @@ -54,12 +58,21 @@ class InstantController : public InstantLoaderDelegate { // Registers instant related preferences. static void RegisterUserPrefs(PrefService* prefs); + // Records instant metrics. + static void RecordMetrics(Profile* profile); + // Returns true if either type of instant is enabled. static bool IsEnabled(Profile* profile); // Returns true if the specified type of instant is enabled. static bool IsEnabled(Profile* profile, Type type); + // Enables instant. + static void Enable(Profile* profile); + + // Disables instant. + static void Disable(Profile* profile); + // Invoked as the user types in the omnibox with the url to navigate to. If // the url is empty and there is a preview TabContents it is destroyed. If url // is non-empty and the preview TabContents has not been created it is diff --git a/chrome/browser/instant/instant_opt_in.cc b/chrome/browser/instant/instant_opt_in.cc deleted file mode 100644 index 02db5c5..0000000 --- a/chrome/browser/instant/instant_opt_in.cc +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2010 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/instant/instant_opt_in.h" - -#include "base/command_line.h" -#include "chrome/browser/instant/instant_confirm_dialog.h" -#include "chrome/browser/profile.h" -#include "chrome/common/chrome_switches.h" - -namespace browser { - -static bool dialog_shown = false; - -bool ShouldShowInstantOptIn(Profile* profile) { - if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowInstantOptIn)) - return false; - - // TODO(sky): implement me. - return !dialog_shown; -} - -void UserPickedInstantOptIn(gfx::NativeWindow parent, - Profile* profile, - bool opt_in) { - // TODO: set pref so don't show opt-in again. - dialog_shown = true; - if (opt_in) - browser::ShowInstantConfirmDialogIfNecessary(parent, profile); -} - -} // namespace browser diff --git a/chrome/browser/instant/instant_opt_in.h b/chrome/browser/instant/instant_opt_in.h deleted file mode 100644 index 55c62fb..0000000 --- a/chrome/browser/instant/instant_opt_in.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2010 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. - -#ifndef CHROME_BROWSER_INSTANT_INSTANT_OPT_IN_H_ -#define CHROME_BROWSER_INSTANT_INSTANT_OPT_IN_H_ -#pragma once - -#include "gfx/native_widget_types.h" - -class Profile; - -namespace browser { - -// Returns true if the opt-in should be shown. -bool ShouldShowInstantOptIn(Profile* profile); - -// Invoked if the user clicks on the opt-in promo. -void UserPickedInstantOptIn(gfx::NativeWindow parent, - Profile* profile, - bool opt_in); - -} // namespace browser - -#endif // CHROME_BROWSER_INSTANT_INSTANT_OPT_IN_H_ diff --git a/chrome/browser/instant/promo_counter.cc b/chrome/browser/instant/promo_counter.cc new file mode 100644 index 0000000..34c9b2f --- /dev/null +++ b/chrome/browser/instant/promo_counter.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2010 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/instant/promo_counter.h" + +#include "base/metrics/histogram.h" +#include "base/values.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profile.h" + +// Pref keys. These are relative to pref_key_. +static const char* kShowKey = ".show"; +static const char* kNumSessionsKey = ".num_sessions"; +static const char* kInitialTimeKey = ".initial_time"; + +// Values used for histograms. These are relative to histogram_key_. +static const char* kHistogramHide = ".hide"; +static const char* kHistogramMaxSessions = ".max_sessions"; +static const char* kHistogramMaxTime = ".max_time"; + +PromoCounter::PromoCounter(Profile* profile, + const std::string& pref_key, + const std::string& histogram_key, + int max_sessions, + int max_days) + : profile_(profile), + pref_key_(pref_key), + histogram_key_(histogram_key), + max_sessions_(max_sessions), + max_days_(max_days), + did_init_(false), + show_(false) { +} + +PromoCounter::~PromoCounter() { +} + +// static +void PromoCounter::RegisterUserPrefs(PrefService* prefs, + const std::string& base_key) { + prefs->RegisterBooleanPref((base_key + kShowKey).c_str(), true); + prefs->RegisterIntegerPref((base_key + kNumSessionsKey).c_str(), 0); + prefs->RegisterInt64Pref((base_key + kInitialTimeKey).c_str(), 0); +} + +bool PromoCounter::ShouldShow(base::Time current_time) { + if (!did_init_) { + did_init_ = true; + Init(current_time); + } + + if (show_ && (current_time - initial_show_).InDays() >= max_days_) + MaxTimeLapsed(current_time); + + return show_; +} + +void PromoCounter::Hide() { + show_ = false; + did_init_ = true; + UMA_HISTOGRAM_CUSTOM_COUNTS(histogram_key_ + kHistogramHide, + (base::Time::Now() - initial_show_).InHours(), + 1, max_days_ * 24, 24); + if (profile_->GetPrefs()) + profile_->GetPrefs()->SetBoolean((pref_key_ + kShowKey).c_str(), false); +} + +void PromoCounter::Init(base::Time current_time) { + PrefService* prefs = profile_->GetPrefs(); + if (!prefs) + return; + + show_ = prefs->GetBoolean((pref_key_ + kShowKey).c_str()); + if (!show_) + return; + + // The user hasn't chosen to opt in or out. Only show the opt-in if it's + // less than max_days_ since we first showed the opt-in, or the user hasn't + // launched the profile max_sessions_ times. + int session_count = prefs->GetInteger((pref_key_ + kNumSessionsKey).c_str()); + int64 initial_show_int = + prefs->GetInt64((pref_key_ + kInitialTimeKey).c_str()); + initial_show_ = base::Time(base::Time::FromInternalValue(initial_show_int)); + if (initial_show_int == 0 || initial_show_ > current_time) { + initial_show_ = base::Time::Now(); + prefs->SetInt64((pref_key_ + kInitialTimeKey).c_str(), + initial_show_.ToInternalValue()); + } + if (session_count >= max_sessions_) { + // Time check is handled in ShouldShow. + MaxSessionsEncountered(current_time); + } else { + // Up the session count. + prefs->SetInteger((pref_key_ + kNumSessionsKey).c_str(), session_count + 1); + } +} + +void PromoCounter::MaxSessionsEncountered(base::Time current_time) { + show_ = false; + UMA_HISTOGRAM_CUSTOM_COUNTS(histogram_key_ + kHistogramMaxSessions, + (current_time - initial_show_).InHours(), 1, + max_days_ * 24, 24); + if (profile_->GetPrefs()) + profile_->GetPrefs()->SetBoolean((pref_key_ + kShowKey).c_str(), false); +} + +void PromoCounter::MaxTimeLapsed(base::Time current_time) { + show_ = false; + UMA_HISTOGRAM_CUSTOM_COUNTS(histogram_key_ + kHistogramMaxTime, + (current_time - initial_show_).InHours(), + 1, max_days_ * 24, 24); + if (profile_->GetPrefs()) + profile_->GetPrefs()->SetBoolean((pref_key_ + kShowKey).c_str(), false); +} diff --git a/chrome/browser/instant/promo_counter.h b/chrome/browser/instant/promo_counter.h new file mode 100644 index 0000000..480f677 --- /dev/null +++ b/chrome/browser/instant/promo_counter.h @@ -0,0 +1,78 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_INSTANT_PROMO_COUNTER_H_ +#define CHROME_BROWSER_INSTANT_PROMO_COUNTER_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/time.h" + +class PrefService; +class Profile; + +// PromoCounter is used to track whether a promo should be shown. The promo is +// shown for a specified number of days or sessions (launches of chrome). +class PromoCounter { + public: + // Creates a new PromoCounter. |pref_key| is used to store prefs related to + // the promo. |histogram_key| is the key used to store histograms related to + // the promo. See the .cc file for the exact prefs and histogram values used. + // |ShouldShow| returns true until the users restarts chrome |max_sessions| or + // runs Chrome for |max_days|, or |Hide| is invoked. + PromoCounter(Profile* profile, + const std::string& pref_key, + const std::string& histogram_key, + int max_sessions, + int max_days); + ~PromoCounter(); + + // Registers the preferences used by PromoCounter. + static void RegisterUserPrefs(PrefService* prefs, + const std::string& base_key); + + // Returns true if the promo should be shown. + bool ShouldShow(base::Time current_time); + + // Permanently hides the promo. + void Hide(); + + private: + // Called the first time ShouldShow is invoked. Updates the necessary pref + // state and show_. + void Init(base::Time current_time); + + // Invoked if the max number of sessions has been encountered. + void MaxSessionsEncountered(base::Time current_time); + + // Invoked if the max number of days has elapsed. + void MaxTimeLapsed(base::Time current_time); + + Profile* profile_; + + // Base key all prefs are stored under. + const std::string pref_key_; + + // Base key used for histograms. + const std::string histogram_key_; + + // Max number of sessions/days before the promo stops. + const int max_sessions_; + const int max_days_; + + // Has Init been invoked? + bool did_init_; + + // Return value from ShouldShow. + bool show_; + + // Initial time the promo was first shown. + base::Time initial_show_; + + DISALLOW_COPY_AND_ASSIGN(PromoCounter); +}; + +#endif // CHROME_BROWSER_INSTANT_PROMO_COUNTER_H_ diff --git a/chrome/browser/instant/promo_counter_unittest.cc b/chrome/browser/instant/promo_counter_unittest.cc new file mode 100644 index 0000000..43cda77 --- /dev/null +++ b/chrome/browser/instant/promo_counter_unittest.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2010 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/instant/promo_counter.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +typedef testing::Test PromoCounterTest; + +// Makes sure ShouldShow returns false after the max number of days. +TEST_F(PromoCounterTest, MaxTimeElapsed) { + TestingProfile profile; + PromoCounter::RegisterUserPrefs(profile.GetPrefs(), "test"); + + base::Time test_time(base::Time::Now()); + PromoCounter counter(&profile, "test", "test", 2, 2); + ASSERT_TRUE(counter.ShouldShow(test_time)); + ASSERT_TRUE(counter.ShouldShow(test_time + base::TimeDelta::FromHours(2))); + ASSERT_FALSE(counter.ShouldShow(test_time + base::TimeDelta::FromDays(4))); +} + +// Makes sure ShouldShow returns false after max number of sessions encountered. +TEST_F(PromoCounterTest, MaxSessionsLapsed) { + TestingProfile profile; + PromoCounter::RegisterUserPrefs(profile.GetPrefs(), "test"); + + base::Time test_time(base::Time::Now()); + { + PromoCounter counter(&profile, "test", "test", 2, 2); + ASSERT_TRUE(counter.ShouldShow(test_time)); + } + + { + PromoCounter counter(&profile, "test", "test", 2, 2); + ASSERT_TRUE(counter.ShouldShow(test_time)); + } + + { + PromoCounter counter(&profile, "test", "test", 2, 2); + ASSERT_FALSE(counter.ShouldShow(test_time)); + } +} + +// Makes sure invoking Hide make ShouldShow return false. +TEST_F(PromoCounterTest, Hide) { + TestingProfile profile; + PromoCounter::RegisterUserPrefs(profile.GetPrefs(), "test"); + + base::Time test_time(base::Time::Now()); + { + PromoCounter counter(&profile, "test", "test", 2, 2); + counter.Hide(); + ASSERT_FALSE(counter.ShouldShow(test_time)); + } + + // Recreate to make sure pref was correctly written. + { + PromoCounter counter(&profile, "test", "test", 2, 2); + ASSERT_FALSE(counter.ShouldShow(test_time)); + } +} + +// Same as Hide, but invokes ShouldShow first. +TEST_F(PromoCounterTest, Hide2) { + TestingProfile profile; + PromoCounter::RegisterUserPrefs(profile.GetPrefs(), "test"); + + base::Time test_time(base::Time::Now()); + { + PromoCounter counter(&profile, "test", "test", 2, 2); + ASSERT_TRUE(counter.ShouldShow(test_time)); + counter.Hide(); + ASSERT_FALSE(counter.ShouldShow(test_time)); + } + + { + PromoCounter counter(&profile, "test", "test", 2, 2); + ASSERT_FALSE(counter.ShouldShow(test_time)); + } +} diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index 0bb8892..adf5885 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -581,6 +581,10 @@ class OffTheRecordProfileImpl : public Profile, return profile_->GetExtensionInfoMap(); } + virtual PromoCounter* GetInstantPromoCounter() { + return NULL; + } + private: NotificationRegistrar registrar_; diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h index 7d4e8f5..f2accbf 100644 --- a/chrome/browser/profile.h +++ b/chrome/browser/profile.h @@ -68,6 +68,7 @@ class PersonalDataManager; class PinnedTabService; class PrefService; class ExtensionInfoMap; +class PromoCounter; class ProfileSyncService; class ProfileSyncFactory; class SessionService; @@ -468,6 +469,9 @@ class Profile { // Returns the IO-thread-accessible profile data for this profile. virtual ExtensionInfoMap* GetExtensionInfoMap() = 0; + // Returns the PromoCounter for Instant, or NULL if not applicable. + virtual PromoCounter* GetInstantPromoCounter() = 0; + #if defined(OS_CHROMEOS) // Returns ChromeOS's ProxyConfigServiceImpl, creating if not yet created. virtual chromeos::ProxyConfigServiceImpl* diff --git a/chrome/browser/profile_impl.cc b/chrome/browser/profile_impl.cc index 745b4b9..02f1a2e 100644 --- a/chrome/browser/profile_impl.cc +++ b/chrome/browser/profile_impl.cc @@ -46,6 +46,7 @@ #include "chrome/browser/history/top_sites.h" #include "chrome/browser/host_content_settings_map.h" #include "chrome/browser/host_zoom_map.h" +#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/in_process_webkit/webkit_context.h" #include "chrome/browser/net/chrome_url_request_context.h" #include "chrome/browser/net/gaia/token_service.h" @@ -92,7 +93,9 @@ #endif #if defined(OS_WIN) +#include "chrome/browser/instant/promo_counter.h" #include "chrome/browser/password_manager/password_store_win.h" +#include "chrome/installer/util/install_util.h" #elif defined(OS_MACOSX) #include "chrome/browser/keychain_mac.h" #include "chrome/browser/password_manager/password_store_mac.h" @@ -255,6 +258,9 @@ ProfileImpl::ProfileImpl(const FilePath& path) start_time_(Time::Now()), spellcheck_host_(NULL), spellcheck_host_ready_(false), +#if defined(OS_WIN) + checked_instant_promo_(false), +#endif shutdown_session_service_(false) { DCHECK(!path.empty()) << "Using an empty path will attempt to write " << "profile files to the root directory!"; @@ -348,6 +354,8 @@ ProfileImpl::ProfileImpl(const FilePath& path) // Log the profile size after a reasonable startup delay. BrowserThread::PostDelayedTask(BrowserThread::FILE, FROM_HERE, new ProfileSizeTask(path_), 112000); + + InstantController::RecordMetrics(this); } void ProfileImpl::InitExtensions() { @@ -1303,6 +1311,28 @@ ExtensionInfoMap* ProfileImpl::GetExtensionInfoMap() { return extension_info_map_.get(); } +PromoCounter* ProfileImpl::GetInstantPromoCounter() { +#if defined(OS_WIN) + // TODO: enable this when we're ready to turn on the promo. + /* + if (!checked_instant_promo_) { + checked_instant_promo_ = true; + PrefService* prefs = GetPrefs(); + if (!prefs->GetBoolean(prefs::kInstantEnabledOnce) && + !InstantController::IsEnabled(this) && + InstallUtil::IsChromeSxSProcess()) { + DCHECK(!instant_promo_counter_.get()); + instant_promo_counter_.reset( + new PromoCounter(this, prefs::kInstantPromo, "Instant.Promo", 3, 3)); + } + } + */ + return instant_promo_counter_.get(); +#else + return NULL; +#endif +} + #if defined(OS_CHROMEOS) chromeos::ProxyConfigServiceImpl* ProfileImpl::GetChromeOSProxyConfigServiceImpl() { diff --git a/chrome/browser/profile_impl.h b/chrome/browser/profile_impl.h index a027406..fdae9e2 100644 --- a/chrome/browser/profile_impl.h +++ b/chrome/browser/profile_impl.h @@ -122,6 +122,7 @@ class ProfileImpl : public Profile, void InitCloudPrintProxyService(); virtual ChromeBlobStorageContext* GetBlobStorageContext(); virtual ExtensionInfoMap* GetExtensionInfoMap(); + virtual PromoCounter* GetInstantPromoCounter(); virtual BrowserSignin* GetBrowserSignin(); #if defined(OS_CHROMEOS) @@ -247,6 +248,11 @@ class ProfileImpl : public Profile, // finished. bool spellcheck_host_ready_; +#if defined(OS_WIN) + bool checked_instant_promo_; + scoped_ptr<PromoCounter> instant_promo_counter_; +#endif + // Set to true when ShutdownSessionService is invoked. If true // GetSessionService won't recreate the SessionService. bool shutdown_session_service_; diff --git a/chrome/browser/search_engines/template_url_model.cc b/chrome/browser/search_engines/template_url_model.cc index 6c72f5f..cde70f3 100644 --- a/chrome/browser/search_engines/template_url_model.cc +++ b/chrome/browser/search_engines/template_url_model.cc @@ -576,6 +576,7 @@ void TemplateURLModel::Observe(NotificationType type, } } +// static void TemplateURLModel::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterBooleanPref( prefs::kDefaultSearchProviderEnabled, true); diff --git a/chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view.cc b/chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view.cc index bfd7321..02e0a7a 100644 --- a/chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view.cc +++ b/chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view.cc @@ -15,7 +15,9 @@ #include "chrome/browser/autocomplete/autocomplete_edit_view.h" #include "chrome/browser/autocomplete/autocomplete_match.h" #include "chrome/browser/autocomplete/autocomplete_popup_model.h" -#include "chrome/browser/instant/instant_opt_in.h" +#include "chrome/browser/instant/instant_confirm_dialog.h" +#include "chrome/browser/instant/promo_counter.h" +#include "chrome/browser/profile.h" #include "chrome/browser/views/bubble_border.h" #include "chrome/browser/views/location_bar/location_bar_view.h" #include "gfx/canvas_skia.h" @@ -131,12 +133,13 @@ const int kOptInButtonPadding = 2; // Padding around the opt in view. const int kOptInLeftPadding = 12; -const int kOptInRightPadding = 6; +const int kOptInRightPadding = 10; const int kOptInTopPadding = 6; -const int kOptInBottomPadding = 3; +const int kOptInBottomPadding = 5; -// Padding between the top of the opt-in view and the separator. -const int kOptInSeparatorSpacing = 2; +// Horizontal/Vertical inset of the promo background. +const int kOptInBackgroundHInset = 6; +const int kOptInBackgroundVInset = 2; // Border for instant opt-in buttons. Consists of two 9 patch painters: one for // the normal state, the other for the pressed state. @@ -192,7 +195,10 @@ class AutocompletePopupContentsView::InstantOptInView InstantOptInView(AutocompletePopupContentsView* contents_view, const gfx::Font& label_font, const gfx::Font& button_font) - : contents_view_(contents_view) { + : contents_view_(contents_view), + bg_painter_(views::Painter::CreateVerticalGradient( + SkColorSetRGB(255, 242, 183), + SkColorSetRGB(250, 230, 145))) { views::Label* label = new views::Label(l10n_util::GetString(IDS_INSTANT_OPT_IN_LABEL)); label->SetFont(label_font); @@ -227,11 +233,14 @@ class AutocompletePopupContentsView::InstantOptInView } virtual void Paint(gfx::Canvas* canvas) { - SkColor line_color = color_utils::AlphaBlend(GetColor(NORMAL, DIMMED_TEXT), - GetColor(NORMAL, BACKGROUND), - 48); - canvas->DrawLineInt( - line_color, 0, kOptInSeparatorSpacing, width(), kOptInSeparatorSpacing); + canvas->Save(); + canvas->TranslateInt(kOptInBackgroundHInset, kOptInBackgroundVInset); + bg_painter_->Paint(width() - kOptInBackgroundHInset * 2, + height() - kOptInBackgroundVInset * 2, canvas); + canvas->DrawRectInt(ResourceBundle::toolbar_separator_color, 0, 0, + width() - kOptInBackgroundHInset * 2, + height() - kOptInBackgroundVInset * 2); + canvas->Restore(); } private: @@ -252,6 +261,7 @@ class AutocompletePopupContentsView::InstantOptInView } AutocompletePopupContentsView* contents_view_; + scoped_ptr<views::Painter> bg_painter_; DISALLOW_COPY_AND_ASSIGN(InstantOptInView); }; @@ -849,9 +859,14 @@ void AutocompletePopupContentsView::UpdatePopupAppearance() { for (size_t i = model_->result().size(); i < child_rv_count; ++i) GetChildViewAt(i)->SetVisible(false); - if (!opt_in_view_ && browser::ShouldShowInstantOptIn(model_->profile())) { + PromoCounter* counter = model_->profile()->GetInstantPromoCounter(); + if (!opt_in_view_ && counter && counter->ShouldShow(base::Time::Now())) { opt_in_view_ = new InstantOptInView(this, result_bold_font_, result_font_); AddChildView(opt_in_view_); + } else if (opt_in_view_ && (!counter || + !counter->ShouldShow(base::Time::Now()))) { + delete opt_in_view_; + opt_in_view_ = NULL; } if (opt_in_view_) @@ -1185,7 +1200,12 @@ gfx::Rect AutocompletePopupContentsView::CalculateTargetBounds(int h) { void AutocompletePopupContentsView::UserPressedOptIn(bool opt_in) { delete opt_in_view_; opt_in_view_ = NULL; - browser::UserPickedInstantOptIn(location_bar_->GetWindow()->GetNativeWindow(), - model_->profile(), opt_in); + PromoCounter* counter = model_->profile()->GetInstantPromoCounter(); + DCHECK(counter); + counter->Hide(); + if (opt_in) { + browser::ShowInstantConfirmDialogIfNecessary( + location_bar_->GetWindow()->GetNativeWindow(), model_->profile()); + } UpdatePopupAppearance(); } diff --git a/chrome/browser/ui/views/infobars/infobars.cc b/chrome/browser/ui/views/infobars/infobars.cc index c96f72f..756ce70 100644 --- a/chrome/browser/ui/views/infobars/infobars.cc +++ b/chrome/browser/ui/views/infobars/infobars.cc @@ -7,9 +7,6 @@ #include "app/l10n_util.h" #include "app/resource_bundle.h" #include "app/slide_animation.h" -#if defined(OS_WIN) -#include "app/win_util.h" -#endif // defined(OS_WIN) #include "base/message_loop.h" #include "base/utf_string_conversions.h" #include "chrome/browser/views/event_utils.h" @@ -25,6 +22,10 @@ #include "views/focus/external_focus_tracker.h" #include "views/widget/widget.h" +#if defined(OS_WIN) +#include "app/win_util.h" +#endif + // static const double InfoBar::kDefaultTargetHeight = 36.0; const int InfoBar::kHorizontalPadding = 6; diff --git a/chrome/browser/ui/views/instant_confirm_view.cc b/chrome/browser/ui/views/instant_confirm_view.cc index 4a714b2f..f68647b 100644 --- a/chrome/browser/ui/views/instant_confirm_view.cc +++ b/chrome/browser/ui/views/instant_confirm_view.cc @@ -8,6 +8,7 @@ #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/instant/instant_confirm_dialog.h" +#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/common/pref_names.h" @@ -48,11 +49,7 @@ bool InstantConfirmView::Accept(bool window_closing) { } bool InstantConfirmView::Accept() { - PrefService* prefs = profile_->GetPrefs(); - if (prefs) { - prefs->SetBoolean(prefs::kInstantEnabled, true); - prefs->SetBoolean(prefs::kInstantConfirmDialogShown, true); - } + InstantController::Enable(profile_); return true; } diff --git a/chrome/browser/ui/views/options/general_page_view.cc b/chrome/browser/ui/views/options/general_page_view.cc index e7c4d3e..6259867 100644 --- a/chrome/browser/ui/views/options/general_page_view.cc +++ b/chrome/browser/ui/views/options/general_page_view.cc @@ -9,6 +9,7 @@ #include "base/callback.h" #include "base/message_loop.h" #include "base/string16.h" +#include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/browser.h" @@ -16,6 +17,7 @@ #include "chrome/browser/custom_home_pages_table_model.h" #include "chrome/browser/dom_ui/new_tab_ui.h" #include "chrome/browser/instant/instant_confirm_dialog.h" +#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" @@ -213,6 +215,7 @@ GeneralPageView::GeneralPageView(Profile* profile) default_search_group_(NULL), default_search_manage_engines_button_(NULL), instant_checkbox_(NULL), + instant_type_label_(NULL), instant_link_(NULL), default_browser_group_(NULL), default_browser_status_label_(NULL), @@ -295,7 +298,7 @@ void GeneralPageView::ButtonPressed( browser::ShowInstantConfirmDialogIfNecessary( GetWindow()->GetNativeWindow(), profile()); } else { - profile()->GetPrefs()->SetBoolean(prefs::kInstantEnabled, false); + InstantController::Disable(profile()); } } } @@ -441,8 +444,19 @@ void GeneralPageView::NotifyPrefChanged(const std::string* pref_name) { !show_home_button_.IsManaged()); } - if (!pref_name || *pref_name == prefs::kInstantEnabled) - instant_checkbox_->SetChecked(prefs->GetBoolean(prefs::kInstantEnabled)); + if (!pref_name || *pref_name == prefs::kInstantEnabled) { + bool is_instant_enabled = prefs->GetBoolean(prefs::kInstantEnabled); + instant_checkbox_->SetChecked(is_instant_enabled); + if (is_instant_enabled) { + instant_type_label_->SetText( + L"[" + + UTF8ToWide( + base::IntToString(prefs->GetInteger(prefs::kInstantType))) + + L"]"); + } + instant_type_label_->SetVisible(is_instant_enabled); + instant_type_label_->GetParent()->Layout(); + } } void GeneralPageView::HighlightGroup(OptionsGroup highlight_group) { @@ -655,9 +669,11 @@ void GeneralPageView::InitDefaultSearchGroup() { instant_checkbox_ = new views::Checkbox( l10n_util::GetString(IDS_INSTANT_PREF)); - instant_checkbox_->SetMultiLine(true); + instant_checkbox_->SetMultiLine(false); instant_checkbox_->set_listener(this); + instant_type_label_ = new views::Label(); + instant_link_ = new views::Link(l10n_util::GetString(IDS_LEARN_MORE)); instant_link_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); instant_link_->SetController(this); @@ -677,9 +693,11 @@ void GeneralPageView::InitDefaultSearchGroup() { column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0); - const int single_column_view_set_id = 1; - column_set = layout->AddColumnSet(single_column_view_set_id); - column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + const int checkbox_column_view_set_id = 1; + column_set = layout->AddColumnSet(checkbox_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0); const int link_column_set_id = 2; @@ -697,8 +715,9 @@ void GeneralPageView::InitDefaultSearchGroup() { layout->AddView(default_search_manage_engines_button_); layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); - layout->StartRow(0, single_column_view_set_id); + layout->StartRow(0, checkbox_column_view_set_id); layout->AddView(instant_checkbox_); + layout->AddView(instant_type_label_); layout->AddPaddingRow(0, 0); layout->StartRow(0, link_column_set_id); diff --git a/chrome/browser/ui/views/options/general_page_view.h b/chrome/browser/ui/views/options/general_page_view.h index 129fe5e..970896e 100644 --- a/chrome/browser/ui/views/options/general_page_view.h +++ b/chrome/browser/ui/views/options/general_page_view.h @@ -154,6 +154,7 @@ class GeneralPageView : public OptionsPageView, views::NativeButton* default_search_manage_engines_button_; scoped_ptr<SearchEngineListModel> default_search_engines_model_; views::Checkbox* instant_checkbox_; + views::Label* instant_type_label_; views::Link* instant_link_; // Controls for the Default Browser group diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 5fbfc91..f0a5a6d 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2153,8 +2153,8 @@ 'browser/instant/instant_loader_delegate.h', 'browser/instant/instant_loader_manager.cc', 'browser/instant/instant_loader_manager.h', - 'browser/instant/instant_opt_in.cc', - 'browser/instant/instant_opt_in.h', + 'browser/instant/promo_counter.cc', + 'browser/instant/promo_counter.h', 'browser/intranet_redirect_detector.cc', 'browser/intranet_redirect_detector.h', 'browser/io_thread.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index dbebb62..cef55a2 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1352,6 +1352,7 @@ 'browser/in_process_webkit/dom_storage_dispatcher_host_unittest.cc', 'browser/in_process_webkit/webkit_context_unittest.cc', 'browser/in_process_webkit/webkit_thread_unittest.cc', + 'browser/instant/promo_counter_unittest.cc', 'browser/keychain_mock_mac.cc', 'browser/keychain_mock_mac.h', 'browser/login_prompt_unittest.cc', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index da136d1..f65f86a 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -1004,9 +1004,6 @@ const char kServiceAccountLsid[] = "service-account-lsid"; // See kHideIcons. const char kShowIcons[] = "show-icons"; -// If true the instant opt-in promo is shown in the omnibox. -const char kShowInstantOptIn[] = "show-instant-opt-in"; - // Renders a border around composited Render Layers to help debug and study // layer compositing. const char kShowCompositedLayerBorders[] = "show-composited-layer-borders"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 027b5e9..cbcebe7 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -283,7 +283,6 @@ extern const char kServiceProcess[]; extern const char kServiceAccountLsid[]; extern const char kShowCompositedLayerBorders[]; extern const char kShowIcons[]; -extern const char kShowInstantOptIn[]; extern const char kShowPaintRects[]; extern const char kSilentDumpOnDCHECK[]; extern const char kSimpleDataSource[]; diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index 575b311..93a68cc 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -212,6 +212,19 @@ const char kInstantConfirmDialogShown[] = "instant.confirm_dialog_shown"; // Boolean pref indicating if instant is enabled. const char kInstantEnabled[] = "instant.enabled"; +// Boolean pref indicating if instant was ever enabled. +const char kInstantEnabledOnce[] = "instant.enabled_once"; + +// Time when instant was last enabled. +const char kInstantEnabledTime[] = "instant.enabled_time"; + +// Used to maintain instant promo keys. See PromoCounter for details of subkeys +// that are used. +const char kInstantPromo[] = "instant.promo"; + +// Type of instant. This is one of the enums defined in InstantController::TYPE. +const char kInstantType[] = "instant.type"; + #if defined(USE_NSS) || defined(USE_OPENSSL) // Prefs for SSLConfigServicePref. Currently, these are only present on // and used by NSS/OpenSSL using OSes. diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index f051a78..42232a48 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -80,6 +80,10 @@ extern const char kDisableSpdy[]; extern const char kCookiePromptExpanded[]; extern const char kInstantConfirmDialogShown[]; extern const char kInstantEnabled[]; +extern const char kInstantEnabledOnce[]; +extern const char kInstantEnabledTime[]; +extern const char kInstantPromo[]; +extern const char kInstantType[]; #if defined(USE_NSS) || defined(USE_OPENSSL) extern const char kCertRevocationCheckingEnabled[]; extern const char kSSL2Enabled[]; diff --git a/chrome/test/testing_profile.h b/chrome/test/testing_profile.h index 503c25c..fa593b3 100644 --- a/chrome/test/testing_profile.h +++ b/chrome/test/testing_profile.h @@ -180,9 +180,10 @@ class TestingProfile : public Profile { virtual PasswordStore* GetPasswordStore(ServiceAccessType access) { return NULL; } - // Initialized the profile's PrefService with an explicity specified - // PrefService. Must be called before the TestingProfile. - // The profile takes ownership of |pref|. + // Sets the profile's PrefService. If a pref service hasn't been explicitly + // set GetPrefs creates one, so normally you need not invoke this. If you need + // to set a pref service you must invoke this before GetPrefs. + // TestingPrefService takes ownership of |prefs|. void SetPrefService(PrefService* prefs); virtual PrefService* GetPrefs(); virtual TemplateURLModel* GetTemplateURLModel() { @@ -306,6 +307,7 @@ class TestingProfile : public Profile { virtual CloudPrintProxyService* GetCloudPrintProxyService() { return NULL; } virtual ChromeBlobStorageContext* GetBlobStorageContext() { return NULL; } virtual ExtensionInfoMap* GetExtensionInfoMap() { return NULL; } + virtual PromoCounter* GetInstantPromoCounter() { return NULL; } protected: base::Time start_time_; diff --git a/views/background.cc b/views/background.cc index f135138..ba6216f 100644 --- a/views/background.cc +++ b/views/background.cc @@ -93,12 +93,12 @@ Background* Background::CreateStandardPanelBackground() { //static Background* Background::CreateVerticalGradientBackground( const SkColor& color1, const SkColor& color2) { - Background* background = CreateBackgroundPainter( - true, Painter::CreateVerticalGradient(color1, color2)); - background->SetNativeControlColor( - color_utils::AlphaBlend(color1, color2, 128)); + Background* background = CreateBackgroundPainter( + true, Painter::CreateVerticalGradient(color1, color2)); + background->SetNativeControlColor( + color_utils::AlphaBlend(color1, color2, 128)); - return background; + return background; } //static |