OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/banners/app_banner_settings_helper.h" | 5 #include "chrome/browser/banners/app_banner_settings_helper.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <string> | 8 #include <string> |
9 | 9 |
10 #include "chrome/browser/profiles/profile.h" | 10 #include "chrome/browser/profiles/profile.h" |
11 #include "components/content_settings/core/browser/host_content_settings_map.h" | 11 #include "components/content_settings/core/browser/host_content_settings_map.h" |
12 #include "components/content_settings/core/common/content_settings_pattern.h" | 12 #include "components/content_settings/core/common/content_settings_pattern.h" |
13 #include "content/public/browser/web_contents.h" | 13 #include "content/public/browser/web_contents.h" |
14 #include "net/base/escape.h" | 14 #include "net/base/escape.h" |
15 #include "url/gurl.h" | 15 #include "url/gurl.h" |
16 | 16 |
17 namespace { | 17 namespace { |
18 | 18 |
19 // Max number of apps (including ServiceWorker based web apps) that a particular | 19 // Max number of apps (including ServiceWorker based web apps) that a particular |
20 // site may show a banner for. | 20 // site may show a banner for. |
21 const size_t kMaxAppsPerSite = 3; | 21 const size_t kMaxAppsPerSite = 3; |
22 | 22 |
23 // Oldest could show banner event we care about, in days. | 23 // Oldest could show banner event we care about, in days. |
24 const unsigned int kOldestCouldShowBannerEventInDays = 14; | 24 const unsigned int kOldestCouldShowBannerEventInDays = 14; |
25 | 25 |
26 // Dictionary key to use for the 'could show banner' events. | 26 // Number of times that the banner could have been shown before the banner will |
27 const char kCouldShowBannerEventsKey[] = "couldShowBannerEvents"; | 27 // actually be triggered. |
| 28 const unsigned int kCouldShowEventsToTrigger = 2; |
| 29 |
| 30 // Number of days that showing the banner will prevent it being seen again for. |
| 31 const unsigned int kMinimumDaysBetweenBannerShows = 60; |
| 32 |
| 33 // Number of days that the banner being blocked will prevent it being seen again |
| 34 // for. |
| 35 const unsigned int kMinimumBannerBlockedToBannerShown = 90; |
| 36 |
| 37 // Dictionary keys to use for the events. |
| 38 const char* kBannerEventKeys[] = { |
| 39 "couldShowBannerEvents", |
| 40 "didShowBannerEvent", |
| 41 "didBlockBannerEvent", |
| 42 "didAddToHomescreenEvent", |
| 43 }; |
28 | 44 |
29 // Dictionary key to use whether the banner has been blocked. | 45 // Dictionary key to use whether the banner has been blocked. |
30 const char kHasBlockedKey[] = "hasBlocked"; | 46 const char kHasBlockedKey[] = "hasBlocked"; |
31 | 47 |
32 base::Time DateFromTime(base::Time time) { | |
33 base::Time::Exploded exploded; | |
34 time.LocalExplode(&exploded); | |
35 exploded.hour = 0; | |
36 exploded.minute = 0; | |
37 exploded.second = 0; | |
38 return base::Time::FromLocalExploded(exploded); | |
39 } | |
40 | |
41 scoped_ptr<base::DictionaryValue> GetOriginDict( | 48 scoped_ptr<base::DictionaryValue> GetOriginDict( |
42 HostContentSettingsMap* settings, | 49 HostContentSettingsMap* settings, |
43 const GURL& origin_url) { | 50 const GURL& origin_url) { |
44 if (!settings) | 51 if (!settings) |
45 return scoped_ptr<base::DictionaryValue>(); | 52 return scoped_ptr<base::DictionaryValue>(); |
46 | 53 |
47 scoped_ptr<base::Value> value = settings->GetWebsiteSetting( | 54 scoped_ptr<base::Value> value = settings->GetWebsiteSetting( |
48 origin_url, origin_url, CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), | 55 origin_url, origin_url, CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), |
49 NULL); | 56 NULL); |
50 if (!value.get()) | 57 if (!value.get()) |
(...skipping 14 matching lines...) Expand all Loading... |
65 app_dict = new base::DictionaryValue(); | 72 app_dict = new base::DictionaryValue(); |
66 origin_dict->SetWithoutPathExpansion(key_name, make_scoped_ptr(app_dict)); | 73 origin_dict->SetWithoutPathExpansion(key_name, make_scoped_ptr(app_dict)); |
67 } | 74 } |
68 } | 75 } |
69 | 76 |
70 return app_dict; | 77 return app_dict; |
71 } | 78 } |
72 | 79 |
73 } // namespace | 80 } // namespace |
74 | 81 |
75 void AppBannerSettingsHelper::RecordCouldShowBannerEvent( | 82 void AppBannerSettingsHelper::RecordBannerEvent( |
76 content::WebContents* web_contents, | 83 content::WebContents* web_contents, |
77 const GURL& origin_url, | 84 const GURL& origin_url, |
78 const std::string& package_name_or_start_url, | 85 const std::string& package_name_or_start_url, |
| 86 AppBannerEvent event, |
79 base::Time time) { | 87 base::Time time) { |
80 Profile* profile = | 88 Profile* profile = |
81 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 89 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
82 if (profile->IsOffTheRecord() || web_contents->GetURL() != origin_url || | 90 if (profile->IsOffTheRecord() || web_contents->GetURL() != origin_url || |
83 package_name_or_start_url.empty()) { | 91 package_name_or_start_url.empty()) { |
84 return; | 92 return; |
85 } | 93 } |
86 | 94 |
87 ContentSettingsPattern pattern(ContentSettingsPattern::FromURL(origin_url)); | 95 ContentSettingsPattern pattern(ContentSettingsPattern::FromURL(origin_url)); |
88 if (!pattern.IsValid()) | 96 if (!pattern.IsValid()) |
89 return; | 97 return; |
90 | 98 |
91 HostContentSettingsMap* settings = profile->GetHostContentSettingsMap(); | 99 HostContentSettingsMap* settings = profile->GetHostContentSettingsMap(); |
92 scoped_ptr<base::DictionaryValue> origin_dict = | 100 scoped_ptr<base::DictionaryValue> origin_dict = |
93 GetOriginDict(settings, origin_url); | 101 GetOriginDict(settings, origin_url); |
94 if (!origin_dict) | 102 if (!origin_dict) |
95 return; | 103 return; |
96 | 104 |
97 base::DictionaryValue* app_dict = | 105 base::DictionaryValue* app_dict = |
98 GetAppDict(origin_dict.get(), package_name_or_start_url); | 106 GetAppDict(origin_dict.get(), package_name_or_start_url); |
99 if (!app_dict) | 107 if (!app_dict) |
100 return; | 108 return; |
101 | 109 |
102 base::ListValue* could_show_list = nullptr; | 110 std::string event_key(kBannerEventKeys[event]); |
103 if (!app_dict->GetList(kCouldShowBannerEventsKey, &could_show_list)) { | |
104 could_show_list = new base::ListValue(); | |
105 app_dict->Set(kCouldShowBannerEventsKey, make_scoped_ptr(could_show_list)); | |
106 } | |
107 | 111 |
108 // Trim any items that are older than we should care about. For comparisons | 112 if (event == APP_BANNER_EVENT_COULD_SHOW) { |
109 // the times are converted to local dates. | 113 base::ListValue* could_show_list = nullptr; |
110 base::Time date = DateFromTime(time); | 114 if (!app_dict->GetList(event_key, &could_show_list)) { |
111 base::ValueVector::iterator it = could_show_list->begin(); | 115 could_show_list = new base::ListValue(); |
112 while (it != could_show_list->end()) { | 116 app_dict->Set(event_key, make_scoped_ptr(could_show_list)); |
113 if ((*it)->IsType(base::Value::TYPE_DOUBLE)) { | |
114 double internal_date; | |
115 (*it)->GetAsDouble(&internal_date); | |
116 base::Time other_date = | |
117 DateFromTime(base::Time::FromInternalValue(internal_date)); | |
118 // This date has already been added. Don't add the date again, and don't | |
119 // bother trimming values as it will have been done the first time the | |
120 // date was added (unless the local date has changed, which we can live | |
121 // with). | |
122 if (other_date == date) | |
123 return; | |
124 | |
125 base::TimeDelta delta = date - other_date; | |
126 if (delta < | |
127 base::TimeDelta::FromDays(kOldestCouldShowBannerEventInDays)) { | |
128 ++it; | |
129 continue; | |
130 } | |
131 } | 117 } |
132 | 118 |
133 // Either this date is older than we care about, or it isn't a date, so | 119 // Trim any items that are older than we should care about. For comparisons |
134 // remove it; | 120 // the times are converted to local dates. |
135 it = could_show_list->Erase(it, nullptr); | 121 base::Time date = time.LocalMidnight(); |
| 122 base::ValueVector::iterator it = could_show_list->begin(); |
| 123 while (it != could_show_list->end()) { |
| 124 if ((*it)->IsType(base::Value::TYPE_DOUBLE)) { |
| 125 double internal_date; |
| 126 (*it)->GetAsDouble(&internal_date); |
| 127 base::Time other_date = |
| 128 base::Time::FromInternalValue(internal_date).LocalMidnight(); |
| 129 // This date has already been added. Don't add the date again, and don't |
| 130 // bother trimming values as it will have been done the first time the |
| 131 // date was added (unless the local date has changed, which we can live |
| 132 // with). |
| 133 if (other_date == date) |
| 134 return; |
| 135 |
| 136 base::TimeDelta delta = date - other_date; |
| 137 if (delta < |
| 138 base::TimeDelta::FromDays(kOldestCouldShowBannerEventInDays)) { |
| 139 ++it; |
| 140 continue; |
| 141 } |
| 142 } |
| 143 |
| 144 // Either this date is older than we care about, or it isn't a date, so |
| 145 // remove it; |
| 146 it = could_show_list->Erase(it, nullptr); |
| 147 } |
| 148 |
| 149 // Dates are stored in their raw form (i.e. not local dates) to be resilient |
| 150 // to time zone changes. |
| 151 could_show_list->AppendDouble(time.ToInternalValue()); |
| 152 } else { |
| 153 app_dict->SetDouble(event_key, time.ToInternalValue()); |
136 } | 154 } |
137 | |
138 // Dates are stored in their raw form (i.e. not local dates) to be resilient | |
139 // to time zone changes. | |
140 could_show_list->AppendDouble(time.ToInternalValue()); | |
141 settings->SetWebsiteSetting(pattern, ContentSettingsPattern::Wildcard(), | 155 settings->SetWebsiteSetting(pattern, ContentSettingsPattern::Wildcard(), |
142 CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), | 156 CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), |
143 origin_dict.release()); | 157 origin_dict.release()); |
144 } | 158 } |
145 | 159 |
| 160 bool AppBannerSettingsHelper::ShouldShowBanner( |
| 161 content::WebContents* web_contents, |
| 162 const GURL& origin_url, |
| 163 const std::string& package_name_or_start_url, |
| 164 base::Time time) { |
| 165 // Don't show if it has been added to the homescreen. |
| 166 base::Time added_time = |
| 167 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, |
| 168 APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN); |
| 169 if (!added_time.is_null()) |
| 170 return false; |
| 171 |
| 172 base::Time blocked_time = |
| 173 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, |
| 174 APP_BANNER_EVENT_DID_BLOCK); |
| 175 |
| 176 // Null times are in the distant past, so the delta between real times and |
| 177 // null events will always be greater than the limits. |
| 178 if (time - blocked_time < |
| 179 base::TimeDelta::FromDays(kMinimumBannerBlockedToBannerShown)) { |
| 180 return false; |
| 181 } |
| 182 |
| 183 base::Time shown_time = |
| 184 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, |
| 185 APP_BANNER_EVENT_DID_SHOW); |
| 186 if (time - shown_time < |
| 187 base::TimeDelta::FromDays(kMinimumDaysBetweenBannerShows)) { |
| 188 return false; |
| 189 } |
| 190 |
| 191 std::vector<base::Time> could_show_events = GetCouldShowBannerEvents( |
| 192 web_contents, origin_url, package_name_or_start_url); |
| 193 return could_show_events.size() >= kCouldShowEventsToTrigger; |
| 194 } |
| 195 |
146 std::vector<base::Time> AppBannerSettingsHelper::GetCouldShowBannerEvents( | 196 std::vector<base::Time> AppBannerSettingsHelper::GetCouldShowBannerEvents( |
147 content::WebContents* web_contents, | 197 content::WebContents* web_contents, |
148 const GURL& origin_url, | 198 const GURL& origin_url, |
149 const std::string& package_name_or_start_url) { | 199 const std::string& package_name_or_start_url) { |
150 std::vector<base::Time> result; | 200 std::vector<base::Time> result; |
151 if (package_name_or_start_url.empty()) | 201 if (package_name_or_start_url.empty()) |
152 return result; | 202 return result; |
153 | 203 |
154 Profile* profile = | 204 Profile* profile = |
155 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 205 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
156 HostContentSettingsMap* settings = profile->GetHostContentSettingsMap(); | 206 HostContentSettingsMap* settings = profile->GetHostContentSettingsMap(); |
157 scoped_ptr<base::DictionaryValue> origin_dict = | 207 scoped_ptr<base::DictionaryValue> origin_dict = |
158 GetOriginDict(settings, origin_url); | 208 GetOriginDict(settings, origin_url); |
159 | 209 |
160 if (!origin_dict) | 210 if (!origin_dict) |
161 return result; | 211 return result; |
162 | 212 |
163 base::DictionaryValue* app_dict = | 213 base::DictionaryValue* app_dict = |
164 GetAppDict(origin_dict.get(), package_name_or_start_url); | 214 GetAppDict(origin_dict.get(), package_name_or_start_url); |
165 if (!app_dict) | 215 if (!app_dict) |
166 return result; | 216 return result; |
167 | 217 |
| 218 std::string event_key(kBannerEventKeys[APP_BANNER_EVENT_COULD_SHOW]); |
168 base::ListValue* could_show_list = nullptr; | 219 base::ListValue* could_show_list = nullptr; |
169 if (!app_dict->GetList(kCouldShowBannerEventsKey, &could_show_list)) | 220 if (!app_dict->GetList(event_key, &could_show_list)) |
170 return result; | 221 return result; |
171 | 222 |
172 for (auto value : *could_show_list) { | 223 for (auto value : *could_show_list) { |
173 if (value->IsType(base::Value::TYPE_DOUBLE)) { | 224 if (value->IsType(base::Value::TYPE_DOUBLE)) { |
174 double internal_date; | 225 double internal_date; |
175 value->GetAsDouble(&internal_date); | 226 value->GetAsDouble(&internal_date); |
176 base::Time date = base::Time::FromInternalValue(internal_date); | 227 base::Time date = base::Time::FromInternalValue(internal_date); |
177 result.push_back(date); | 228 result.push_back(date); |
178 } | 229 } |
179 } | 230 } |
180 | 231 |
181 return result; | 232 return result; |
182 } | 233 } |
183 | 234 |
| 235 base::Time AppBannerSettingsHelper::GetSingleBannerEvent( |
| 236 content::WebContents* web_contents, |
| 237 const GURL& origin_url, |
| 238 const std::string& package_name_or_start_url, |
| 239 AppBannerEvent event) { |
| 240 DCHECK(event != APP_BANNER_EVENT_COULD_SHOW); |
| 241 DCHECK(event < APP_BANNER_EVENT_NUM_EVENTS); |
| 242 |
| 243 if (package_name_or_start_url.empty()) |
| 244 return base::Time(); |
| 245 |
| 246 Profile* profile = |
| 247 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| 248 HostContentSettingsMap* settings = profile->GetHostContentSettingsMap(); |
| 249 scoped_ptr<base::DictionaryValue> origin_dict = |
| 250 GetOriginDict(settings, origin_url); |
| 251 |
| 252 if (!origin_dict) |
| 253 return base::Time(); |
| 254 |
| 255 base::DictionaryValue* app_dict = |
| 256 GetAppDict(origin_dict.get(), package_name_or_start_url); |
| 257 if (!app_dict) |
| 258 return base::Time(); |
| 259 |
| 260 std::string event_key(kBannerEventKeys[event]); |
| 261 double internal_time; |
| 262 if (!app_dict->GetDouble(event_key, &internal_time)) |
| 263 return base::Time(); |
| 264 |
| 265 return base::Time::FromInternalValue(internal_time); |
| 266 } |
| 267 |
184 bool AppBannerSettingsHelper::IsAllowed( | 268 bool AppBannerSettingsHelper::IsAllowed( |
185 content::WebContents* web_contents, | 269 content::WebContents* web_contents, |
186 const GURL& origin_url, | 270 const GURL& origin_url, |
187 const std::string& package_name_or_start_url) { | 271 const std::string& package_name_or_start_url) { |
188 Profile* profile = | 272 Profile* profile = |
189 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 273 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
190 if (profile->IsOffTheRecord() || web_contents->GetURL() != origin_url || | 274 if (profile->IsOffTheRecord() || web_contents->GetURL() != origin_url || |
191 package_name_or_start_url.empty()) { | 275 package_name_or_start_url.empty()) { |
192 return false; | 276 return false; |
193 } | 277 } |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
237 GetAppDict(origin_dict.get(), package_name_or_start_url); | 321 GetAppDict(origin_dict.get(), package_name_or_start_url); |
238 if (!app_dict) | 322 if (!app_dict) |
239 return; | 323 return; |
240 | 324 |
241 // Update the setting and save it back. | 325 // Update the setting and save it back. |
242 app_dict->SetBoolean(kHasBlockedKey, true); | 326 app_dict->SetBoolean(kHasBlockedKey, true); |
243 settings->SetWebsiteSetting(pattern, ContentSettingsPattern::Wildcard(), | 327 settings->SetWebsiteSetting(pattern, ContentSettingsPattern::Wildcard(), |
244 CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), | 328 CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), |
245 origin_dict.release()); | 329 origin_dict.release()); |
246 } | 330 } |
OLD | NEW |