Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / bookmarks / bookmark_prompt_controller.cc
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.
4
5 #include "chrome/browser/ui/bookmarks/bookmark_prompt_controller.h"
6
7 #include "base/bind.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"
30
31 using content::WebContents;
32
33 namespace {
34
35 const char kBookmarkPromptTrialName[] = "BookmarkPrompt";
36 const char kBookmarkPromptDefaultGroup[] = "Disabled";
37 const char kBookmarkPromptControlGroup[] = "Control";
38 const char kBookmarkPromptExperimentGroup[] = "Experiment";
39
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,
44
45   PROMPT_DISABLED_REASON_LIMIT, // Keep this last.
46 };
47
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,
54
55   PROMPT_DISPLAY_REASON_LIMIT, // Keep this last.
56 };
57
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;
63 };
64
65 bool CanShowBookmarkPrompt(Browser* browser) {
66   BookmarkPromptPrefs prefs(browser->profile()->GetPrefs());
67   if (!prefs.IsBookmarkPromptEnabled())
68     return false;
69   return prefs.GetPromptImpressionCount() <
70          BookmarkPromptController::kMaxPromptImpressionCount;
71 }
72
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
81       };
82       return &kBetaAndDevRange;
83     }
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
89       };
90       return &kCanaryRange;
91     }
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
97       };
98       return &kStableRange;
99     }
100     default:
101       return NULL;
102   }
103 }
104
105 bool IsActiveWebContents(Browser* browser, WebContents* web_contents) {
106   if (!browser->window()->IsActive())
107     return false;
108   return browser->tab_strip_model()->GetActiveWebContents() == web_contents;
109 }
110
111 bool IsBookmarked(Browser* browser, const GURL& url) {
112   BookmarkModel* model = BookmarkModelFactory::GetForProfile(
113       browser->profile());
114   return model && model->IsBookmarked(url);
115 }
116
117 bool IsEligiblePageTransitionForBookmarkPrompt(
118     content::PageTransition type) {
119   if (!content::PageTransitionIsMainFrame(type))
120     return false;
121
122   const content::PageTransition core_type =
123       PageTransitionStripQualifier(type);
124
125   if (core_type == content::PAGE_TRANSITION_RELOAD)
126     return false;
127
128   const int32 qualifier = content::PageTransitionGetQualifier(type);
129   return !(qualifier & content::PAGE_TRANSITION_FORWARD_BACK);
130 }
131
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))
142       continue;
143     ++visit_permanent_count;
144     if ((now - it->visit_time) <= base::TimeDelta::FromDays(1))
145       ++visit_session_count;
146   }
147
148   if (visit_permanent_count ==
149       BookmarkPromptController::kVisitCountForPermanentTrigger)
150     return PROMPT_DISPLAY_REASON_PERMANENT;
151
152   if (visit_session_count ==
153       BookmarkPromptController::kVisitCountForSessionTrigger)
154     return PROMPT_DISPLAY_REASON_SESSION;
155
156   return PROMPT_DISPLAY_REASON_NOT_DISPLAY;
157 }
158
159 }  // namespace
160
161 // BookmarkPromptController
162
163 // When impression count is greater than |kMaxPromptImpressionCount|, we
164 // don't display bookmark prompt anymore.
165 const int BookmarkPromptController::kMaxPromptImpressionCount = 5;
166
167 // When user visited the URL 10 times, we show the bookmark prompt.
168 const int BookmarkPromptController::kVisitCountForPermanentTrigger = 10;
169
170 // When user visited the URL 3 times last 24 hours, we show the bookmark
171 // prompt.
172 const int BookmarkPromptController::kVisitCountForSessionTrigger = 3;
173
174 BookmarkPromptController::BookmarkPromptController()
175     : browser_(NULL),
176       web_contents_(NULL) {
177   DCHECK(browser_defaults::bookmarks_enabled);
178   BrowserList::AddObserver(this);
179 }
180
181 BookmarkPromptController::~BookmarkPromptController() {
182   BrowserList::RemoveObserver(this);
183   SetBrowser(NULL);
184 }
185
186 // static
187 void BookmarkPromptController::AddedBookmark(Browser* browser,
188                                              const GURL& url) {
189   BookmarkPromptController* controller =
190       g_browser_process->bookmark_prompt_controller();
191   if (controller)
192     controller->AddedBookmarkInternal(browser, url);
193 }
194
195 // static
196 void BookmarkPromptController::ClosingBookmarkPrompt() {
197   BookmarkPromptController* controller =
198       g_browser_process->bookmark_prompt_controller();
199   if (controller)
200     controller->ClosingBookmarkPromptInternal();
201 }
202
203 // static
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();
211 }
212
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.
215 // static
216 bool BookmarkPromptController::IsEnabled() {
217   // If manually create field trial available, we use it.
218   const std::string manual_group_name = base::FieldTrialList::FindFullName(
219       "BookmarkPrompt");
220   if (!manual_group_name.empty())
221     return manual_group_name == kBookmarkPromptExperimentGroup;
222
223   const ExperimentDateRange* date_range = GetExperimentDateRange();
224   if (!date_range)
225     return false;
226
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,
234           NULL));
235   trial->AppendGroup(kBookmarkPromptControlGroup, 10);
236   trial->AppendGroup(kBookmarkPromptExperimentGroup, 10);
237
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);
250
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);
258
259   if (install_date < start_date) {
260     trial->Disable();
261     return false;
262   }
263   return trial->group_name() == kBookmarkPromptExperimentGroup;
264 }
265
266 void BookmarkPromptController::ActiveTabChanged(WebContents* old_contents,
267                                                 WebContents* new_contents,
268                                                 int index,
269                                                 int reason) {
270   SetWebContents(new_contents);
271 }
272
273 void BookmarkPromptController::AddedBookmarkInternal(Browser* browser,
274                                                      const GURL& url) {
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_);
279   }
280 }
281
282 void BookmarkPromptController::ClosingBookmarkPromptInternal() {
283   UMA_HISTOGRAM_TIMES("BookmarkPrompt.DisplayDuration",
284                       base::Time::Now() - last_prompted_time_);
285 }
286
287 void BookmarkPromptController::Observe(
288     int type,
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_))
294     return;
295
296   const GURL url = web_contents_->GetURL();
297   if (!HistoryService::CanAddURL(url) || IsBookmarked(browser_, url))
298     return;
299
300   HistoryService* history_service = HistoryServiceFactory::GetForProfile(
301       browser_->profile(),
302       Profile::IMPLICIT_ACCESS);
303   if (!history_service)
304     return;
305
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)));
311 }
312
313 void BookmarkPromptController::OnBrowserRemoved(Browser* browser) {
314   if (browser_ == browser)
315     SetBrowser(NULL);
316 }
317
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))
323     SetBrowser(browser);
324   else
325     SetBrowser(NULL);
326 }
327
328 void BookmarkPromptController::OnDidQueryURL(
329     CancelableRequestProvider::Handle handle,
330     bool success,
331     const history::URLRow* url_row,
332     history::VisitVector* visits) {
333   if (!success)
334     return;
335
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.
340     return;
341   }
342
343   UMA_HISTOGRAM_TIMES("BookmarkPrompt.QueryURLDuration",
344                       base::Time::Now() - query_url_start_time_);
345
346   if (!browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR) ||
347       !CanShowBookmarkPrompt(browser_) ||
348       !IsActiveWebContents(browser_, web_contents_) ||
349       IsBookmarked(browser_, url))
350     return;
351
352   PromptDisplayReason reason = CheckPromptTriger(*visits);
353   UMA_HISTOGRAM_ENUMERATION("BookmarkPrompt.DisplayReason",
354                             reason,
355                             PROMPT_DISPLAY_REASON_LIMIT);
356   if (reason == PROMPT_DISPLAY_REASON_NOT_DISPLAY)
357     return;
358
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();
366   }
367   last_prompted_time_ = base::Time::Now();
368   last_prompted_url_ = web_contents_->GetURL();
369   browser_->window()->ShowBookmarkPrompt();
370 }
371
372 void BookmarkPromptController::SetBrowser(Browser* browser) {
373   if (browser_ == browser)
374     return;
375   if (browser_)
376     browser_->tab_strip_model()->RemoveObserver(this);
377   browser_ = browser;
378   if (browser_)
379     browser_->tab_strip_model()->AddObserver(this);
380   SetWebContents(browser_ ? browser_->tab_strip_model()->GetActiveWebContents()
381                           : NULL);
382 }
383
384 void BookmarkPromptController::SetWebContents(WebContents* web_contents) {
385   if (web_contents_) {
386     last_prompted_url_ = GURL::EmptyGURL();
387     query_url_consumer_.CancelAllRequests();
388     registrar_.Remove(
389         this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
390         content::Source<WebContents>(web_contents_));
391   }
392   web_contents_ = web_contents;
393   if (web_contents_) {
394     registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
395                    content::Source<WebContents>(web_contents_));
396   }
397 }