1 // Copyright 2014 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/banners/app_banner_settings_helper.h"
10 #include "base/command_line.h"
11 #include "chrome/browser/banners/app_banner_metrics.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/common/chrome_switches.h"
14 #include "components/content_settings/core/browser/host_content_settings_map.h"
15 #include "components/content_settings/core/common/content_settings_pattern.h"
16 #include "content/public/browser/web_contents.h"
17 #include "net/base/escape.h"
22 // Max number of apps (including ServiceWorker based web apps) that a particular
23 // site may show a banner for.
24 const size_t kMaxAppsPerSite
= 3;
26 // Oldest could show banner event we care about, in days.
27 const unsigned int kOldestCouldShowBannerEventInDays
= 14;
29 // Number of times that the banner could have been shown before the banner will
30 // actually be triggered.
31 const unsigned int kCouldShowEventsToTrigger
= 2;
33 // Number of days that showing the banner will prevent it being seen again for.
34 const unsigned int kMinimumDaysBetweenBannerShows
= 60;
36 // Number of days that the banner being blocked will prevent it being seen again
38 const unsigned int kMinimumBannerBlockedToBannerShown
= 90;
40 // Dictionary keys to use for the events.
41 const char* kBannerEventKeys
[] = {
42 "couldShowBannerEvents",
44 "didBlockBannerEvent",
45 "didAddToHomescreenEvent",
48 scoped_ptr
<base::DictionaryValue
> GetOriginDict(
49 HostContentSettingsMap
* settings
,
50 const GURL
& origin_url
) {
52 return scoped_ptr
<base::DictionaryValue
>();
54 scoped_ptr
<base::Value
> value
= settings
->GetWebsiteSetting(
55 origin_url
, origin_url
, CONTENT_SETTINGS_TYPE_APP_BANNER
, std::string(),
58 return make_scoped_ptr(new base::DictionaryValue());
60 if (!value
->IsType(base::Value::TYPE_DICTIONARY
))
61 return make_scoped_ptr(new base::DictionaryValue());
63 return make_scoped_ptr(static_cast<base::DictionaryValue
*>(value
.release()));
66 base::DictionaryValue
* GetAppDict(base::DictionaryValue
* origin_dict
,
67 const std::string
& key_name
) {
68 base::DictionaryValue
* app_dict
= nullptr;
69 if (!origin_dict
->GetDictionaryWithoutPathExpansion(key_name
, &app_dict
)) {
70 // Don't allow more than kMaxAppsPerSite dictionaries.
71 if (origin_dict
->size() < kMaxAppsPerSite
) {
72 app_dict
= new base::DictionaryValue();
73 origin_dict
->SetWithoutPathExpansion(key_name
, make_scoped_ptr(app_dict
));
82 void AppBannerSettingsHelper::ClearHistoryForURLs(
84 const std::set
<GURL
>& origin_urls
) {
85 HostContentSettingsMap
* settings
= profile
->GetHostContentSettingsMap();
86 for (const GURL
& origin_url
: origin_urls
) {
87 ContentSettingsPattern
pattern(ContentSettingsPattern::FromURL(origin_url
));
88 if (!pattern
.IsValid())
91 settings
->SetWebsiteSetting(pattern
, ContentSettingsPattern::Wildcard(),
92 CONTENT_SETTINGS_TYPE_APP_BANNER
, std::string(),
97 void AppBannerSettingsHelper::RecordBannerEvent(
98 content::WebContents
* web_contents
,
99 const GURL
& origin_url
,
100 const std::string
& package_name_or_start_url
,
101 AppBannerEvent event
,
104 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
105 if (profile
->IsOffTheRecord() || package_name_or_start_url
.empty())
108 ContentSettingsPattern
pattern(ContentSettingsPattern::FromURL(origin_url
));
109 if (!pattern
.IsValid())
112 HostContentSettingsMap
* settings
= profile
->GetHostContentSettingsMap();
113 scoped_ptr
<base::DictionaryValue
> origin_dict
=
114 GetOriginDict(settings
, origin_url
);
118 base::DictionaryValue
* app_dict
=
119 GetAppDict(origin_dict
.get(), package_name_or_start_url
);
123 std::string
event_key(kBannerEventKeys
[event
]);
125 if (event
== APP_BANNER_EVENT_COULD_SHOW
) {
126 base::ListValue
* could_show_list
= nullptr;
127 if (!app_dict
->GetList(event_key
, &could_show_list
)) {
128 could_show_list
= new base::ListValue();
129 app_dict
->Set(event_key
, make_scoped_ptr(could_show_list
));
132 // Trim any items that are older than we should care about. For comparisons
133 // the times are converted to local dates.
134 base::Time date
= time
.LocalMidnight();
135 base::ValueVector::iterator it
= could_show_list
->begin();
136 while (it
!= could_show_list
->end()) {
137 if ((*it
)->IsType(base::Value::TYPE_DOUBLE
)) {
138 double internal_date
;
139 (*it
)->GetAsDouble(&internal_date
);
140 base::Time other_date
=
141 base::Time::FromInternalValue(internal_date
).LocalMidnight();
142 // This date has already been added. Don't add the date again, and don't
143 // bother trimming values as it will have been done the first time the
144 // date was added (unless the local date has changed, which we can live
146 if (other_date
== date
)
149 base::TimeDelta delta
= date
- other_date
;
151 base::TimeDelta::FromDays(kOldestCouldShowBannerEventInDays
)) {
157 // Either this date is older than we care about, or it isn't a date, so
159 it
= could_show_list
->Erase(it
, nullptr);
162 // Dates are stored in their raw form (i.e. not local dates) to be resilient
163 // to time zone changes.
164 could_show_list
->AppendDouble(time
.ToInternalValue());
166 app_dict
->SetDouble(event_key
, time
.ToInternalValue());
168 settings
->SetWebsiteSetting(pattern
, ContentSettingsPattern::Wildcard(),
169 CONTENT_SETTINGS_TYPE_APP_BANNER
, std::string(),
170 origin_dict
.release());
173 bool AppBannerSettingsHelper::ShouldShowBanner(
174 content::WebContents
* web_contents
,
175 const GURL
& origin_url
,
176 const std::string
& package_name_or_start_url
,
178 // Don't show if it has been added to the homescreen.
179 base::Time added_time
=
180 GetSingleBannerEvent(web_contents
, origin_url
, package_name_or_start_url
,
181 APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN
);
182 if (!added_time
.is_null()) {
183 banners::TrackDisplayEvent(banners::DISPLAY_EVENT_INSTALLED_PREVIOUSLY
);
187 // Otherwise, ignore all checks if the flag to do so is set.
188 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
189 switches::kBypassAppBannerEngagementChecks
)) {
193 base::Time blocked_time
=
194 GetSingleBannerEvent(web_contents
, origin_url
, package_name_or_start_url
,
195 APP_BANNER_EVENT_DID_BLOCK
);
197 // Null times are in the distant past, so the delta between real times and
198 // null events will always be greater than the limits.
199 if (time
- blocked_time
<
200 base::TimeDelta::FromDays(kMinimumBannerBlockedToBannerShown
)) {
201 banners::TrackDisplayEvent(banners::DISPLAY_EVENT_BLOCKED_PREVIOUSLY
);
205 base::Time shown_time
=
206 GetSingleBannerEvent(web_contents
, origin_url
, package_name_or_start_url
,
207 APP_BANNER_EVENT_DID_SHOW
);
208 if (time
- shown_time
<
209 base::TimeDelta::FromDays(kMinimumDaysBetweenBannerShows
)) {
210 banners::TrackDisplayEvent(banners::DISPLAY_EVENT_IGNORED_PREVIOUSLY
);
214 std::vector
<base::Time
> could_show_events
= GetCouldShowBannerEvents(
215 web_contents
, origin_url
, package_name_or_start_url
);
216 if (could_show_events
.size() < kCouldShowEventsToTrigger
) {
217 banners::TrackDisplayEvent(banners::DISPLAY_EVENT_NOT_VISITED_ENOUGH
);
224 std::vector
<base::Time
> AppBannerSettingsHelper::GetCouldShowBannerEvents(
225 content::WebContents
* web_contents
,
226 const GURL
& origin_url
,
227 const std::string
& package_name_or_start_url
) {
228 std::vector
<base::Time
> result
;
229 if (package_name_or_start_url
.empty())
233 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
234 HostContentSettingsMap
* settings
= profile
->GetHostContentSettingsMap();
235 scoped_ptr
<base::DictionaryValue
> origin_dict
=
236 GetOriginDict(settings
, origin_url
);
241 base::DictionaryValue
* app_dict
=
242 GetAppDict(origin_dict
.get(), package_name_or_start_url
);
246 std::string
event_key(kBannerEventKeys
[APP_BANNER_EVENT_COULD_SHOW
]);
247 base::ListValue
* could_show_list
= nullptr;
248 if (!app_dict
->GetList(event_key
, &could_show_list
))
251 for (auto value
: *could_show_list
) {
252 if (value
->IsType(base::Value::TYPE_DOUBLE
)) {
253 double internal_date
;
254 value
->GetAsDouble(&internal_date
);
255 base::Time date
= base::Time::FromInternalValue(internal_date
);
256 result
.push_back(date
);
263 base::Time
AppBannerSettingsHelper::GetSingleBannerEvent(
264 content::WebContents
* web_contents
,
265 const GURL
& origin_url
,
266 const std::string
& package_name_or_start_url
,
267 AppBannerEvent event
) {
268 DCHECK(event
!= APP_BANNER_EVENT_COULD_SHOW
);
269 DCHECK(event
< APP_BANNER_EVENT_NUM_EVENTS
);
271 if (package_name_or_start_url
.empty())
275 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
276 HostContentSettingsMap
* settings
= profile
->GetHostContentSettingsMap();
277 scoped_ptr
<base::DictionaryValue
> origin_dict
=
278 GetOriginDict(settings
, origin_url
);
283 base::DictionaryValue
* app_dict
=
284 GetAppDict(origin_dict
.get(), package_name_or_start_url
);
288 std::string
event_key(kBannerEventKeys
[event
]);
289 double internal_time
;
290 if (!app_dict
->GetDouble(event_key
, &internal_time
))
293 return base::Time::FromInternalValue(internal_time
);