summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorachuith@chromium.org <achuith@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-01 18:53:42 +0000
committerachuith@chromium.org <achuith@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-01 18:53:42 +0000
commitd1666539b57bf8552e203d355fd09909d36f9732 (patch)
tree4c923efc9c402f6a9111cf10d5864f044da41b10 /chrome
parentfc921169fc1ba829856b8d27af000781de2cb2e2 (diff)
downloadchromium_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.js3
-rw-r--r--chrome/browser/ui/webui/ntp/new_tab_page_handler.cc34
-rw-r--r--chrome/browser/ui/webui/ntp/new_tab_page_handler.h10
-rw-r--r--chrome/browser/web_resource/notification_promo.cc309
-rw-r--r--chrome/browser/web_resource/notification_promo.h117
-rw-r--r--chrome/browser/web_resource/promo_resource_service.cc275
-rw-r--r--chrome/browser/web_resource/promo_resource_service.h94
-rw-r--r--chrome/browser/web_resource/promo_resource_service_unittest.cc392
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/common/pref_names.cc6
-rw-r--r--chrome/common/pref_names.h2
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(&notification_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(&notification_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[];