1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/bookmarks/bookmark_prompt_controller.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h"
10 #include "base/prefs/pref_service.h"
11 #include "chrome/browser/bookmarks/bookmark_model.h"
12 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
13 #include "chrome/browser/bookmarks/bookmark_prompt_prefs.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/defaults.h"
16 #include "chrome/browser/history/history_service.h"
17 #include "chrome/browser/history/history_service_factory.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_finder.h"
20 #include "chrome/browser/ui/browser_list.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/common/chrome_version_info.h"
24 #include "chrome/common/metrics/variations/variation_ids.h"
25 #include "chrome/common/pref_names.h"
26 #include "components/variations/variations_associated_data.h"
27 #include "content/public/browser/notification_service.h"
28 #include "content/public/browser/notification_types.h"
29 #include "content/public/browser/web_contents.h"
31 using content::WebContents
;
35 const char kBookmarkPromptTrialName
[] = "BookmarkPrompt";
36 const char kBookmarkPromptDefaultGroup
[] = "Disabled";
37 const char kBookmarkPromptControlGroup
[] = "Control";
38 const char kBookmarkPromptExperimentGroup
[] = "Experiment";
40 // This enum is used for the BookmarkPrompt.DisabledReason histogram.
41 enum PromptDisabledReason
{
42 PROMPT_DISABLED_REASON_BY_IMPRESSION_COUNT
,
43 PROMPT_DISABLED_REASON_BY_MANUAL
,
45 PROMPT_DISABLED_REASON_LIMIT
, // Keep this last.
48 // This enum represents reason why we display bookmark prompt and for the
49 // BookmarkPrompt.DisplayReason histogram.
50 enum PromptDisplayReason
{
51 PROMPT_DISPLAY_REASON_NOT_DISPLAY
, // We don't display the prompt.
52 PROMPT_DISPLAY_REASON_PERMANENT
,
53 PROMPT_DISPLAY_REASON_SESSION
,
55 PROMPT_DISPLAY_REASON_LIMIT
, // Keep this last.
58 // We enable bookmark prompt experiment for users who have profile created
59 // before |install_date| until |expiration_date|.
60 struct ExperimentDateRange
{
61 base::Time::Exploded install_date
;
62 base::Time::Exploded expiration_date
;
65 bool CanShowBookmarkPrompt(Browser
* browser
) {
66 BookmarkPromptPrefs
prefs(browser
->profile()->GetPrefs());
67 if (!prefs
.IsBookmarkPromptEnabled())
69 return prefs
.GetPromptImpressionCount() <
70 BookmarkPromptController::kMaxPromptImpressionCount
;
73 const ExperimentDateRange
* GetExperimentDateRange() {
74 switch (chrome::VersionInfo::GetChannel()) {
75 case chrome::VersionInfo::CHANNEL_BETA
:
76 case chrome::VersionInfo::CHANNEL_DEV
: {
77 // Experiment date range for M26 Beta/Dev
78 static const ExperimentDateRange kBetaAndDevRange
= {
79 { 2013, 3, 0, 1, 0, 0, 0, 0 }, // Mar 1, 2013
80 { 2013, 4, 0, 1, 0, 0, 0, 0 }, // Apr 1, 2013
82 return &kBetaAndDevRange
;
84 case chrome::VersionInfo::CHANNEL_CANARY
: {
85 // Experiment date range for M26 Canary.
86 static const ExperimentDateRange kCanaryRange
= {
87 { 2013, 1, 0, 17, 0, 0, 0, 0 }, // Jan 17, 2013
88 { 2013, 2, 0, 18, 0, 0, 0, 0 }, // Feb 17, 2013
92 case chrome::VersionInfo::CHANNEL_STABLE
: {
93 // Experiment date range for M26 Stable.
94 static const ExperimentDateRange kStableRange
= {
95 { 2013, 4, 0, 5, 0, 0, 0, 0 }, // Apr 5, 2013
96 { 2013, 5, 0, 5, 0, 0, 0, 0 }, // May 5, 2013
105 bool IsActiveWebContents(Browser
* browser
, WebContents
* web_contents
) {
106 if (!browser
->window()->IsActive())
108 return browser
->tab_strip_model()->GetActiveWebContents() == web_contents
;
111 bool IsBookmarked(Browser
* browser
, const GURL
& url
) {
112 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(
114 return model
&& model
->IsBookmarked(url
);
117 bool IsEligiblePageTransitionForBookmarkPrompt(
118 content::PageTransition type
) {
119 if (!content::PageTransitionIsMainFrame(type
))
122 const content::PageTransition core_type
=
123 PageTransitionStripQualifier(type
);
125 if (core_type
== content::PAGE_TRANSITION_RELOAD
)
128 const int32 qualifier
= content::PageTransitionGetQualifier(type
);
129 return !(qualifier
& content::PAGE_TRANSITION_FORWARD_BACK
);
132 // CheckPromptTriger returns prompt display reason based on |visits|.
133 PromptDisplayReason
CheckPromptTriger(const history::VisitVector
& visits
) {
134 const base::Time now
= base::Time::Now();
135 // We assume current visit is already in history database. Although, this
136 // assumption may be false. We'll display prompt next time.
137 int visit_permanent_count
= 0;
138 int visit_session_count
= 0;
139 for (history::VisitVector::const_iterator it
= visits
.begin();
140 it
!= visits
.end(); ++it
) {
141 if (!IsEligiblePageTransitionForBookmarkPrompt(it
->transition
))
143 ++visit_permanent_count
;
144 if ((now
- it
->visit_time
) <= base::TimeDelta::FromDays(1))
145 ++visit_session_count
;
148 if (visit_permanent_count
==
149 BookmarkPromptController::kVisitCountForPermanentTrigger
)
150 return PROMPT_DISPLAY_REASON_PERMANENT
;
152 if (visit_session_count
==
153 BookmarkPromptController::kVisitCountForSessionTrigger
)
154 return PROMPT_DISPLAY_REASON_SESSION
;
156 return PROMPT_DISPLAY_REASON_NOT_DISPLAY
;
161 // BookmarkPromptController
163 // When impression count is greater than |kMaxPromptImpressionCount|, we
164 // don't display bookmark prompt anymore.
165 const int BookmarkPromptController::kMaxPromptImpressionCount
= 5;
167 // When user visited the URL 10 times, we show the bookmark prompt.
168 const int BookmarkPromptController::kVisitCountForPermanentTrigger
= 10;
170 // When user visited the URL 3 times last 24 hours, we show the bookmark
172 const int BookmarkPromptController::kVisitCountForSessionTrigger
= 3;
174 BookmarkPromptController::BookmarkPromptController()
176 web_contents_(NULL
) {
177 DCHECK(browser_defaults::bookmarks_enabled
);
178 BrowserList::AddObserver(this);
181 BookmarkPromptController::~BookmarkPromptController() {
182 BrowserList::RemoveObserver(this);
187 void BookmarkPromptController::AddedBookmark(Browser
* browser
,
189 BookmarkPromptController
* controller
=
190 g_browser_process
->bookmark_prompt_controller();
192 controller
->AddedBookmarkInternal(browser
, url
);
196 void BookmarkPromptController::ClosingBookmarkPrompt() {
197 BookmarkPromptController
* controller
=
198 g_browser_process
->bookmark_prompt_controller();
200 controller
->ClosingBookmarkPromptInternal();
204 void BookmarkPromptController::DisableBookmarkPrompt(
205 PrefService
* prefs
) {
206 UMA_HISTOGRAM_ENUMERATION("BookmarkPrompt.DisabledReason",
207 PROMPT_DISABLED_REASON_BY_MANUAL
,
208 PROMPT_DISABLED_REASON_LIMIT
);
209 BookmarkPromptPrefs
prompt_prefs(prefs
);
210 prompt_prefs
.DisableBookmarkPrompt();
213 // Enable bookmark prompt controller feature for 1% of new users for one month
214 // on canary. We'll change the date for stable channel once release date fixed.
216 bool BookmarkPromptController::IsEnabled() {
217 // If manually create field trial available, we use it.
218 const std::string manual_group_name
= base::FieldTrialList::FindFullName(
220 if (!manual_group_name
.empty())
221 return manual_group_name
== kBookmarkPromptExperimentGroup
;
223 const ExperimentDateRange
* date_range
= GetExperimentDateRange();
227 scoped_refptr
<base::FieldTrial
> trial(
228 base::FieldTrialList::FactoryGetFieldTrial(
229 kBookmarkPromptTrialName
, 100, kBookmarkPromptDefaultGroup
,
230 date_range
->expiration_date
.year
,
231 date_range
->expiration_date
.month
,
232 date_range
->expiration_date
.day_of_month
,
233 base::FieldTrial::ONE_TIME_RANDOMIZED
,
235 trial
->AppendGroup(kBookmarkPromptControlGroup
, 10);
236 trial
->AppendGroup(kBookmarkPromptExperimentGroup
, 10);
238 chrome_variations::AssociateGoogleVariationID(
239 chrome_variations::GOOGLE_UPDATE_SERVICE
,
240 kBookmarkPromptTrialName
, kBookmarkPromptDefaultGroup
,
241 chrome_variations::BOOKMARK_PROMPT_TRIAL_DEFAULT
);
242 chrome_variations::AssociateGoogleVariationID(
243 chrome_variations::GOOGLE_UPDATE_SERVICE
,
244 kBookmarkPromptTrialName
, kBookmarkPromptControlGroup
,
245 chrome_variations::BOOKMARK_PROMPT_TRIAL_CONTROL
);
246 chrome_variations::AssociateGoogleVariationID(
247 chrome_variations::GOOGLE_UPDATE_SERVICE
,
248 kBookmarkPromptTrialName
, kBookmarkPromptExperimentGroup
,
249 chrome_variations::BOOKMARK_PROMPT_TRIAL_EXPERIMENT
);
251 const base::Time start_date
= base::Time::FromLocalExploded(
252 date_range
->install_date
);
253 const int64 install_time
=
254 g_browser_process
->local_state()->GetInt64(prefs::kInstallDate
);
255 // This must be called after the pref is initialized.
256 DCHECK(install_time
);
257 const base::Time install_date
= base::Time::FromTimeT(install_time
);
259 if (install_date
< start_date
) {
263 return trial
->group_name() == kBookmarkPromptExperimentGroup
;
266 void BookmarkPromptController::ActiveTabChanged(WebContents
* old_contents
,
267 WebContents
* new_contents
,
270 SetWebContents(new_contents
);
273 void BookmarkPromptController::AddedBookmarkInternal(Browser
* browser
,
275 if (browser
== browser_
&& url
== last_prompted_url_
) {
276 last_prompted_url_
= GURL::EmptyGURL();
277 UMA_HISTOGRAM_TIMES("BookmarkPrompt.AddedBookmark",
278 base::Time::Now() - last_prompted_time_
);
282 void BookmarkPromptController::ClosingBookmarkPromptInternal() {
283 UMA_HISTOGRAM_TIMES("BookmarkPrompt.DisplayDuration",
284 base::Time::Now() - last_prompted_time_
);
287 void BookmarkPromptController::Observe(
289 const content::NotificationSource
&,
290 const content::NotificationDetails
&) {
291 DCHECK_EQ(type
, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
);
292 query_url_consumer_
.CancelAllRequests();
293 if (!CanShowBookmarkPrompt(browser_
))
296 const GURL url
= web_contents_
->GetURL();
297 if (!HistoryService::CanAddURL(url
) || IsBookmarked(browser_
, url
))
300 HistoryService
* history_service
= HistoryServiceFactory::GetForProfile(
302 Profile::IMPLICIT_ACCESS
);
303 if (!history_service
)
306 query_url_start_time_
= base::Time::Now();
307 history_service
->QueryURL(
308 url
, true, &query_url_consumer_
,
309 base::Bind(&BookmarkPromptController::OnDidQueryURL
,
310 base::Unretained(this)));
313 void BookmarkPromptController::OnBrowserRemoved(Browser
* browser
) {
314 if (browser_
== browser
)
318 void BookmarkPromptController::OnBrowserSetLastActive(Browser
* browser
) {
319 if (browser
&& browser
->type() == Browser::TYPE_TABBED
&&
320 !browser
->profile()->IsOffTheRecord() &&
321 browser
->CanSupportWindowFeature(Browser::FEATURE_LOCATIONBAR
) &&
322 CanShowBookmarkPrompt(browser
))
328 void BookmarkPromptController::OnDidQueryURL(
329 CancelableRequestProvider::Handle handle
,
331 const history::URLRow
* url_row
,
332 history::VisitVector
* visits
) {
336 const GURL url
= web_contents_
->GetURL();
337 if (url_row
->url() != url
) {
338 // The URL of web_contents_ is changed during QueryURL call. This is an
339 // edge case but can be happened.
343 UMA_HISTOGRAM_TIMES("BookmarkPrompt.QueryURLDuration",
344 base::Time::Now() - query_url_start_time_
);
346 if (!browser_
->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR
) ||
347 !CanShowBookmarkPrompt(browser_
) ||
348 !IsActiveWebContents(browser_
, web_contents_
) ||
349 IsBookmarked(browser_
, url
))
352 PromptDisplayReason reason
= CheckPromptTriger(*visits
);
353 UMA_HISTOGRAM_ENUMERATION("BookmarkPrompt.DisplayReason",
355 PROMPT_DISPLAY_REASON_LIMIT
);
356 if (reason
== PROMPT_DISPLAY_REASON_NOT_DISPLAY
)
359 BookmarkPromptPrefs
prefs(browser_
->profile()->GetPrefs());
360 prefs
.IncrementPromptImpressionCount();
361 if (prefs
.GetPromptImpressionCount() == kMaxPromptImpressionCount
) {
362 UMA_HISTOGRAM_ENUMERATION("BookmarkPrompt.DisabledReason",
363 PROMPT_DISABLED_REASON_BY_IMPRESSION_COUNT
,
364 PROMPT_DISABLED_REASON_LIMIT
);
365 prefs
.DisableBookmarkPrompt();
367 last_prompted_time_
= base::Time::Now();
368 last_prompted_url_
= web_contents_
->GetURL();
369 browser_
->window()->ShowBookmarkPrompt();
372 void BookmarkPromptController::SetBrowser(Browser
* browser
) {
373 if (browser_
== browser
)
376 browser_
->tab_strip_model()->RemoveObserver(this);
379 browser_
->tab_strip_model()->AddObserver(this);
380 SetWebContents(browser_
? browser_
->tab_strip_model()->GetActiveWebContents()
384 void BookmarkPromptController::SetWebContents(WebContents
* web_contents
) {
386 last_prompted_url_
= GURL::EmptyGURL();
387 query_url_consumer_
.CancelAllRequests();
389 this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
,
390 content::Source
<WebContents
>(web_contents_
));
392 web_contents_
= web_contents
;
394 registrar_
.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
,
395 content::Source
<WebContents
>(web_contents_
));