diff options
author | achuith@chromium.org <achuith@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-01 18:53:42 +0000 |
---|---|---|
committer | achuith@chromium.org <achuith@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-01 18:53:42 +0000 |
commit | d1666539b57bf8552e203d355fd09909d36f9732 (patch) | |
tree | 4c923efc9c402f6a9111cf10d5864f044da41b10 /chrome | |
parent | fc921169fc1ba829856b8d27af000781de2cb2e2 (diff) | |
download | chromium_src-d1666539b57bf8552e203d355fd09909d36f9732.zip chromium_src-d1666539b57bf8552e203d355fd09909d36f9732.tar.gz chromium_src-d1666539b57bf8552e203d355fd09909d36f9732.tar.bz2 |
NotificationPromo.
* Split out NotificationPromo helper class for PromoResourceService to handle promo notification.
* Support for views/max_views.
* NotificationPromo has data members for all the prefs fields (start, end, build, time_slice,
max_group, max_views, group, views, text and closed).
* Move notification parsing methods from PromoResourceService to NotificationPromo.
* NotificationPromo can be initialized from json when the promo resource is parsed, or from prefs,
when CanShowNotificationPromo is called.
* NotificationPromo now only writes out prefs upon detecting a new notification.
* NotificationPromo has a Delegate class, useful for testing.
* Static helper methods introduced for extracting time from DictionaryValue, string and prefs.
These may be easily unit-tested in the future.
* Number of additional tests to more thoroughly test parsing, CanShow logic, and static helper
functions like GetNextQuestionValue and NewGroup.
* NewGroup now uses RandInt instead of rand(), so this CL passes lint with no complaints.
* Some additional cleanup of PromoResourceService, esp GetChannel and IsBuildTargeted.
BUG=96290
TEST=Unit tests pass. Use
--promo-server-url='http://achuithz600.mtv.corp.google.com/www/files/promoresource2?hl='
to see the promo. If you exceed 5 views, you have to reset the views count (ntp.promo_views)
in your Preferences.
Review URL: http://codereview.chromium.org/8045012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@103646 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/resources/ntp4/new_tab.js | 3 | ||||
-rw-r--r-- | chrome/browser/ui/webui/ntp/new_tab_page_handler.cc | 34 | ||||
-rw-r--r-- | chrome/browser/ui/webui/ntp/new_tab_page_handler.h | 10 | ||||
-rw-r--r-- | chrome/browser/web_resource/notification_promo.cc | 309 | ||||
-rw-r--r-- | chrome/browser/web_resource/notification_promo.h | 117 | ||||
-rw-r--r-- | chrome/browser/web_resource/promo_resource_service.cc | 275 | ||||
-rw-r--r-- | chrome/browser/web_resource/promo_resource_service.h | 94 | ||||
-rw-r--r-- | chrome/browser/web_resource/promo_resource_service_unittest.cc | 392 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/common/pref_names.cc | 6 | ||||
-rw-r--r-- | chrome/common/pref_names.h | 2 |
11 files changed, 853 insertions, 391 deletions
diff --git a/chrome/browser/resources/ntp4/new_tab.js b/chrome/browser/resources/ntp4/new_tab.js index d28e407..d35660f 100644 --- a/chrome/browser/resources/ntp4/new_tab.js +++ b/chrome/browser/resources/ntp4/new_tab.js @@ -215,8 +215,9 @@ cr.define('ntp4', function() { var serverpromo = localStrings.getString('serverpromo'); if (serverpromo) { showNotification(parseHtmlSubset(serverpromo), [], function() { - chrome.send('closePromo'); + chrome.send('closeNotificationPromo'); }, 60000); + chrome.send('notificationPromoViewed'); } } diff --git a/chrome/browser/ui/webui/ntp/new_tab_page_handler.cc b/chrome/browser/ui/webui/ntp/new_tab_page_handler.cc index cc2c0c5..0d794c6 100644 --- a/chrome/browser/ui/webui/ntp/new_tab_page_handler.cc +++ b/chrome/browser/ui/webui/ntp/new_tab_page_handler.cc @@ -8,6 +8,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/ui/webui/ntp/new_tab_ui.h" +#include "chrome/browser/web_resource/notification_promo.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/pref_names.h" #include "content/common/notification_service.h" @@ -22,8 +23,10 @@ static const char kNTP4IntroURL[] = "http://www.google.com/support/chrome/bin/answer.py?answer=95451"; void NewTabPageHandler::RegisterMessages() { - web_ui_->RegisterMessageCallback("closePromo", NewCallback( - this, &NewTabPageHandler::HandleClosePromo)); + web_ui_->RegisterMessageCallback("closeNotificationPromo", NewCallback( + this, &NewTabPageHandler::HandleCloseNotificationPromo)); + web_ui_->RegisterMessageCallback("notificationPromoViewed", NewCallback( + this, &NewTabPageHandler::HandleNotificationPromoViewed)); web_ui_->RegisterMessageCallback("pageSelected", NewCallback( this, &NewTabPageHandler::HandlePageSelected)); web_ui_->RegisterMessageCallback("introMessageDismissed", NewCallback( @@ -32,13 +35,19 @@ void NewTabPageHandler::RegisterMessages() { this, &NewTabPageHandler::HandleIntroMessageSeen)); } -void NewTabPageHandler::HandleClosePromo(const ListValue* args) { - Profile::FromWebUI(web_ui_)->GetPrefs()->SetBoolean(prefs::kNTPPromoClosed, - true); - NotificationService* service = NotificationService::current(); - service->Notify(chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED, - Source<NewTabPageHandler>(this), - NotificationService::NoDetails()); +void NewTabPageHandler::HandleCloseNotificationPromo(const ListValue* args) { + NotificationPromo notification_promo( + Profile::FromWebUI(web_ui_)->GetPrefs(), NULL); + notification_promo.HandleClosed(); + NotifyPromoResourceChanged(); +} + +void NewTabPageHandler::HandleNotificationPromoViewed(const ListValue* args) { + NotificationPromo notification_promo( + Profile::FromWebUI(web_ui_)->GetPrefs(), NULL); + if (notification_promo.HandleViewed()) { + NotifyPromoResourceChanged(); + } } void NewTabPageHandler::HandlePageSelected(const ListValue* args) { @@ -103,3 +112,10 @@ void NewTabPageHandler::GetLocalizedValues(Profile* profile, void NewTabPageHandler::DismissIntroMessage(PrefService* prefs) { prefs->SetInteger(prefs::kNTP4IntroDisplayCount, kIntroDisplayMax + 1); } + +void NewTabPageHandler::NotifyPromoResourceChanged() { + NotificationService* service = NotificationService::current(); + service->Notify(chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED, + Source<NewTabPageHandler>(this), + NotificationService::NoDetails()); +} diff --git a/chrome/browser/ui/webui/ntp/new_tab_page_handler.h b/chrome/browser/ui/webui/ntp/new_tab_page_handler.h index ad83c7b..6f556ea 100644 --- a/chrome/browser/ui/webui/ntp/new_tab_page_handler.h +++ b/chrome/browser/ui/webui/ntp/new_tab_page_handler.h @@ -21,8 +21,11 @@ class NewTabPageHandler : public WebUIMessageHandler { // WebUIMessageHandler implementation. virtual void RegisterMessages() OVERRIDE; - // Callback for "closePromo". - void HandleClosePromo(const ListValue* args); + // Callback for "closeNotificationPromo". + void HandleCloseNotificationPromo(const ListValue* args); + + // Callback for "notificationPromoViewed". + void HandleNotificationPromoViewed(const ListValue* args); // Callback for "pageSelected". void HandlePageSelected(const ListValue* args); @@ -56,6 +59,9 @@ class NewTabPageHandler : public WebUIMessageHandler { BOOKMARKS_PAGE_ID = 3 << 10, }; + // Helper to send out promo resource change notification. + void NotifyPromoResourceChanged(); + DISALLOW_COPY_AND_ASSIGN(NewTabPageHandler); }; diff --git a/chrome/browser/web_resource/notification_promo.cc b/chrome/browser/web_resource/notification_promo.cc new file mode 100644 index 0000000..3a28915 --- /dev/null +++ b/chrome/browser/web_resource/notification_promo.cc @@ -0,0 +1,309 @@ +// Copyright (c) 2011 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/web_resource/notification_promo.h" + +#include "base/rand_util.h" +#include "base/string_number_conversions.h" +#include "base/time.h" +#include "base/values.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/web_resource/promo_resource_service.h" +#include "chrome/common/pref_names.h" + +namespace { + +// Maximum number of views. +static const int kMaxViews = 1000; + +// Maximum number of hours for each time slice (4 weeks). +static const int kMaxTimeSliceHours = 24 * 7 * 4; + +bool OutOfBounds(int var, int min, int max) { + return var < min || var > max; +} + +static const char kHeaderProperty[] = "topic"; +static const char kArrayProperty[] = "answers"; +static const char kIdentifierProperty[] = "name"; +static const char kStartPropertyValue[] = "promo_start"; +static const char kEndPropertyValue[] = "promo_end"; +static const char kTextProperty[] = "tooltip"; +static const char kTimeProperty[] = "inproduct"; +static const char kParamsProperty[] = "question"; + +// Time getters. +double GetTimeFromDict(const DictionaryValue* dict) { + std::string time_str; + if (!dict->GetString(kTimeProperty, &time_str)) + return 0.0; + + base::Time time; + if (time_str.empty() || !base::Time::FromString(time_str.c_str(), &time)) + return 0.0; + + return time.ToDoubleT(); +} + +double GetTimeFromPrefs(PrefService* prefs, const char* pref) { + return prefs->HasPrefPath(pref) ? prefs->GetDouble(pref) : 0.0; +} + +} // namespace + +NotificationPromo::NotificationPromo(PrefService* prefs, Delegate* delegate) + : prefs_(prefs), + delegate_(delegate), + start_(0.0), + end_(0.0), + build_(0), + time_slice_(0), + max_group_(0), + max_views_(0), + group_(0), + views_(0), + text_(), + closed_(false) { + DCHECK(prefs); +} + +void NotificationPromo::InitFromJson(const DictionaryValue& json) { + DictionaryValue* dict; + if (json.GetDictionary(kHeaderProperty, &dict)) { + ListValue* answers; + if (dict->GetList(kArrayProperty, &answers)) { + for (ListValue::const_iterator it = answers->begin(); + it != answers->end(); + ++it) { + if ((*it)->IsType(Value::TYPE_DICTIONARY)) + Parse(static_cast<DictionaryValue*>(*it)); + } + } + } + + CheckForNewNotification(); +} + +void NotificationPromo::Parse(const DictionaryValue* dict) { + std::string key; + if (dict->GetString(kIdentifierProperty, &key)) { + if (key == kStartPropertyValue) { + ParseParams(dict); + dict->GetString(kTextProperty, &text_); + start_ = GetTimeFromDict(dict); + } else if (key == kEndPropertyValue) { + end_ = GetTimeFromDict(dict); + } + } +} + +void NotificationPromo::ParseParams(const DictionaryValue* dict) { + std::string question; + if (!dict->GetString(kParamsProperty, &question)) + return; + + size_t index = 0; + bool err = false; + + build_ = GetNextQuestionValue(question, &index, &err); + time_slice_ = GetNextQuestionValue(question, &index, &err); + max_group_ = GetNextQuestionValue(question, &index, &err); + max_views_ = GetNextQuestionValue(question, &index, &err); + + if (err || + OutOfBounds(build_, PromoResourceService::NO_BUILD, + PromoResourceService::ALL_BUILDS) || + OutOfBounds(time_slice_, 0, kMaxTimeSliceHours) || + OutOfBounds(max_group_, 0, kMaxGroupSize) || + OutOfBounds(max_views_, 0, kMaxViews)) { + // If values are not valid, do not show promo notification. + DLOG(ERROR) << "Invalid server data, question=" << question << + ", build=" << build_ << + ", time_slice=" << time_slice_ << + ", max_group=" << max_group_ << + ", max_views=" << max_views_; + build_ = PromoResourceService::NO_BUILD; + time_slice_ = 0; + max_group_ = 0; + max_views_ = 0; + } +} + +void NotificationPromo::CheckForNewNotification() { + const double old_start = GetTimeFromPrefs(prefs_, prefs::kNTPPromoStart); + const double old_end = GetTimeFromPrefs(prefs_, prefs::kNTPPromoEnd); + const bool has_views = prefs_->HasPrefPath(prefs::kNTPPromoViewsMax); + + // Trigger a new notification if the times have changed, or if + // we previously never wrote out a max_views preference. + if (old_start != start_ || old_end != end_ || !has_views) + OnNewNotification(); +} + +void NotificationPromo::OnNewNotification() { + group_ = NewGroup(); + WritePrefs(); + if (delegate_) + delegate_->OnNewNotification(StartTimeWithOffset(), end_); +} + +// static +int NotificationPromo::NewGroup() { + return base::RandInt(0, kMaxGroupSize); +} + +// static +void NotificationPromo::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterDoublePref(prefs::kNTPPromoStart, + 0, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterDoublePref(prefs::kNTPPromoEnd, + 0, + PrefService::UNSYNCABLE_PREF); + + prefs->RegisterIntegerPref(prefs::kNTPPromoBuild, + PromoResourceService::ALL_BUILDS, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterIntegerPref(prefs::kNTPPromoGroupTimeSlice, + 0, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterIntegerPref(prefs::kNTPPromoGroupMax, + 0, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterIntegerPref(prefs::kNTPPromoViewsMax, + 0, + PrefService::UNSYNCABLE_PREF); + + prefs->RegisterStringPref(prefs::kNTPPromoLine, + std::string(), + PrefService::UNSYNCABLE_PREF); + prefs->RegisterIntegerPref(prefs::kNTPPromoGroup, + 0, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterIntegerPref(prefs::kNTPPromoViews, + 0, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterBooleanPref(prefs::kNTPPromoClosed, + false, + PrefService::UNSYNCABLE_PREF); +} + + +void NotificationPromo::WritePrefs() { + prefs_->SetDouble(prefs::kNTPPromoStart, start_); + prefs_->SetDouble(prefs::kNTPPromoEnd, end_); + + prefs_->SetInteger(prefs::kNTPPromoBuild, build_); + prefs_->SetInteger(prefs::kNTPPromoGroupTimeSlice, time_slice_); + prefs_->SetInteger(prefs::kNTPPromoGroupMax, max_group_); + prefs_->SetInteger(prefs::kNTPPromoViewsMax, max_views_); + + prefs_->SetString(prefs::kNTPPromoLine, text_); + prefs_->SetInteger(prefs::kNTPPromoGroup, group_); + prefs_->SetInteger(prefs::kNTPPromoViews, views_); + prefs_->SetBoolean(prefs::kNTPPromoClosed, closed_); +} + +void NotificationPromo::InitFromPrefs() { + if (prefs_->HasPrefPath(prefs::kNTPPromoStart)) + start_ = prefs_->GetDouble(prefs::kNTPPromoStart); + + if (prefs_->HasPrefPath(prefs::kNTPPromoEnd)) + end_ = prefs_->GetDouble(prefs::kNTPPromoEnd); + + if (prefs_->HasPrefPath(prefs::kNTPPromoBuild)) + build_ = prefs_->GetInteger(prefs::kNTPPromoBuild); + + if (prefs_->HasPrefPath(prefs::kNTPPromoGroupTimeSlice)) + time_slice_ = prefs_->GetInteger(prefs::kNTPPromoGroupTimeSlice); + + if (prefs_->HasPrefPath(prefs::kNTPPromoGroupMax)) + max_group_ = prefs_->GetInteger(prefs::kNTPPromoGroupMax); + + if (prefs_->HasPrefPath(prefs::kNTPPromoViewsMax)) + max_views_ = prefs_->GetInteger(prefs::kNTPPromoViewsMax); + + if (prefs_->HasPrefPath(prefs::kNTPPromoLine)) + text_ = prefs_->GetString(prefs::kNTPPromoLine); + + if (prefs_->HasPrefPath(prefs::kNTPPromoGroup)) + group_ = prefs_->GetInteger(prefs::kNTPPromoGroup); + + if (prefs_->HasPrefPath(prefs::kNTPPromoViews)) + views_ = prefs_->GetInteger(prefs::kNTPPromoViews); + + if (prefs_->HasPrefPath(prefs::kNTPPromoClosed)) + closed_ = prefs_->GetBoolean(prefs::kNTPPromoClosed); +} + +bool NotificationPromo::CanShow() const { + return !closed_ && + !text_.empty() && + group_ < max_group_ && + views_ < max_views_ && + IsBuildAllowed(build_) && + base::Time::FromDoubleT(StartTimeWithOffset()) < base::Time::Now() && + base::Time::FromDoubleT(end_) > base::Time::Now(); +} + +void NotificationPromo::HandleClosed() { + prefs_->SetBoolean(prefs::kNTPPromoClosed, true); +} + +bool NotificationPromo::HandleViewed() { + if (prefs_->HasPrefPath(prefs::kNTPPromoViewsMax)) + max_views_ = prefs_->GetInteger(prefs::kNTPPromoViewsMax); + + if (prefs_->HasPrefPath(prefs::kNTPPromoViews)) + views_ = prefs_->GetInteger(prefs::kNTPPromoViews); + + prefs_->SetInteger(prefs::kNTPPromoViews, ++views_); + return views_ >= max_views_; +} + +bool NotificationPromo::IsBuildAllowed(int builds_allowed) const { + if (delegate_) // For testing. + return delegate_->IsBuildAllowed(builds_allowed); + else + return PromoResourceService::IsBuildTargeted( + PromoResourceService::GetChannel(), builds_allowed); +} + +double NotificationPromo::StartTimeWithOffset() const { + // Adjust start using group and time slice, adjusted from hours to seconds. + static const double kSecondsInHour = 60.0 * 60.0; + return start_ + group_ * time_slice_ * kSecondsInHour; +} + +// static +int NotificationPromo::GetNextQuestionValue(const std::string& question, + size_t* index, + bool* err) { + if (*err) + return 0; + + size_t new_index = question.find(':', *index); + // Note that substr correctly handles npos. + std::string fragment(question.substr(*index, new_index - *index)); + *index = new_index + 1; + + int value; + *err = !base::StringToInt(fragment, &value); + return *err ? 0 : value; +} + +bool NotificationPromo::operator==(const NotificationPromo& other) const { + return prefs_ == other.prefs_ && + delegate_ == other.delegate_ && + start_ == other.start_ && + end_ == other.end_ && + build_ == other.build_ && + time_slice_ == other.time_slice_ && + max_group_ == other.max_group_ && + max_views_ == other.max_views_ && + group_ == other.group_ && + views_ == other.views_ && + text_ == other.text_ && + closed_ == other.closed_; +} diff --git a/chrome/browser/web_resource/notification_promo.h b/chrome/browser/web_resource/notification_promo.h new file mode 100644 index 0000000..f88b525 --- /dev/null +++ b/chrome/browser/web_resource/notification_promo.h @@ -0,0 +1,117 @@ +// Copyright (c) 2011 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_WEB_RESOURCE_NOTIFICATION_PROMO_H_ +#define CHROME_BROWSER_WEB_RESOURCE_NOTIFICATION_PROMO_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" + +namespace base { + class DictionaryValue; +} + +class PrefService; + +// Helper class for PromoResourceService that parses promo notification info +// from json or prefs. +class NotificationPromo { + public: + class Delegate { + public: + virtual ~Delegate() {} + virtual void OnNewNotification(double start, double end) = 0; + // For testing. + virtual bool IsBuildAllowed(int builds_targeted) const { return false; } + }; + + explicit NotificationPromo(PrefService* prefs, Delegate* delegate); + + // Initialize from json/prefs. + void InitFromJson(const base::DictionaryValue& json); + void InitFromPrefs(); + + // Can this promo be shown? + bool CanShow() const; + + // Calculates promo notification start time with group-based time slice + // offset. + double StartTimeWithOffset() const; + + // Helpers for NewTabPageHandler. + void HandleClosed(); + bool HandleViewed(); // returns true if views exceeds maximum allowed. + + // Register preferences. + static void RegisterUserPrefs(PrefService* prefs); + + private: + // For testing. + friend class NotificationPromoTestDelegate; + FRIEND_TEST_ALL_PREFIXES(PromoResourceServiceTest, GetNextQuestionValueTest); + FRIEND_TEST_ALL_PREFIXES(PromoResourceServiceTest, NewGroupTest); + + // Users are randomly assigned to one of kMaxGroupSize + 1 buckets, in order + // to be able to roll out promos slowly, or display different promos to + // different groups. + static const int kMaxGroupSize = 99; + + // Parse the answers array element. Set the data members of this instance + // and trigger OnNewNotification callback if necessary. + void Parse(const base::DictionaryValue* dict); + + // Set promo notification params from a question string, which is of the form + // <build_type>:<time_slice>:<max_group>:<max_views> + void ParseParams(const base::DictionaryValue* dict); + + // Check if this promo notification is new based on start/end times, + // and trigger events accordingly. + void CheckForNewNotification(); + + // Actions on receiving a new promo notification. + void OnNewNotification(); + + // Create a new promo notification group. + static int NewGroup(); + + // Returns an int converted from the question substring starting at index + // till the next colon. Sets index to the location right after the colon. + // Returns 0 if *err is true, and sets *err to true upon error. + static int GetNextQuestionValue(const std::string& question, + size_t* index, + bool* err); + + // Flush data members to prefs for storage. + void WritePrefs(); + + // Match our channel with specified build type. + bool IsBuildAllowed(int builds_allowed) const; + + // For testing. + bool operator==(const NotificationPromo& other) const; + + PrefService* prefs_; + Delegate* delegate_; + + double start_; + double end_; + + int build_; + int time_slice_; + int max_group_; + int max_views_; + + int group_; + int views_; + std::string text_; + bool closed_; + + DISALLOW_COPY_AND_ASSIGN(NotificationPromo); +}; + +#endif // CHROME_BROWSER_WEB_RESOURCE_NOTIFICATION_PROMO_H_ + diff --git a/chrome/browser/web_resource/promo_resource_service.cc b/chrome/browser/web_resource/promo_resource_service.cc index 7b62757..14c1a30 100644 --- a/chrome/browser/web_resource/promo_resource_service.cc +++ b/chrome/browser/web_resource/promo_resource_service.cc @@ -30,15 +30,7 @@ static const int kStartResourceFetchDelay = 5000; // Delay between calls to update the cache (48 hours), and 3 min in debug mode. static const int kCacheUpdateDelay = 48 * 60 * 60 * 1000; -static const int kDebugCacheUpdateDelay = 3 * 60 * 1000; - -// Users are randomly assigned to one of kNTPPromoGroupSize buckets, in order -// to be able to roll out promos slowly, or display different promos to -// different groups. -static const int kNTPPromoGroupSize = 100; - -// Maximum number of hours for each time slice (4 weeks). -static const int kMaxTimeSliceHours = 24 * 7 * 4; +static const int kTestCacheUpdateDelay = 3 * 60 * 1000; // The version of the service (used to expire the cache when upgrading Chrome // to versions with different types of promos). @@ -51,28 +43,6 @@ static const char kWebStoreButtonProperty[] = "inproduct_target"; static const char kWebStoreLinkProperty[] = "inproduct"; static const char kWebStoreExpireProperty[] = "tooltip"; -chrome::VersionInfo::Channel GetChannel() { - // GetChannel hits the registry on Windows. See http://crbug.com/70898. - base::ThreadRestrictions::ScopedAllowIO allow_io; - return chrome::VersionInfo::GetChannel(); -} - -int GetNextQuestionValue(const std::string& question, - size_t* index, - bool* err) { - if (*err) - return 0; - - size_t new_index = question.find(':', *index); - // Note that substr correctly handles npos. - std::string fragment(question.substr(*index, new_index - *index)); - *index = new_index + 1; - - int value; - *err = !base::StringToInt(fragment, &value); - return value; -} - const char* GetPromoResourceURL() { std::string promo_server_url = CommandLine::ForCurrentProcess()-> GetSwitchValueASCII(switches::kPromoServerURL); @@ -81,9 +51,12 @@ const char* GetPromoResourceURL() { promo_server_url.c_str(); } +bool IsTest() { + return CommandLine::ForCurrentProcess()->HasSwitch(switches::kPromoServerURL); +} + int GetCacheUpdateDelay() { - return CommandLine::ForCurrentProcess()->HasSwitch( - switches::kPromoServerURL) ? kDebugCacheUpdateDelay : kCacheUpdateDelay; + return IsTest() ? kTestCacheUpdateDelay : kCacheUpdateDelay; } } // namespace @@ -106,31 +79,14 @@ void PromoResourceService::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterDoublePref(prefs::kNTPCustomLogoEnd, 0, PrefService::UNSYNCABLE_PREF); - prefs->RegisterDoublePref(prefs::kNTPPromoStart, - 0, - PrefService::UNSYNCABLE_PREF); - prefs->RegisterDoublePref(prefs::kNTPPromoEnd, - 0, - PrefService::UNSYNCABLE_PREF); - prefs->RegisterStringPref(prefs::kNTPPromoLine, - std::string(), - PrefService::UNSYNCABLE_PREF); - prefs->RegisterBooleanPref(prefs::kNTPPromoClosed, - false, - PrefService::UNSYNCABLE_PREF); - prefs->RegisterIntegerPref(prefs::kNTPPromoGroup, - 0, - PrefService::UNSYNCABLE_PREF); - prefs->RegisterIntegerPref( - prefs::kNTPPromoBuild, - ALL_BUILDS, - PrefService::UNSYNCABLE_PREF); - prefs->RegisterIntegerPref(prefs::kNTPPromoGroupTimeSlice, - 0, - PrefService::UNSYNCABLE_PREF); - prefs->RegisterIntegerPref(prefs::kNTPPromoGroupMax, - 0, - PrefService::UNSYNCABLE_PREF); + NotificationPromo::RegisterUserPrefs(prefs); +} + +// static +chrome::VersionInfo::Channel PromoResourceService::GetChannel() { + // GetChannel hits the registry on Windows. See http://crbug.com/70898. + base::ThreadRestrictions::ScopedAllowIO allow_io; + return chrome::VersionInfo::GetChannel(); } // static @@ -173,7 +129,7 @@ void PromoResourceService::Init() { ScheduleNotificationOnInit(); } -bool PromoResourceService::IsThisBuildTargeted(int builds_targeted) { +bool PromoResourceService::IsBuildTargeted(int builds_targeted) { if (channel_ == chrome::VersionInfo::CHANNEL_UNKNOWN) channel_ = GetChannel(); @@ -186,6 +142,10 @@ void PromoResourceService::Unpack(const DictionaryValue& parsed_json) { UnpackWebStoreSignal(parsed_json); } +void PromoResourceService::OnNewNotification(double start, double end) { + ScheduleNotification(start, end); +} + void PromoResourceService::ScheduleNotification(double promo_start, double promo_end) { if (promo_start > 0 && promo_end > 0) { @@ -240,199 +200,14 @@ std::string PromoResourceService::GetPromoLocale() { void PromoResourceService::UnpackNotificationSignal( const DictionaryValue& parsed_json) { - // Check for newly received start and end values. - std::string promo_start_string, promo_end_string; - - DictionaryValue* topic_dict; - if (parsed_json.GetDictionary("topic", &topic_dict)) { - ListValue* answer_list; - if (topic_dict->GetList("answers", &answer_list)) { - for (ListValue::const_iterator answer_iter = answer_list->begin(); - answer_iter != answer_list->end(); ++answer_iter) { - if (!(*answer_iter)->IsType(Value::TYPE_DICTIONARY)) - continue; - - ParseNotification(static_cast<DictionaryValue*>(*answer_iter), - &promo_start_string, &promo_end_string); - } - } - } - - CheckForNewNotification(promo_start_string, promo_end_string); -} - -void PromoResourceService::ParseNotification( - DictionaryValue* a_dic, - std::string* promo_start_string, - std::string* promo_end_string) { - std::string promo_signal; - if (a_dic->GetString("name", &promo_signal)) { - if (promo_signal == "promo_start") { - SetNotificationParams(a_dic); - SetNotificationLine(a_dic); - - a_dic->GetString("inproduct", promo_start_string); - } else if (promo_signal == "promo_end") { - a_dic->GetString("inproduct", promo_end_string); - } - } -} - -void PromoResourceService::SetNotificationParams(DictionaryValue* a_dic) { - std::string question; - a_dic->GetString("question", &question); - - size_t index(0); - bool err(false); - int promo_build = GetNextQuestionValue(question, &index, &err); - int time_slice = GetNextQuestionValue(question, &index, &err); - int max_group = GetNextQuestionValue(question, &index, &err); - - if (err || - promo_build < 0 || - promo_build > ALL_BUILDS || - time_slice < 0 || - time_slice > kMaxTimeSliceHours || - max_group < 0 || - max_group >= kNTPPromoGroupSize) { - // If values are not valid, do not show promo. - NOTREACHED() << "Invalid server data, question=" << question << - ", build=" << promo_build << - ", time_slice=" << time_slice << - ", max_group=" << max_group; - promo_build = NO_BUILD; - time_slice = 0; - max_group = 0; - } - - prefs_->SetInteger(prefs::kNTPPromoBuild, promo_build); - prefs_->SetInteger(prefs::kNTPPromoGroupTimeSlice, time_slice); - prefs_->SetInteger(prefs::kNTPPromoGroupMax, max_group); -} - -void PromoResourceService::SetNotificationLine(DictionaryValue* a_dic) { - std::string promo_line; - a_dic->GetString("tooltip", &promo_line); - if (!promo_line.empty()) - prefs_->SetString(prefs::kNTPPromoLine, promo_line); -} - -void PromoResourceService::CheckForNewNotification( - const std::string& promo_start_string, - const std::string& promo_end_string) { - double promo_start = 0.0, promo_end = 0.0; - ParseNewNotificationTimes(promo_start_string, promo_end_string, - &promo_start, &promo_end); - - double old_promo_start = 0.0, old_promo_end = 0.0; - GetCurrentNotificationTimes(&old_promo_start, &old_promo_end); - - // If start or end times have changed, trigger a new web resource - // notification, so that the logo on the NTP is updated. This check is - // outside the reading of the web resource data, because the absence of - // dates counts as a triggering change if there were dates before. - // Also create new promo groups, and reset the promo closed preference, - // to signal a new promo. - if (old_promo_start != promo_start || old_promo_end != promo_end) - OnNewNotification(promo_start, promo_end); -} - -void PromoResourceService::ParseNewNotificationTimes( - const std::string& promo_start_string, - const std::string& promo_end_string, - double* promo_start, - double* promo_end) { - *promo_start = *promo_end = 0.0; - - if (promo_start_string.empty() && !promo_end_string.empty()) - return; - - base::Time start_time, end_time; - if (!base::Time::FromString(promo_start_string.c_str(), &start_time) || - !base::Time::FromString(promo_end_string.c_str(), &end_time)) - return; - - *promo_start = start_time.ToDoubleT(); - *promo_end = end_time.ToDoubleT(); -} - -void PromoResourceService::GetCurrentNotificationTimes(double* old_promo_start, - double* old_promo_end) { - *old_promo_start = *old_promo_end = 0.0; - if (prefs_->HasPrefPath(prefs::kNTPPromoStart) && - prefs_->HasPrefPath(prefs::kNTPPromoEnd)) { - *old_promo_start = prefs_->GetDouble(prefs::kNTPPromoStart); - *old_promo_end = prefs_->GetDouble(prefs::kNTPPromoEnd); - } -} - -int PromoResourceService::ResetNotificationGroup() { - srand(static_cast<uint32>(time(NULL))); - const int promo_group = rand() % kNTPPromoGroupSize; - prefs_->SetInteger(prefs::kNTPPromoGroup, promo_group); - return promo_group; -} - -// static -double PromoResourceService::GetNotificationStartTime(PrefService* prefs) { - if (!prefs->HasPrefPath(prefs::kNTPPromoStart)) - return 0.0; - - const double promo_start = prefs->GetDouble(prefs::kNTPPromoStart); - - if (!prefs->HasPrefPath(prefs::kNTPPromoGroup) || - !prefs->HasPrefPath(prefs::kNTPPromoGroupTimeSlice)) - return promo_start; - - const int promo_group = prefs->GetInteger(prefs::kNTPPromoGroup); - const int time_slice = prefs->GetInteger(prefs::kNTPPromoGroupTimeSlice); - // Adjust promo_start using group time slice, adjusted from hours to seconds. - static const double kSecondsInHour = 60.0 * 60.0; - return promo_start + promo_group * time_slice * kSecondsInHour; -} - -void PromoResourceService::OnNewNotification(double promo_start, - double promo_end) { - ResetNotificationGroup(); - - prefs_->SetBoolean(prefs::kNTPPromoClosed, false); - - prefs_->SetDouble(prefs::kNTPPromoStart, promo_start); - prefs_->SetDouble(prefs::kNTPPromoEnd, promo_end); - - ScheduleNotification(GetNotificationStartTime(prefs_), promo_end); + NotificationPromo notification_promo(prefs_, this); + notification_promo.InitFromJson(parsed_json); } bool PromoResourceService::CanShowNotificationPromo(Profile* profile) { - PrefService* prefs = profile->GetPrefs(); - - // Check if promo has been closed by the user. - if (prefs->HasPrefPath(prefs::kNTPPromoClosed) && - prefs->GetBoolean(prefs::kNTPPromoClosed)) - return false; - - // Check if our build is appropriate for this promo. - if (!prefs->HasPrefPath(prefs::kNTPPromoBuild) || - !IsBuildTargeted(GetChannel(), prefs->GetInteger(prefs::kNTPPromoBuild))) - return false; - - // Check if we are in the right group for this promo. - if (!prefs->FindPreference(prefs::kNTPPromoGroup) || - !prefs->FindPreference(prefs::kNTPPromoGroupMax) || - (prefs->GetInteger(prefs::kNTPPromoGroup) >= - prefs->GetInteger(prefs::kNTPPromoGroupMax))) - return false; - - // Check if we are in the right time window for this promo. - if (!prefs->FindPreference(prefs::kNTPPromoStart) || - !prefs->FindPreference(prefs::kNTPPromoEnd) || - base::Time::FromDoubleT(GetNotificationStartTime(prefs)) > - base::Time::Now() || - base::Time::FromDoubleT(prefs->GetDouble(prefs::kNTPPromoEnd)) < - base::Time::Now()) - return false; - - return prefs->HasPrefPath(prefs::kNTPPromoLine); + NotificationPromo notification_promo(profile->GetPrefs(), NULL); + notification_promo.InitFromPrefs(); + return notification_promo.CanShow(); } void PromoResourceService::UnpackWebStoreSignal( @@ -497,7 +272,7 @@ void PromoResourceService::UnpackWebStoreSignal( !a_dic->GetString(kWebStoreExpireProperty, &promo_data.expire)) continue; - if (IsThisBuildTargeted(target_builds)) { + if (IsBuildTargeted(target_builds)) { // The downloader will set the promo prefs and send the // NOTIFICATION_WEB_STORE_PROMO_LOADED notification. promo_data.link = GURL(promo_link); diff --git a/chrome/browser/web_resource/promo_resource_service.h b/chrome/browser/web_resource/promo_resource_service.h index 0aed63e..1077cbf 100644 --- a/chrome/browser/web_resource/promo_resource_service.h +++ b/chrome/browser/web_resource/promo_resource_service.h @@ -8,6 +8,7 @@ #include <string> +#include "chrome/browser/web_resource/notification_promo.h" #include "chrome/browser/web_resource/web_resource_service.h" #include "chrome/common/chrome_version_info.h" @@ -27,8 +28,19 @@ class Profile; // messages, which have until now been piggybacked onto the old tips server // structure. (see http://crbug.com/70634 for details.) class PromoResourceService - : public WebResourceService { + : public WebResourceService, + public NotificationPromo::Delegate { public: + // Identifies types of Chrome builds for promo targeting. + enum BuildType { + NO_BUILD = 0, + DEV_BUILD = 1, + BETA_BUILD = 1 << 1, + STABLE_BUILD = 1 << 2, + CANARY_BUILD = 1 << 3, + ALL_BUILDS = (1 << 4) - 1, + }; + // Checks for conditions to show promo: start/end times, channel, etc. static bool CanShowNotificationPromo(Profile* profile); @@ -38,13 +50,15 @@ class PromoResourceService explicit PromoResourceService(Profile* profile); + static chrome::VersionInfo::Channel GetChannel(); + static bool IsBuildTargeted(chrome::VersionInfo::Channel, int builds_allowed); + // Default server of dynamically loaded NTP HTML elements. static const char* kDefaultPromoResourceServer; private: - FRIEND_TEST_ALL_PREFIXES(PromoResourceServiceTest, IsBuildTargeted); + FRIEND_TEST_ALL_PREFIXES(PromoResourceServiceTest, IsBuildTargetedTest); FRIEND_TEST_ALL_PREFIXES(PromoResourceServiceTest, UnpackLogoSignal); - FRIEND_TEST_ALL_PREFIXES(PromoResourceServiceTest, UnpackNotificationSignal); FRIEND_TEST_ALL_PREFIXES(PromoResourceServiceTest, UnpackWebStoreSignal); FRIEND_TEST_ALL_PREFIXES( PromoResourceServiceTest, UnpackPartialWebStoreSignal); @@ -55,16 +69,6 @@ class PromoResourceService FRIEND_TEST_ALL_PREFIXES( PromoResourceServiceTest, UnpackWebStoreSignalHttpLogo); - // Identifies types of Chrome builds for promo targeting. - enum BuildType { - NO_BUILD = 0, - DEV_BUILD = 1, - BETA_BUILD = 1 << 1, - STABLE_BUILD = 1 << 2, - CANARY_BUILD = 1 << 3, - ALL_BUILDS = (1 << 4) - 1, - }; - virtual ~PromoResourceService(); int GetPromoServiceVersion(); @@ -75,17 +79,14 @@ class PromoResourceService void Init(); - static bool IsBuildTargeted(chrome::VersionInfo::Channel channel, - int builds_targeted); - // Returns true if |builds_targeted| includes the release channel Chrome // belongs to. For testing purposes, you can override the current channel // with set_channel. - bool IsThisBuildTargeted(int builds_targeted); + bool IsBuildTargeted(int builds_targeted); // Schedule a notification that a web resource is either going to become // available or be no longer valid. - void ScheduleNotification(double ms_start_time, double ms_end_time); + void ScheduleNotification(double start, double end); // Schedules the initial notification for when the web resource is going // to become available or no longer valid. This performs a few additional @@ -96,18 +97,19 @@ class PromoResourceService // Overrides the current Chrome release channel for testing purposes. void set_channel(chrome::VersionInfo::Channel channel) { channel_ = channel; } - virtual void Unpack(const base::DictionaryValue& parsed_json); + // WebResourceService override. + virtual void Unpack(const base::DictionaryValue& parsed_json) OVERRIDE; - // Unpack the web resource as a custom promo signal. Expects a start and end - // signal, with the promo to be shown in the tooltip of the start signal - // field. Delivery will be in json in the form of: + // Unpack the web resource as a custom notification signal. Expects a start + // and end signal, with the promo to be shown in the tooltip of the start + // signal field. Delivery will be in json in the form of: // { // "topic": { // "answers": [ // { // "answer_id": "1067976", // "name": "promo_start", - // "question": "1:24:10", + // "question": "1:24:10:10", // "tooltip": // "Click \u003ca href=http://www.google.com\u003ehere\u003c/a\u003e!", // "inproduct": "10/8/09 12:00", @@ -136,9 +138,10 @@ class PromoResourceService // this promo (see the BuildType enum in web_resource_service.cc), the // number of hours that each promo group should see it, and the maximum promo // group that should see it, separated by ":". - // For example, "7:24:5" would indicate that all groups with ids less than 5, - // and with dev, beta and stable builds, should see the promo. The groups - // ramp up so 1 additional group sees the promo every 24 hours. + // For example, "7:24:5:10" would indicate that all groups with ids less than + // 5, and with dev, beta and stable builds, should see the promo a maximum of + // 10 times. The groups ramp up so 1 additional group sees the promo every + // 24 hours. // void UnpackNotificationSignal(const base::DictionaryValue& parsed_json); @@ -202,43 +205,8 @@ class PromoResourceService // answer_id: the promo's id void UnpackWebStoreSignal(const base::DictionaryValue& parsed_json); - // Parse the answers array element. - void ParseNotification(base::DictionaryValue* a_dic, - std::string* promo_start_string, - std::string* promo_end_string); - - // Set notification promo params from a question string, which is of the form - // <build_type>:<time_slice>:<max_group>. - void SetNotificationParams(base::DictionaryValue* a_dic); - - // Extract the notification promo text from the tooltip string. - void SetNotificationLine(base::DictionaryValue* a_dic); - - // Check if this notification promo is new based on start/end times, - // and trigger events accordingly. - void CheckForNewNotification(const std::string& promo_start_string, - const std::string& promo_end_string); - - // Calculate the notification promo times, taking into account our group, and - // the group time slice. - void ParseNewNotificationTimes(const std::string& promo_start_string, - const std::string& promo_end_string, - double* promo_start, - double* promo_end); - - // Calculates notification promo start time with group-based time slice - // offset. - static double GetNotificationStartTime(PrefService* prefs); - - // Create a new notification promo group. - int ResetNotificationGroup(); - - // Get saved notification promo times. - void GetCurrentNotificationTimes(double* old_promo_start, - double* old_promo_end); - - // Actions on receiving a new notification promo. - void OnNewNotification(double promo_start, double promo_end); + // NotificationPromo::Delegate override. + virtual void OnNewNotification(double start, double end) OVERRIDE; // The profile this service belongs to. Profile* profile_; diff --git a/chrome/browser/web_resource/promo_resource_service_unittest.cc b/chrome/browser/web_resource/promo_resource_service_unittest.cc index 089f811..6b6a8ed 100644 --- a/chrome/browser/web_resource/promo_resource_service_unittest.cc +++ b/chrome/browser/web_resource/promo_resource_service_unittest.cc @@ -10,6 +10,7 @@ #include "chrome/browser/extensions/apps_promo.h" #include "chrome/browser/prefs/browser_prefs.h" #include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/web_resource/notification_promo.h" #include "chrome/browser/web_resource/promo_resource_service.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/testing_browser_process.h" @@ -111,78 +112,337 @@ TEST_F(PromoResourceServiceTest, UnpackLogoSignal) { EXPECT_EQ(logo_end, 0); // date value reset to 0; } -TEST_F(PromoResourceServiceTest, UnpackNotificationSignal) { +class NotificationPromoTestDelegate : public NotificationPromo::Delegate { + public: + explicit NotificationPromoTestDelegate(PrefService* prefs) + : prefs_(prefs), + notification_promo_(NULL), + received_notification_(false), + build_targeted_(true), + start_(0.0), + end_(0.0), + build_(PromoResourceService::ALL_BUILDS), + time_slice_(0), + max_group_(0), + max_views_(0), + text_(), + closed_(false) { + } + + void Init(NotificationPromo* notification_promo, + const std::string& json, + double start, double end, + int build, int time_slice, int max_group, int max_views, + const std::string& text, bool closed) { + notification_promo_ = notification_promo; + + test_json_.reset(static_cast<DictionaryValue*>( + base::JSONReader::Read(json, false))); + + start_ = start; + end_ = end; + + build_ = build; + time_slice_ = time_slice; + max_group_ = max_group; + max_views_ = max_views; + + text_ = text; + closed_ = closed; + + received_notification_ = false; + } + + // NotificationPromo::Delegate implementation. + virtual void OnNewNotification(double start, double end) { + EXPECT_EQ(CalcStart(), start); + EXPECT_EQ(notification_promo_->StartTimeWithOffset(), start); + EXPECT_EQ(notification_promo_->end_, end); + received_notification_ = true; + } + + virtual bool IsBuildAllowed(int builds_targeted) const { + EXPECT_EQ(builds_targeted, build_); + return build_targeted_; + } + + const base::DictionaryValue& TestJson() const { + return *test_json_; + } + + double CalcStart() const { + return start_ + notification_promo_->group_ * time_slice_ * 60.0 * 60.0; + } + + void InitPromoFromJson(bool should_receive_notification) { + received_notification_ = false; + notification_promo_->InitFromJson(TestJson()); + EXPECT_TRUE(received_notification_ == should_receive_notification); + + // Test the fields. + TestNotification(); + TestPrefs(); + } + + void TestNotification() { + // Check values. + EXPECT_EQ(notification_promo_->start_, start_); + EXPECT_EQ(notification_promo_->end_, end_); + EXPECT_EQ(notification_promo_->build_, build_); + EXPECT_EQ(notification_promo_->time_slice_, time_slice_); + EXPECT_EQ(notification_promo_->max_group_, max_group_); + EXPECT_EQ(notification_promo_->max_views_, max_views_); + EXPECT_EQ(notification_promo_->text_, text_); + EXPECT_EQ(notification_promo_->closed_, closed_); + + // Check group within bounds. + EXPECT_GE(notification_promo_->group_, 0); + EXPECT_LT(notification_promo_->group_, 100); + + // Views should be 0 for now. + EXPECT_EQ(notification_promo_->views_, 0); + + // Check calculated time. + EXPECT_EQ(notification_promo_->StartTimeWithOffset(), CalcStart()); + } + + void TestPrefs() { + EXPECT_EQ(prefs_->GetDouble(prefs::kNTPPromoStart), start_); + EXPECT_EQ(prefs_->GetDouble(prefs::kNTPPromoEnd), end_); + + EXPECT_EQ(prefs_->GetInteger(prefs::kNTPPromoBuild), build_); + EXPECT_EQ(prefs_->GetInteger(prefs::kNTPPromoGroupTimeSlice), time_slice_); + EXPECT_EQ(prefs_->GetInteger(prefs::kNTPPromoGroupMax), max_group_); + EXPECT_EQ(prefs_->GetInteger(prefs::kNTPPromoViewsMax), max_views_); + + EXPECT_EQ(prefs_->GetInteger(prefs::kNTPPromoGroup), + notification_promo_ ? notification_promo_->group_: 0); + EXPECT_EQ(prefs_->GetInteger(prefs::kNTPPromoViews), 0); + EXPECT_EQ(prefs_->GetString(prefs::kNTPPromoLine), text_); + EXPECT_EQ(prefs_->GetBoolean(prefs::kNTPPromoClosed), closed_); + } + + // Create a new NotificationPromo from prefs and compare to current + // notification. + void TestInitFromPrefs() { + NotificationPromo prefs_notification_promo(prefs_, this); + prefs_notification_promo.InitFromPrefs(); + const bool is_equal = *notification_promo_ == prefs_notification_promo; + EXPECT_TRUE(is_equal); + } + + void TestGroup() { + // Test out of range groups. + for (int i = max_group_; i <= NotificationPromo::kMaxGroupSize; ++i) { + notification_promo_->group_ = i; + EXPECT_FALSE(notification_promo_->CanShow()); + } + + // Test in-range groups. + for (int i = 0; i < max_group_; ++i) { + notification_promo_->group_ = i; + EXPECT_TRUE(notification_promo_->CanShow()); + } + } + + void TestViews() { + // Test out of range views. + for (int i = max_views_; i < 100; ++i) { + notification_promo_->views_ = i; + EXPECT_FALSE(notification_promo_->CanShow()); + } + + // Test in range views. + for (int i = 0; i < max_views_; ++i) { + notification_promo_->views_ = i; + EXPECT_TRUE(notification_promo_->CanShow()); + } + } + + void TestBuild() { + build_targeted_ = false; + EXPECT_FALSE(notification_promo_->CanShow()); + + build_targeted_ = true; + EXPECT_TRUE(notification_promo_->CanShow()); + } + + void TestClosed() { + notification_promo_->closed_ = true; + EXPECT_FALSE(notification_promo_->CanShow()); + + notification_promo_->closed_ = false; + EXPECT_TRUE(notification_promo_->CanShow()); + } + + void TestText() { + notification_promo_->text_.clear(); + EXPECT_FALSE(notification_promo_->CanShow()); + + notification_promo_->text_ = text_; + EXPECT_TRUE(notification_promo_->CanShow()); + } + + void TestTime() { + const double now = base::Time::Now().ToDoubleT(); + const double qhour = 15 * 60; + + notification_promo_->group_ = 0; // For simplicity. + + notification_promo_->start_ = now - qhour; + notification_promo_->end_ = now + qhour; + EXPECT_TRUE(notification_promo_->CanShow()); + + // Start time has not arrived. + notification_promo_->start_ = now + qhour; + notification_promo_->end_ = now + qhour; + EXPECT_FALSE(notification_promo_->CanShow()); + + // End time has past. + notification_promo_->start_ = now - qhour; + notification_promo_->end_ = now - qhour; + EXPECT_FALSE(notification_promo_->CanShow()); + + notification_promo_->start_ = start_; + notification_promo_->end_ = end_; + EXPECT_TRUE(notification_promo_->CanShow()); + } + + private: + PrefService* prefs_; + NotificationPromo* notification_promo_; + bool received_notification_; + bool build_targeted_; + scoped_ptr<DictionaryValue> test_json_; + + double start_; + double end_; + + int build_; + int time_slice_; + int max_group_; + int max_views_; + + std::string text_; + bool closed_; +}; + +TEST_F(PromoResourceServiceTest, NotificationPromoTest) { + // Check that prefs are set correctly. + PrefService* prefs = profile_.GetPrefs(); + ASSERT_TRUE(prefs != NULL); + + NotificationPromoTestDelegate delegate(prefs); + NotificationPromo notification_promo(prefs, &delegate); + + // Make sure prefs are unset. + delegate.TestPrefs(); + // Set up start and end dates and promo line in a Dictionary as if parsed // from the service. - std::string json = "{ " - " \"topic\": {" - " \"answers\": [" - " {" - " \"name\": \"promo_start\"," - " \"question\": \"3:2:5\"," - " \"tooltip\": \"Eat more pie!\"," - " \"inproduct\": \"31/01/10 01:00 GMT\"" - " }," - " {" - " \"name\": \"promo_end\"," - " \"inproduct\": \"31/01/12 01:00 GMT\"" - " }" - " ]" - " }" - "}"; - scoped_ptr<DictionaryValue> test_json(static_cast<DictionaryValue*>( - base::JSONReader::Read(json, false))); + delegate.Init(¬ification_promo, + "{ " + " \"topic\": {" + " \"answers\": [" + " {" + " \"name\": \"promo_start\"," + " \"question\": \"3:2:5:10\"," + " \"tooltip\": \"Eat more pie!\"," + " \"inproduct\": \"31/01/10 01:00 GMT\"" + " }," + " {" + " \"name\": \"promo_end\"," + " \"inproduct\": \"31/01/12 01:00 GMT\"" + " }" + " ]" + " }" + "}", + 1264899600, // unix epoch for Jan 31 2010 0100 GMT. + 1327971600, // unix epoch for Jan 31 2012 0100 GMT. + 3, 2, 5, 10, + "Eat more pie!", false); + + delegate.InitPromoFromJson(true); + + // Second time should not trigger a notification. + delegate.InitPromoFromJson(false); + + delegate.TestInitFromPrefs(); + + // Test various conditions of CanShow. + // TestGroup Has the side effect of setting us to a passing group. + delegate.TestGroup(); + delegate.TestViews(); + delegate.TestBuild(); + delegate.TestClosed(); + delegate.TestText(); + delegate.TestTime(); +} +TEST_F(PromoResourceServiceTest, NotificationPromoTestFail) { // Check that prefs are set correctly. - web_resource_service_->UnpackNotificationSignal(*(test_json.get())); PrefService* prefs = profile_.GetPrefs(); ASSERT_TRUE(prefs != NULL); - std::string promo_line = prefs->GetString(prefs::kNTPPromoLine); - EXPECT_EQ(promo_line, "Eat more pie!"); - - int promo_group = prefs->GetInteger(prefs::kNTPPromoGroup); - EXPECT_GE(promo_group, 0); - EXPECT_LT(promo_group, 100); - - int promo_build_type = prefs->GetInteger(prefs::kNTPPromoBuild); - EXPECT_EQ(promo_build_type & PromoResourceService::DEV_BUILD, - PromoResourceService::DEV_BUILD); - EXPECT_EQ(promo_build_type & PromoResourceService::BETA_BUILD, - PromoResourceService::BETA_BUILD); - EXPECT_EQ(promo_build_type & PromoResourceService::STABLE_BUILD, 0); - - int promo_time_slice = prefs->GetInteger(prefs::kNTPPromoGroupTimeSlice); - EXPECT_EQ(promo_time_slice, 2); - - int promo_group_max = prefs->GetInteger(prefs::kNTPPromoGroupMax); - EXPECT_EQ(promo_group_max, 5); - - double promo_start = prefs->GetDouble(prefs::kNTPPromoStart); - double actual_start = 1264899600; // unix epoch for Jan 31 2010 0100 GMT. - EXPECT_EQ(promo_start, actual_start); - - double modified_start = actual_start + promo_group * 2 * 60 * 60; - EXPECT_EQ(PromoResourceService::GetNotificationStartTime(prefs), - modified_start); - - double promo_end = prefs->GetDouble(prefs::kNTPPromoEnd); - EXPECT_EQ(promo_end, 1327971600); // unix epoch for Jan 31 2012 0100 GMT. - - // Unpack the same json a second time. - web_resource_service_->UnpackNotificationSignal(*(test_json.get())); - - // All the data should be unchanged. - EXPECT_EQ(promo_line, prefs->GetString(prefs::kNTPPromoLine)); - EXPECT_EQ(promo_group, prefs->GetInteger(prefs::kNTPPromoGroup)); - EXPECT_EQ(promo_build_type, prefs->GetInteger(prefs::kNTPPromoBuild)); - EXPECT_EQ(promo_time_slice, - prefs->GetInteger(prefs::kNTPPromoGroupTimeSlice)); - EXPECT_EQ(promo_group_max, prefs->GetInteger(prefs::kNTPPromoGroupMax)); - EXPECT_EQ(promo_start, prefs->GetDouble(prefs::kNTPPromoStart)); - EXPECT_EQ(modified_start, - PromoResourceService::GetNotificationStartTime(prefs)); - EXPECT_EQ(promo_end, prefs->GetDouble(prefs::kNTPPromoEnd)); + NotificationPromoTestDelegate delegate(prefs); + NotificationPromo notification_promo(prefs, &delegate); + + // Set up start and end dates and promo line in a Dictionary as if parsed + // from the service. + delegate.Init(¬ification_promo, + "{ " + " \"topic\": {" + " \"answers\": [" + " {" + " \"name\": \"promo_start\"," + " \"question\": \"12:8:10:20\"," + " \"tooltip\": \"Happy 3rd Birthday!\"," + " \"inproduct\": \"09/15/10 05:00 PDT\"" + " }," + " {" + " \"name\": \"promo_end\"," + " \"inproduct\": \"09/30/10 05:00 PDT\"" + " }" + " ]" + " }" + "}", + 1284552000, // unix epoch for Sep 15 2010 0500 PDT. + 1285848000, // unix epoch for Sep 30 2010 0500 PDT. + 12, 8, 10, 20, + "Happy 3rd Birthday!", false); + + delegate.InitPromoFromJson(true); + + // Second time should not trigger a notification. + delegate.InitPromoFromJson(false); + + delegate.TestInitFromPrefs(); + + // Should fail because out of time bounds. + EXPECT_FALSE(notification_promo.CanShow()); +} + +TEST_F(PromoResourceServiceTest, GetNextQuestionValueTest) { + const std::string question("0:-100:2048:0:2a"); + const int question_vec[] = { 0, -100, 2048, 0 }; + size_t index = 0; + bool err = false; + + for (size_t i = 0; i < arraysize(question_vec); ++i) { + EXPECT_EQ(question_vec[i], + NotificationPromo::GetNextQuestionValue(question, &index, &err)); + EXPECT_FALSE(err); + } + EXPECT_EQ(NotificationPromo::GetNextQuestionValue(question, &index, &err), 0); + EXPECT_TRUE(err); +} + +TEST_F(PromoResourceServiceTest, NewGroupTest) { + for (size_t i = 0; i < 1000; ++i) { + const int group = NotificationPromo::NewGroup(); + EXPECT_GE(group, 0); + EXPECT_LT(group, 100); + } } TEST_F(PromoResourceServiceTest, UnpackWebStoreSignal) { @@ -409,7 +669,7 @@ TEST_F(PromoResourceServiceTest, UnpackWebStoreSignalHttpLogo) { EXPECT_EQ(GURL(""), AppsPromo::GetSourcePromoLogoURL()); } -TEST_F(PromoResourceServiceTest, IsBuildTargeted) { +TEST_F(PromoResourceServiceTest, IsBuildTargetedTest) { // canary const chrome::VersionInfo::Channel canary = chrome::VersionInfo::CHANNEL_CANARY; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 824e1859..1410cee 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -3841,6 +3841,8 @@ 'browser/web_applications/web_app.h', 'browser/web_resource/gpu_blacklist_updater.cc', 'browser/web_resource/gpu_blacklist_updater.h', + 'browser/web_resource/notification_promo.cc', + 'browser/web_resource/notification_promo.h', 'browser/web_resource/promo_resource_service.cc', 'browser/web_resource/promo_resource_service.h', 'browser/web_resource/web_resource_service.cc', diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index 6da5534..b8a6813 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -1278,6 +1278,12 @@ const char kNTPPromoGroupTimeSlice[] = "ntp.promo_group_timeslice"; // Number of groups to roll out this promo to. const char kNTPPromoGroupMax[] = "ntp.promo_group_max"; +// Number of views of this promo. +const char kNTPPromoViews[] = "ntp.promo_views"; + +// Max number of views of this promo. +const char kNTPPromoViewsMax[] = "ntp.promo_views_max"; + // Promo line from server. const char kNTPPromoLine[] = "ntp.promo_line"; diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index e43aee3..d42a727 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -459,6 +459,8 @@ extern const char kNTPPromoClosed[]; extern const char kNTPPromoGroup[]; extern const char kNTPPromoGroupTimeSlice[]; extern const char kNTPPromoGroupMax[]; +extern const char kNTPPromoViews[]; +extern const char kNTPPromoViewsMax[]; extern const char kNTPPromoBuild[]; extern const char kNTPWebStoreEnabled[]; extern const char kNTPWebStorePromoLastId[]; |