Add a minor text member to ui::MenuModel.
[chromium-blink-merge.git] / chrome / browser / ui / bookmarks / bookmark_prompt_controller.cc
blob4e76dcde29f3a6b269357d7ce8d3d936e9b29040
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"
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"
31 using content::WebContents;
33 namespace {
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())
68 return false;
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
90 return &kCanaryRange;
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
98 return &kStableRange;
100 default:
101 return NULL;
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;
111 bool IsBookmarked(Browser* browser, const GURL& url) {
112 BookmarkModel* model = BookmarkModelFactory::GetForProfile(
113 browser->profile());
114 return model && model->IsBookmarked(url);
117 bool IsEligiblePageTransitionForBookmarkPrompt(
118 content::PageTransition type) {
119 if (!content::PageTransitionIsMainFrame(type))
120 return false;
122 const content::PageTransition core_type =
123 PageTransitionStripQualifier(type);
125 if (core_type == content::PAGE_TRANSITION_RELOAD)
126 return false;
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))
142 continue;
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;
159 } // namespace
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
171 // prompt.
172 const int BookmarkPromptController::kVisitCountForSessionTrigger = 3;
174 BookmarkPromptController::BookmarkPromptController()
175 : browser_(NULL),
176 web_contents_(NULL) {
177 DCHECK(browser_defaults::bookmarks_enabled);
178 BrowserList::AddObserver(this);
181 BookmarkPromptController::~BookmarkPromptController() {
182 BrowserList::RemoveObserver(this);
183 SetBrowser(NULL);
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);
195 // static
196 void BookmarkPromptController::ClosingBookmarkPrompt() {
197 BookmarkPromptController* controller =
198 g_browser_process->bookmark_prompt_controller();
199 if (controller)
200 controller->ClosingBookmarkPromptInternal();
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();
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;
223 const ExperimentDateRange* date_range = GetExperimentDateRange();
224 if (!date_range)
225 return false;
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);
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) {
260 trial->Disable();
261 return false;
263 return trial->group_name() == kBookmarkPromptExperimentGroup;
266 void BookmarkPromptController::ActiveTabChanged(WebContents* old_contents,
267 WebContents* new_contents,
268 int index,
269 int reason) {
270 SetWebContents(new_contents);
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_);
282 void BookmarkPromptController::ClosingBookmarkPromptInternal() {
283 UMA_HISTOGRAM_TIMES("BookmarkPrompt.DisplayDuration",
284 base::Time::Now() - last_prompted_time_);
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;
296 const GURL url = web_contents_->GetURL();
297 if (!HistoryService::CanAddURL(url) || IsBookmarked(browser_, url))
298 return;
300 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
301 browser_->profile(),
302 Profile::IMPLICIT_ACCESS);
303 if (!history_service)
304 return;
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)
315 SetBrowser(NULL);
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);
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;
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;
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))
350 return;
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;
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)
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);
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_));
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_));