Chromium Code Reviews| 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 <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <algorithm> | 9 #include <memory> |
| 10 #include <string> | 10 #include <string> |
| 11 #include <utility> | 11 #include <utility> |
| 12 | 12 |
| 13 #include "base/command_line.h" | 13 #include "base/command_line.h" |
| 14 #include "base/memory/ptr_util.h" | 14 #include "base/memory/ptr_util.h" |
| 15 #include "base/metrics/field_trial.h" | |
| 16 #include "base/strings/string_number_conversions.h" | 15 #include "base/strings/string_number_conversions.h" |
| 17 #include "base/strings/string_util.h" | |
| 18 #include "chrome/browser/banners/app_banner_manager.h" | 16 #include "chrome/browser/banners/app_banner_manager.h" |
| 19 #include "chrome/browser/banners/app_banner_metrics.h" | 17 #include "chrome/browser/banners/app_banner_metrics.h" |
| 20 #include "chrome/browser/browser_process.h" | 18 #include "chrome/browser/browser_process.h" |
| 21 #include "chrome/browser/content_settings/host_content_settings_map_factory.h" | 19 #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| 22 #include "chrome/browser/installable/installable_logging.h" | 20 #include "chrome/browser/installable/installable_logging.h" |
| 23 #include "chrome/browser/profiles/profile.h" | 21 #include "chrome/browser/profiles/profile.h" |
| 24 #include "chrome/common/chrome_switches.h" | 22 #include "chrome/common/chrome_switches.h" |
| 25 #include "components/content_settings/core/browser/host_content_settings_map.h" | 23 #include "components/content_settings/core/browser/host_content_settings_map.h" |
| 26 #include "components/content_settings/core/common/content_settings_pattern.h" | 24 #include "components/content_settings/core/common/content_settings_pattern.h" |
| 27 #include "components/rappor/public/rappor_utils.h" | 25 #include "components/rappor/public/rappor_utils.h" |
| 28 #include "components/rappor/rappor_service_impl.h" | 26 #include "components/rappor/rappor_service_impl.h" |
| 29 #include "components/variations/variations_associated_data.h" | 27 #include "components/variations/variations_associated_data.h" |
| 30 #include "content/public/browser/web_contents.h" | 28 #include "content/public/browser/web_contents.h" |
| 31 #include "net/base/escape.h" | |
| 32 #include "url/gurl.h" | 29 #include "url/gurl.h" |
| 33 | 30 |
| 34 namespace { | 31 namespace { |
| 35 | 32 |
| 36 // Max number of apps (including ServiceWorker based web apps) that a particular | 33 // Max number of apps (including ServiceWorker based web apps) that a particular |
| 37 // site may show a banner for. | 34 // site may show a banner for. |
| 38 const size_t kMaxAppsPerSite = 3; | 35 const size_t kMaxAppsPerSite = 3; |
| 39 | 36 |
| 40 // Oldest could show banner event we care about, in days. | |
| 41 const unsigned int kOldestCouldShowBannerEventInDays = 14; | |
| 42 | |
| 43 // Default number of days that dismissing or ignoring the banner will prevent it | 37 // Default number of days that dismissing or ignoring the banner will prevent it |
| 44 // being seen again for. | 38 // being seen again for. |
| 45 const unsigned int kMinimumBannerBlockedToBannerShown = 90; | 39 const unsigned int kMinimumBannerBlockedToBannerShown = 90; |
| 46 const unsigned int kMinimumDaysBetweenBannerShows = 14; | 40 const unsigned int kMinimumDaysBetweenBannerShows = 14; |
| 47 | 41 |
| 48 const unsigned int kNumberOfMinutesInADay = 1440; | 42 // Default site engagement required to trigger the banner. |
| 49 | |
| 50 // Default scores assigned to direct and indirect navigations respectively. | |
| 51 const unsigned int kDefaultDirectNavigationEngagement = 1; | |
| 52 const unsigned int kDefaultIndirectNavigationEngagement = 1; | |
| 53 | |
| 54 // Default number of navigations required to trigger the banner. | |
| 55 const unsigned int kDefaultTotalEngagementToTrigger = 2; | 43 const unsigned int kDefaultTotalEngagementToTrigger = 2; |
| 56 | 44 |
| 57 // The number of days in the past that a site should be launched from homescreen | 45 // The number of days in the past that a site should be launched from homescreen |
| 58 // to be considered recent. | 46 // to be considered recent. |
| 59 // TODO(dominickn): work out how to unify this with | 47 // TODO(dominickn): work out how to unify this with |
| 60 // WebappDataStorage.wasLaunchedRecently. | 48 // WebappDataStorage.wasLaunchedRecently. |
| 61 const unsigned int kRecentLastLaunchInDays = 10; | 49 const unsigned int kRecentLastLaunchInDays = 10; |
| 62 | 50 |
| 63 // Dictionary keys to use for the events. | 51 // Dictionary keys to use for the events. Must be kept in sync with |
| 52 // AppBannerEvent. | |
| 64 const char* kBannerEventKeys[] = { | 53 const char* kBannerEventKeys[] = { |
| 65 "couldShowBannerEvents", | 54 "couldShowBannerEvents", |
| 66 "didShowBannerEvent", | 55 "didShowBannerEvent", |
| 67 "didBlockBannerEvent", | 56 "didBlockBannerEvent", |
| 68 "didAddToHomescreenEvent", | 57 "didAddToHomescreenEvent", |
| 69 }; | 58 }; |
| 70 | 59 |
| 71 // Keys to use when storing BannerEvent structs. | |
| 72 const char kBannerTimeKey[] = "time"; | |
| 73 const char kBannerEngagementKey[] = "engagement"; | |
| 74 | |
| 75 // Keys to use when querying the variations params. | 60 // Keys to use when querying the variations params. |
| 76 const char kBannerParamsKey[] = "AppBannerTriggering"; | 61 const char kBannerParamsKey[] = "AppBannerTriggering"; |
| 77 const char kBannerParamsDirectKey[] = "direct"; | |
| 78 const char kBannerParamsIndirectKey[] = "indirect"; | |
| 79 const char kBannerParamsTotalKey[] = "total"; | |
| 80 const char kBannerParamsMinutesKey[] = "minutes"; | |
| 81 const char kBannerParamsEngagementTotalKey[] = "site_engagement_total"; | 62 const char kBannerParamsEngagementTotalKey[] = "site_engagement_total"; |
| 82 const char kBannerParamsDaysAfterBannerDismissedKey[] = "days_after_dismiss"; | 63 const char kBannerParamsDaysAfterBannerDismissedKey[] = "days_after_dismiss"; |
| 83 const char kBannerParamsDaysAfterBannerIgnoredKey[] = "days_after_ignore"; | 64 const char kBannerParamsDaysAfterBannerIgnoredKey[] = "days_after_ignore"; |
| 84 const char kBannerSiteEngagementParamsKey[] = "use_site_engagement"; | |
| 85 const char kBannerParamsLanguageKey[] = "language_option"; | 65 const char kBannerParamsLanguageKey[] = "language_option"; |
| 86 | 66 |
| 87 // Engagement weight assigned to direct and indirect navigations. | |
| 88 // By default, a direct navigation is a page visit via ui::PAGE_TRANSITION_TYPED | |
| 89 // or ui::PAGE_TRANSITION_GENERATED. | |
| 90 double gDirectNavigationEngagement = kDefaultDirectNavigationEngagement; | |
| 91 double gIndirectNavigationEnagagement = kDefaultIndirectNavigationEngagement; | |
| 92 | |
| 93 // Number of minutes between visits that will trigger a could show banner event. | |
| 94 // Defaults to the number of minutes in a day. | |
| 95 unsigned int gMinimumMinutesBetweenVisits = kNumberOfMinutesInADay; | |
| 96 | |
| 97 // Total engagement score required before a banner will actually be triggered. | 67 // Total engagement score required before a banner will actually be triggered. |
| 98 double gTotalEngagementToTrigger = kDefaultTotalEngagementToTrigger; | 68 double gTotalEngagementToTrigger = kDefaultTotalEngagementToTrigger; |
| 99 | 69 |
| 100 unsigned int gDaysAfterDismissedToShow = kMinimumBannerBlockedToBannerShown; | 70 unsigned int gDaysAfterDismissedToShow = kMinimumBannerBlockedToBannerShown; |
| 101 unsigned int gDaysAfterIgnoredToShow = kMinimumDaysBetweenBannerShows; | 71 unsigned int gDaysAfterIgnoredToShow = kMinimumDaysBetweenBannerShows; |
| 102 | 72 |
| 103 std::unique_ptr<base::DictionaryValue> GetOriginDict( | 73 std::unique_ptr<base::DictionaryValue> GetOriginDict( |
| 104 HostContentSettingsMap* settings, | 74 HostContentSettingsMap* settings, |
| 105 const GURL& origin_url) { | 75 const GURL& origin_url) { |
| 106 if (!settings) | 76 if (!settings) |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 124 if (origin_dict->size() < kMaxAppsPerSite) { | 94 if (origin_dict->size() < kMaxAppsPerSite) { |
| 125 app_dict = new base::DictionaryValue(); | 95 app_dict = new base::DictionaryValue(); |
| 126 origin_dict->SetWithoutPathExpansion(key_name, | 96 origin_dict->SetWithoutPathExpansion(key_name, |
| 127 base::WrapUnique(app_dict)); | 97 base::WrapUnique(app_dict)); |
| 128 } | 98 } |
| 129 } | 99 } |
| 130 | 100 |
| 131 return app_dict; | 101 return app_dict; |
| 132 } | 102 } |
| 133 | 103 |
| 134 double GetEventEngagement(ui::PageTransition transition_type) { | |
| 135 if (ui::PageTransitionCoreTypeIs(transition_type, | |
| 136 ui::PAGE_TRANSITION_TYPED) || | |
| 137 ui::PageTransitionCoreTypeIs(transition_type, | |
| 138 ui::PAGE_TRANSITION_GENERATED)) { | |
| 139 return gDirectNavigationEngagement; | |
| 140 } else { | |
| 141 return gIndirectNavigationEnagagement; | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 // Queries variations for the number of days which dismissing and ignoring the | 104 // Queries variations for the number of days which dismissing and ignoring the |
| 146 // banner should prevent a banner from showing. | 105 // banner should prevent a banner from showing. |
| 147 void UpdateDaysBetweenShowing() { | 106 void UpdateDaysBetweenShowing() { |
| 148 std::string dismiss_param = variations::GetVariationParamValue( | 107 std::string dismiss_param = variations::GetVariationParamValue( |
| 149 kBannerParamsKey, kBannerParamsDaysAfterBannerDismissedKey); | 108 kBannerParamsKey, kBannerParamsDaysAfterBannerDismissedKey); |
| 150 std::string ignore_param = variations::GetVariationParamValue( | 109 std::string ignore_param = variations::GetVariationParamValue( |
| 151 kBannerParamsKey, kBannerParamsDaysAfterBannerIgnoredKey); | 110 kBannerParamsKey, kBannerParamsDaysAfterBannerIgnoredKey); |
| 152 | 111 |
| 153 if (!dismiss_param.empty() && !ignore_param.empty()) { | 112 if (!dismiss_param.empty() && !ignore_param.empty()) { |
| 154 unsigned int dismiss_days = 0; | 113 unsigned int dismiss_days = 0; |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 171 if (!total_param.empty()) { | 130 if (!total_param.empty()) { |
| 172 double total_engagement = -1; | 131 double total_engagement = -1; |
| 173 | 132 |
| 174 if (base::StringToDouble(total_param, &total_engagement) && | 133 if (base::StringToDouble(total_param, &total_engagement) && |
| 175 total_engagement >= 0) { | 134 total_engagement >= 0) { |
| 176 AppBannerSettingsHelper::SetTotalEngagementToTrigger(total_engagement); | 135 AppBannerSettingsHelper::SetTotalEngagementToTrigger(total_engagement); |
| 177 } | 136 } |
| 178 } | 137 } |
| 179 } | 138 } |
| 180 | 139 |
| 181 // Queries variations for updates to the default engagement values assigned | |
| 182 // to direct and indirect navigations. | |
| 183 void UpdateEngagementWeights() { | |
| 184 std::string direct_param = variations::GetVariationParamValue( | |
| 185 kBannerParamsKey, kBannerParamsDirectKey); | |
| 186 std::string indirect_param = variations::GetVariationParamValue( | |
| 187 kBannerParamsKey, kBannerParamsIndirectKey); | |
| 188 std::string total_param = variations::GetVariationParamValue( | |
| 189 kBannerParamsKey, kBannerParamsTotalKey); | |
| 190 | |
| 191 if (!direct_param.empty() && !indirect_param.empty() && | |
| 192 !total_param.empty()) { | |
| 193 double direct_engagement = -1; | |
| 194 double indirect_engagement = -1; | |
| 195 double total_engagement = -1; | |
| 196 | |
| 197 // Ensure that we get valid doubles from the field trial, and that both | |
| 198 // values are greater than or equal to zero and less than or equal to the | |
| 199 // total engagement required to trigger the banner. | |
| 200 if (base::StringToDouble(direct_param, &direct_engagement) && | |
| 201 base::StringToDouble(indirect_param, &indirect_engagement) && | |
| 202 base::StringToDouble(total_param, &total_engagement) && | |
| 203 direct_engagement >= 0 && indirect_engagement >= 0 && | |
| 204 total_engagement > 0 && direct_engagement <= total_engagement && | |
| 205 indirect_engagement <= total_engagement) { | |
| 206 AppBannerSettingsHelper::SetEngagementWeights(direct_engagement, | |
| 207 indirect_engagement); | |
| 208 AppBannerSettingsHelper::SetTotalEngagementToTrigger(total_engagement); | |
| 209 } | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 // Queries variation for updates to the default number of minutes between | |
| 214 // site visits counted for the purposes of displaying a banner. | |
| 215 void UpdateMinutesBetweenVisits() { | |
| 216 std::string param = variations::GetVariationParamValue( | |
| 217 kBannerParamsKey, kBannerParamsMinutesKey); | |
| 218 if (!param.empty()) { | |
| 219 int minimum_minutes = 0; | |
| 220 if (base::StringToInt(param, &minimum_minutes)) | |
| 221 AppBannerSettingsHelper::SetMinimumMinutesBetweenVisits(minimum_minutes); | |
| 222 } | |
| 223 } | |
| 224 | |
| 225 } // namespace | 140 } // namespace |
| 226 | 141 |
| 227 // Key to store instant apps events. | 142 // Key to store instant apps events. |
| 228 const char AppBannerSettingsHelper::kInstantAppsKey[] = "instantapps"; | 143 const char AppBannerSettingsHelper::kInstantAppsKey[] = "instantapps"; |
| 229 | 144 |
| 230 void AppBannerSettingsHelper::ClearHistoryForURLs( | 145 void AppBannerSettingsHelper::ClearHistoryForURLs( |
| 231 Profile* profile, | 146 Profile* profile, |
| 232 const std::set<GURL>& origin_urls) { | 147 const std::set<GURL>& origin_urls) { |
| 233 HostContentSettingsMap* settings = | 148 HostContentSettingsMap* settings = |
| 234 HostContentSettingsMapFactory::GetForProfile(profile); | 149 HostContentSettingsMapFactory::GetForProfile(profile); |
| 235 for (const GURL& origin_url : origin_urls) { | 150 for (const GURL& origin_url : origin_urls) { |
| 236 settings->SetWebsiteSettingDefaultScope(origin_url, GURL(), | 151 settings->SetWebsiteSettingDefaultScope(origin_url, GURL(), |
| 237 CONTENT_SETTINGS_TYPE_APP_BANNER, | 152 CONTENT_SETTINGS_TYPE_APP_BANNER, |
| 238 std::string(), nullptr); | 153 std::string(), nullptr); |
| 239 settings->FlushLossyWebsiteSettings(); | 154 settings->FlushLossyWebsiteSettings(); |
| 240 } | 155 } |
| 241 } | 156 } |
| 242 | 157 |
| 243 void AppBannerSettingsHelper::RecordBannerInstallEvent( | 158 void AppBannerSettingsHelper::RecordBannerInstallEvent( |
| 244 content::WebContents* web_contents, | 159 content::WebContents* web_contents, |
| 245 const std::string& package_name_or_start_url, | 160 const std::string& package_name_or_start_url, |
| 246 AppBannerRapporMetric rappor_metric) { | 161 AppBannerRapporMetric rappor_metric) { |
| 247 banners::TrackInstallEvent(banners::INSTALL_EVENT_WEB_APP_INSTALLED); | 162 banners::TrackInstallEvent(banners::INSTALL_EVENT_WEB_APP_INSTALLED); |
| 248 | 163 |
| 249 AppBannerSettingsHelper::RecordBannerEvent( | 164 AppBannerSettingsHelper::RecordBannerEvent( |
| 250 web_contents, web_contents->GetURL(), package_name_or_start_url, | 165 web_contents, web_contents->GetLastCommittedURL(), |
|
benwells
2016/12/09 04:55:05
Comment: It would probably have been wiser to put
dominickn
2016/12/09 05:27:28
Acknowledged - I was doing so much surgery here th
benwells
2016/12/09 05:44:55
Understood. I'd leave it as is, it was more a comm
| |
| 166 package_name_or_start_url, | |
| 251 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN, | 167 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN, |
| 252 banners::AppBannerManager::GetCurrentTime()); | 168 banners::AppBannerManager::GetCurrentTime()); |
| 253 | 169 |
| 254 rappor::SampleDomainAndRegistryFromGURL( | 170 rappor::SampleDomainAndRegistryFromGURL( |
| 255 g_browser_process->rappor_service(), | 171 g_browser_process->rappor_service(), |
| 256 (rappor_metric == WEB ? "AppBanner.WebApp.Installed" | 172 (rappor_metric == WEB ? "AppBanner.WebApp.Installed" |
| 257 : "AppBanner.NativeApp.Installed"), | 173 : "AppBanner.NativeApp.Installed"), |
| 258 web_contents->GetURL()); | 174 web_contents->GetLastCommittedURL()); |
| 259 } | 175 } |
| 260 | 176 |
| 261 void AppBannerSettingsHelper::RecordBannerDismissEvent( | 177 void AppBannerSettingsHelper::RecordBannerDismissEvent( |
| 262 content::WebContents* web_contents, | 178 content::WebContents* web_contents, |
| 263 const std::string& package_name_or_start_url, | 179 const std::string& package_name_or_start_url, |
| 264 AppBannerRapporMetric rappor_metric) { | 180 AppBannerRapporMetric rappor_metric) { |
| 265 banners::TrackDismissEvent(banners::DISMISS_EVENT_CLOSE_BUTTON); | 181 banners::TrackDismissEvent(banners::DISMISS_EVENT_CLOSE_BUTTON); |
| 266 | 182 |
| 267 AppBannerSettingsHelper::RecordBannerEvent( | 183 AppBannerSettingsHelper::RecordBannerEvent( |
| 268 web_contents, web_contents->GetURL(), package_name_or_start_url, | 184 web_contents, web_contents->GetLastCommittedURL(), |
| 185 package_name_or_start_url, | |
| 269 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK, | 186 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK, |
| 270 banners::AppBannerManager::GetCurrentTime()); | 187 banners::AppBannerManager::GetCurrentTime()); |
| 271 | 188 |
| 272 rappor::SampleDomainAndRegistryFromGURL( | 189 rappor::SampleDomainAndRegistryFromGURL( |
| 273 g_browser_process->rappor_service(), | 190 g_browser_process->rappor_service(), |
| 274 (rappor_metric == WEB ? "AppBanner.WebApp.Dismissed" | 191 (rappor_metric == WEB ? "AppBanner.WebApp.Dismissed" |
| 275 : "AppBanner.NativeApp.Dismissed"), | 192 : "AppBanner.NativeApp.Dismissed"), |
| 276 web_contents->GetURL()); | 193 web_contents->GetLastCommittedURL()); |
| 277 } | 194 } |
| 278 | 195 |
| 279 void AppBannerSettingsHelper::RecordBannerEvent( | 196 void AppBannerSettingsHelper::RecordBannerEvent( |
| 280 content::WebContents* web_contents, | 197 content::WebContents* web_contents, |
| 281 const GURL& origin_url, | 198 const GURL& origin_url, |
| 282 const std::string& package_name_or_start_url, | 199 const std::string& package_name_or_start_url, |
| 283 AppBannerEvent event, | 200 AppBannerEvent event, |
| 284 base::Time time) { | 201 base::Time time) { |
| 285 DCHECK(event != APP_BANNER_EVENT_COULD_SHOW); | |
| 286 | |
| 287 Profile* profile = | 202 Profile* profile = |
| 288 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 203 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| 289 if (profile->IsOffTheRecord() || package_name_or_start_url.empty()) | 204 if (profile->IsOffTheRecord() || package_name_or_start_url.empty()) |
| 290 return; | 205 return; |
| 291 | 206 |
| 292 HostContentSettingsMap* settings = | 207 HostContentSettingsMap* settings = |
| 293 HostContentSettingsMapFactory::GetForProfile(profile); | 208 HostContentSettingsMapFactory::GetForProfile(profile); |
| 294 std::unique_ptr<base::DictionaryValue> origin_dict = | 209 std::unique_ptr<base::DictionaryValue> origin_dict = |
| 295 GetOriginDict(settings, origin_url); | 210 GetOriginDict(settings, origin_url); |
| 296 if (!origin_dict) | 211 if (!origin_dict) |
| 297 return; | 212 return; |
| 298 | 213 |
| 299 base::DictionaryValue* app_dict = | 214 base::DictionaryValue* app_dict = |
| 300 GetAppDict(origin_dict.get(), package_name_or_start_url); | 215 GetAppDict(origin_dict.get(), package_name_or_start_url); |
| 301 if (!app_dict) | 216 if (!app_dict) |
| 302 return; | 217 return; |
| 303 | 218 |
| 304 // Dates are stored in their raw form (i.e. not local dates) to be resilient | 219 // Dates are stored in their raw form (i.e. not local dates) to be resilient |
| 305 // to time zone changes. | 220 // to time zone changes. |
| 306 std::string event_key(kBannerEventKeys[event]); | 221 std::string event_key(kBannerEventKeys[event]); |
| 222 | |
| 223 if (event == APP_BANNER_EVENT_COULD_SHOW) { | |
| 224 // Do not overwrite a could show event, as this is used for metrics. | |
| 225 double internal_date; | |
| 226 if (app_dict->GetDouble(event_key, &internal_date)) | |
| 227 return; | |
| 228 } | |
| 307 app_dict->SetDouble(event_key, time.ToInternalValue()); | 229 app_dict->SetDouble(event_key, time.ToInternalValue()); |
| 308 | 230 |
| 309 settings->SetWebsiteSettingDefaultScope( | 231 settings->SetWebsiteSettingDefaultScope( |
| 310 origin_url, GURL(), CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), | 232 origin_url, GURL(), CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), |
| 311 std::move(origin_dict)); | 233 std::move(origin_dict)); |
| 312 | 234 |
| 313 // App banner content settings are lossy, meaning they will not cause the | 235 // App banner content settings are lossy, meaning they will not cause the |
| 314 // prefs to become dirty. This is fine for most events, as if they are lost it | 236 // prefs to become dirty. This is fine for most events, as if they are lost it |
| 315 // just means the user will have to engage a little bit more. However the | 237 // just means the user will have to engage a little bit more. However the |
| 316 // DID_ADD_TO_HOMESCREEN event should always be recorded to prevent | 238 // DID_ADD_TO_HOMESCREEN event should always be recorded to prevent |
| 317 // spamminess. | 239 // spamminess. |
| 318 if (event == APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN) | 240 if (event == APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN) |
| 319 settings->FlushLossyWebsiteSettings(); | 241 settings->FlushLossyWebsiteSettings(); |
| 320 } | 242 } |
| 321 | 243 |
| 322 void AppBannerSettingsHelper::RecordBannerCouldShowEvent( | |
| 323 content::WebContents* web_contents, | |
| 324 const GURL& origin_url, | |
| 325 const std::string& package_name_or_start_url, | |
| 326 base::Time time, | |
| 327 ui::PageTransition transition_type) { | |
| 328 Profile* profile = | |
| 329 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
| 330 if (profile->IsOffTheRecord() || package_name_or_start_url.empty()) | |
| 331 return; | |
| 332 | |
| 333 HostContentSettingsMap* settings = | |
| 334 HostContentSettingsMapFactory::GetForProfile(profile); | |
| 335 std::unique_ptr<base::DictionaryValue> origin_dict = | |
| 336 GetOriginDict(settings, origin_url); | |
| 337 if (!origin_dict) | |
| 338 return; | |
| 339 | |
| 340 base::DictionaryValue* app_dict = | |
| 341 GetAppDict(origin_dict.get(), package_name_or_start_url); | |
| 342 if (!app_dict) | |
| 343 return; | |
| 344 | |
| 345 std::string event_key(kBannerEventKeys[APP_BANNER_EVENT_COULD_SHOW]); | |
| 346 double engagement = GetEventEngagement(transition_type); | |
| 347 | |
| 348 base::ListValue* could_show_list = nullptr; | |
| 349 if (!app_dict->GetList(event_key, &could_show_list)) { | |
| 350 could_show_list = new base::ListValue(); | |
| 351 app_dict->Set(event_key, base::WrapUnique(could_show_list)); | |
| 352 } | |
| 353 | |
| 354 // Trim any items that are older than we should care about. For comparisons | |
| 355 // the times are converted to local dates. | |
| 356 base::Time date = BucketTimeToResolution(time, gMinimumMinutesBetweenVisits); | |
| 357 for (auto it = could_show_list->begin(); it != could_show_list->end();) { | |
| 358 if ((*it)->IsType(base::Value::Type::DICTIONARY)) { | |
| 359 base::DictionaryValue* internal_value; | |
| 360 double internal_date; | |
| 361 (*it)->GetAsDictionary(&internal_value); | |
| 362 | |
| 363 if (internal_value->GetDouble(kBannerTimeKey, &internal_date)) { | |
| 364 base::Time other_date = | |
| 365 BucketTimeToResolution(base::Time::FromInternalValue(internal_date), | |
| 366 gMinimumMinutesBetweenVisits); | |
| 367 if (other_date == date) { | |
| 368 double other_engagement = 0; | |
| 369 if (internal_value->GetDouble(kBannerEngagementKey, | |
| 370 &other_engagement) && | |
| 371 other_engagement >= engagement) { | |
| 372 // This date has already been added, but with an equal or higher | |
| 373 // engagement. Don't add the date again. If the conditional fails, | |
| 374 // fall to the end of the loop where the existing entry is deleted. | |
| 375 return; | |
| 376 } | |
| 377 } else { | |
| 378 base::TimeDelta delta = date - other_date; | |
| 379 if (delta < | |
| 380 base::TimeDelta::FromDays(kOldestCouldShowBannerEventInDays)) { | |
| 381 ++it; | |
| 382 continue; | |
| 383 } | |
| 384 } | |
| 385 } | |
| 386 } | |
| 387 | |
| 388 // Either this date is older than we care about, or it isn't in the correct | |
| 389 // format, or it is the same as the current date but with a lower | |
| 390 // engagement, so remove it. | |
| 391 it = could_show_list->Erase(it, nullptr); | |
| 392 } | |
| 393 | |
| 394 // Dates are stored in their raw form (i.e. not local dates) to be resilient | |
| 395 // to time zone changes. | |
| 396 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue()); | |
| 397 value->SetDouble(kBannerTimeKey, time.ToInternalValue()); | |
| 398 value->SetDouble(kBannerEngagementKey, engagement); | |
| 399 could_show_list->Append(std::move(value)); | |
| 400 | |
| 401 settings->SetWebsiteSettingDefaultScope( | |
| 402 origin_url, GURL(), CONTENT_SETTINGS_TYPE_APP_BANNER, std::string(), | |
| 403 std::move(origin_dict)); | |
| 404 } | |
| 405 | |
| 406 InstallableStatusCode AppBannerSettingsHelper::ShouldShowBanner( | 244 InstallableStatusCode AppBannerSettingsHelper::ShouldShowBanner( |
| 407 content::WebContents* web_contents, | 245 content::WebContents* web_contents, |
| 408 const GURL& origin_url, | 246 const GURL& origin_url, |
| 409 const std::string& package_name_or_start_url, | 247 const std::string& package_name_or_start_url, |
| 410 base::Time time) { | 248 base::Time time) { |
| 411 // Ignore all checks if the flag to do so is set. | 249 // Ignore all checks if the flag to do so is set. |
| 412 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 250 if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 413 switches::kBypassAppBannerEngagementChecks)) { | 251 switches::kBypassAppBannerEngagementChecks)) { |
| 414 return NO_ERROR_DETECTED; | 252 return NO_ERROR_DETECTED; |
| 415 } | 253 } |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 436 return PREVIOUSLY_BLOCKED; | 274 return PREVIOUSLY_BLOCKED; |
| 437 } | 275 } |
| 438 | 276 |
| 439 base::Time shown_time = | 277 base::Time shown_time = |
| 440 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, | 278 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, |
| 441 APP_BANNER_EVENT_DID_SHOW); | 279 APP_BANNER_EVENT_DID_SHOW); |
| 442 if (time - shown_time < base::TimeDelta::FromDays(gDaysAfterIgnoredToShow)) { | 280 if (time - shown_time < base::TimeDelta::FromDays(gDaysAfterIgnoredToShow)) { |
| 443 return PREVIOUSLY_IGNORED; | 281 return PREVIOUSLY_IGNORED; |
| 444 } | 282 } |
| 445 | 283 |
| 446 // If we have gotten this far and want to use site engagement, the banner flow | |
| 447 // was triggered by the site engagement service informing the banner manager | |
| 448 // that sufficient engagement has been accumulated. Hence there is no need to | |
| 449 // check the total amount of engagement. | |
| 450 // TODO(dominickn): just return true here and remove all of the following code | |
| 451 // in this method when app banners have fully migrated to using site | |
| 452 // engagement as a trigger condition. See crbug.com/616322. | |
| 453 // Do not do engagement checks for instant app banners. | |
| 454 if (ShouldUseSiteEngagementScore() || | |
| 455 package_name_or_start_url == kInstantAppsKey) { | |
| 456 return NO_ERROR_DETECTED; | |
| 457 } | |
| 458 | |
| 459 double total_engagement = 0; | |
| 460 std::vector<BannerEvent> could_show_events = GetCouldShowBannerEvents( | |
| 461 web_contents, origin_url, package_name_or_start_url); | |
| 462 | |
| 463 for (const auto& event : could_show_events) | |
| 464 total_engagement += event.engagement; | |
| 465 | |
| 466 if (!HasSufficientEngagement(total_engagement)) | |
| 467 return INSUFFICIENT_ENGAGEMENT; | |
| 468 | |
| 469 return NO_ERROR_DETECTED; | 284 return NO_ERROR_DETECTED; |
| 470 } | 285 } |
| 471 | 286 |
| 472 std::vector<AppBannerSettingsHelper::BannerEvent> | |
| 473 AppBannerSettingsHelper::GetCouldShowBannerEvents( | |
| 474 content::WebContents* web_contents, | |
| 475 const GURL& origin_url, | |
| 476 const std::string& package_name_or_start_url) { | |
| 477 std::vector<BannerEvent> result; | |
| 478 | |
| 479 Profile* profile = | |
| 480 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
| 481 HostContentSettingsMap* settings = | |
| 482 HostContentSettingsMapFactory::GetForProfile(profile); | |
| 483 std::unique_ptr<base::DictionaryValue> origin_dict = | |
| 484 GetOriginDict(settings, origin_url); | |
| 485 | |
| 486 if (!origin_dict) | |
| 487 return result; | |
| 488 | |
| 489 base::DictionaryValue* app_dict = | |
| 490 GetAppDict(origin_dict.get(), package_name_or_start_url); | |
| 491 if (!app_dict) | |
| 492 return result; | |
| 493 | |
| 494 std::string event_key(kBannerEventKeys[APP_BANNER_EVENT_COULD_SHOW]); | |
| 495 base::ListValue* could_show_list = nullptr; | |
| 496 if (!app_dict->GetList(event_key, &could_show_list)) | |
| 497 return result; | |
| 498 | |
| 499 for (const auto& value : *could_show_list) { | |
| 500 if (value->IsType(base::Value::Type::DICTIONARY)) { | |
| 501 base::DictionaryValue* internal_value; | |
| 502 double internal_date = 0; | |
| 503 value->GetAsDictionary(&internal_value); | |
| 504 double engagement = 0; | |
| 505 | |
| 506 if (internal_value->GetDouble(kBannerTimeKey, &internal_date) && | |
| 507 internal_value->GetDouble(kBannerEngagementKey, &engagement)) { | |
| 508 base::Time date = base::Time::FromInternalValue(internal_date); | |
| 509 result.push_back({date, engagement}); | |
| 510 } | |
| 511 } | |
| 512 } | |
| 513 | |
| 514 return result; | |
| 515 } | |
| 516 | |
| 517 base::Time AppBannerSettingsHelper::GetSingleBannerEvent( | 287 base::Time AppBannerSettingsHelper::GetSingleBannerEvent( |
| 518 content::WebContents* web_contents, | 288 content::WebContents* web_contents, |
| 519 const GURL& origin_url, | 289 const GURL& origin_url, |
| 520 const std::string& package_name_or_start_url, | 290 const std::string& package_name_or_start_url, |
| 521 AppBannerEvent event) { | 291 AppBannerEvent event) { |
| 522 DCHECK(event != APP_BANNER_EVENT_COULD_SHOW); | |
| 523 DCHECK(event < APP_BANNER_EVENT_NUM_EVENTS); | 292 DCHECK(event < APP_BANNER_EVENT_NUM_EVENTS); |
| 524 | 293 |
| 525 Profile* profile = | 294 Profile* profile = |
| 526 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 295 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| 527 HostContentSettingsMap* settings = | 296 HostContentSettingsMap* settings = |
| 528 HostContentSettingsMapFactory::GetForProfile(profile); | 297 HostContentSettingsMapFactory::GetForProfile(profile); |
| 529 std::unique_ptr<base::DictionaryValue> origin_dict = | 298 std::unique_ptr<base::DictionaryValue> origin_dict = |
| 530 GetOriginDict(settings, origin_url); | 299 GetOriginDict(settings, origin_url); |
| 531 | 300 |
| 532 if (!origin_dict) | 301 if (!origin_dict) |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 549 return (base::CommandLine::ForCurrentProcess()->HasSwitch( | 318 return (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 550 switches::kBypassAppBannerEngagementChecks)) || | 319 switches::kBypassAppBannerEngagementChecks)) || |
| 551 (total_engagement >= gTotalEngagementToTrigger); | 320 (total_engagement >= gTotalEngagementToTrigger); |
| 552 } | 321 } |
| 553 | 322 |
| 554 void AppBannerSettingsHelper::RecordMinutesFromFirstVisitToShow( | 323 void AppBannerSettingsHelper::RecordMinutesFromFirstVisitToShow( |
| 555 content::WebContents* web_contents, | 324 content::WebContents* web_contents, |
| 556 const GURL& origin_url, | 325 const GURL& origin_url, |
| 557 const std::string& package_name_or_start_url, | 326 const std::string& package_name_or_start_url, |
| 558 base::Time time) { | 327 base::Time time) { |
| 559 std::vector<BannerEvent> could_show_events = GetCouldShowBannerEvents( | 328 base::Time could_show_time = |
| 560 web_contents, origin_url, package_name_or_start_url); | 329 GetSingleBannerEvent(web_contents, origin_url, package_name_or_start_url, |
| 330 APP_BANNER_EVENT_COULD_SHOW); | |
| 561 | 331 |
| 562 int minutes = 0; | 332 int minutes = 0; |
| 563 if (could_show_events.size()) | 333 if (!could_show_time.is_null()) |
| 564 minutes = (time - could_show_events[0].time).InMinutes(); | 334 minutes = (time - could_show_time).InMinutes(); |
| 565 | 335 |
| 566 banners::TrackMinutesFromFirstVisitToBannerShown(minutes); | 336 banners::TrackMinutesFromFirstVisitToBannerShown(minutes); |
| 567 } | 337 } |
| 568 | 338 |
| 569 bool AppBannerSettingsHelper::WasLaunchedRecently(Profile* profile, | 339 bool AppBannerSettingsHelper::WasLaunchedRecently(Profile* profile, |
| 570 const GURL& origin_url, | 340 const GURL& origin_url, |
| 571 base::Time now) { | 341 base::Time now) { |
| 572 HostContentSettingsMap* settings = | 342 HostContentSettingsMap* settings = |
| 573 HostContentSettingsMapFactory::GetForProfile(profile); | 343 HostContentSettingsMapFactory::GetForProfile(profile); |
| 574 std::unique_ptr<base::DictionaryValue> origin_dict = | 344 std::unique_ptr<base::DictionaryValue> origin_dict = |
| 575 GetOriginDict(settings, origin_url); | 345 GetOriginDict(settings, origin_url); |
| 576 | 346 |
| 577 if (!origin_dict) | 347 if (!origin_dict) |
| 578 return false; | 348 return false; |
| 579 | 349 |
| 580 // Iterate over everything in the content setting, which should be a set of | 350 // Iterate over everything in the content setting, which should be a set of |
| 581 // dictionaries per app path. If we find one that has been added to | 351 // dictionaries per app path. If we find one that has been added to |
| 582 // homescreen recently, return true. | 352 // homescreen recently, return true. |
| 353 base::TimeDelta recent_last_launch_in_days = | |
| 354 base::TimeDelta::FromDays(kRecentLastLaunchInDays); | |
| 583 for (base::DictionaryValue::Iterator it(*origin_dict); !it.IsAtEnd(); | 355 for (base::DictionaryValue::Iterator it(*origin_dict); !it.IsAtEnd(); |
| 584 it.Advance()) { | 356 it.Advance()) { |
| 585 if (it.value().IsType(base::Value::Type::DICTIONARY)) { | 357 if (it.value().IsType(base::Value::Type::DICTIONARY)) { |
| 586 const base::DictionaryValue* value; | 358 const base::DictionaryValue* value; |
| 587 it.value().GetAsDictionary(&value); | 359 it.value().GetAsDictionary(&value); |
| 588 | 360 |
| 589 std::string event_key( | 361 std::string event_key( |
| 590 kBannerEventKeys[APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN]); | 362 kBannerEventKeys[APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN]); |
| 591 double internal_time; | 363 double internal_time; |
| 592 if (it.key() == kInstantAppsKey || | 364 if (it.key() == kInstantAppsKey || |
| 593 !value->GetDouble(event_key, &internal_time)) { | 365 !value->GetDouble(event_key, &internal_time)) { |
| 594 continue; | 366 continue; |
| 595 } | 367 } |
| 596 | 368 |
| 597 base::Time added_time = base::Time::FromInternalValue(internal_time); | 369 if ((now - base::Time::FromInternalValue(internal_time)) <= |
| 598 | 370 recent_last_launch_in_days) { |
| 599 if ((now - added_time) <= | |
| 600 base::TimeDelta::FromDays(kRecentLastLaunchInDays)) { | |
| 601 return true; | 371 return true; |
| 602 } | 372 } |
| 603 } | 373 } |
| 604 } | 374 } |
| 605 | 375 |
| 606 return false; | 376 return false; |
| 607 } | 377 } |
| 608 | 378 |
| 609 void AppBannerSettingsHelper::SetDaysAfterDismissAndIgnoreToTrigger( | 379 void AppBannerSettingsHelper::SetDaysAfterDismissAndIgnoreToTrigger( |
| 610 unsigned int dismiss_days, | 380 unsigned int dismiss_days, |
| 611 unsigned int ignore_days) { | 381 unsigned int ignore_days) { |
| 612 gDaysAfterDismissedToShow = dismiss_days; | 382 gDaysAfterDismissedToShow = dismiss_days; |
| 613 gDaysAfterIgnoredToShow = ignore_days; | 383 gDaysAfterIgnoredToShow = ignore_days; |
| 614 } | 384 } |
| 615 | 385 |
| 616 void AppBannerSettingsHelper::SetEngagementWeights(double direct_engagement, | |
| 617 double indirect_engagement) { | |
| 618 gDirectNavigationEngagement = direct_engagement; | |
| 619 gIndirectNavigationEnagagement = indirect_engagement; | |
| 620 } | |
| 621 | |
| 622 void AppBannerSettingsHelper::SetMinimumMinutesBetweenVisits( | |
| 623 unsigned int minutes) { | |
| 624 gMinimumMinutesBetweenVisits = minutes; | |
| 625 } | |
| 626 | |
| 627 void AppBannerSettingsHelper::SetTotalEngagementToTrigger( | 386 void AppBannerSettingsHelper::SetTotalEngagementToTrigger( |
| 628 double total_engagement) { | 387 double total_engagement) { |
| 629 gTotalEngagementToTrigger = total_engagement; | 388 gTotalEngagementToTrigger = total_engagement; |
| 630 } | 389 } |
| 631 | 390 |
| 632 void AppBannerSettingsHelper::SetDefaultParameters() { | 391 void AppBannerSettingsHelper::SetDefaultParameters() { |
| 633 SetDaysAfterDismissAndIgnoreToTrigger(kMinimumBannerBlockedToBannerShown, | |
| 634 kMinimumDaysBetweenBannerShows); | |
| 635 SetEngagementWeights(kDefaultDirectNavigationEngagement, | |
| 636 kDefaultIndirectNavigationEngagement); | |
| 637 SetMinimumMinutesBetweenVisits(kNumberOfMinutesInADay); | |
| 638 SetTotalEngagementToTrigger(kDefaultTotalEngagementToTrigger); | 392 SetTotalEngagementToTrigger(kDefaultTotalEngagementToTrigger); |
| 639 } | 393 } |
| 640 | 394 |
| 641 // Given a time, returns that time scoped to the nearest minute resolution | |
| 642 // locally. For example, if the resolution is one hour, this function will | |
| 643 // return the time to the closest (previous) hour in the local time zone. | |
| 644 base::Time AppBannerSettingsHelper::BucketTimeToResolution( | |
| 645 base::Time time, | |
| 646 unsigned int minutes) { | |
| 647 // Only support resolutions smaller than or equal to one day. Enforce | |
| 648 // that resolutions divide evenly into one day. Otherwise, default to a | |
| 649 // day resolution (each time converted to midnight local time). | |
| 650 if (minutes == 0 || minutes >= kNumberOfMinutesInADay || | |
| 651 kNumberOfMinutesInADay % minutes != 0) { | |
| 652 return time.LocalMidnight(); | |
| 653 } | |
| 654 | |
| 655 // Extract the number of minutes past midnight in local time. Divide that | |
| 656 // number by the resolution size, and return the time converted to local | |
| 657 // midnight with the resulting truncated number added. | |
| 658 base::Time::Exploded exploded; | |
| 659 time.LocalExplode(&exploded); | |
| 660 int total_minutes = exploded.hour * 60 + exploded.minute; | |
| 661 | |
| 662 // Use truncating integer division here. | |
| 663 return time.LocalMidnight() + | |
| 664 base::TimeDelta::FromMinutes((total_minutes / minutes) * minutes); | |
| 665 } | |
| 666 | |
| 667 void AppBannerSettingsHelper::UpdateFromFieldTrial() { | 395 void AppBannerSettingsHelper::UpdateFromFieldTrial() { |
| 668 // If we are using the site engagement score, only extract the total | 396 // If we are using the site engagement score, only extract the total |
| 669 // engagement to trigger from the params variations. | 397 // engagement to trigger from the params variations. |
| 670 UpdateDaysBetweenShowing(); | 398 UpdateDaysBetweenShowing(); |
| 671 if (ShouldUseSiteEngagementScore()) { | 399 UpdateSiteEngagementToTrigger(); |
| 672 UpdateSiteEngagementToTrigger(); | |
| 673 } else { | |
| 674 UpdateEngagementWeights(); | |
| 675 UpdateMinutesBetweenVisits(); | |
| 676 } | |
| 677 } | 400 } |
| 678 | 401 |
| 679 AppBannerSettingsHelper::LanguageOption | 402 AppBannerSettingsHelper::LanguageOption |
| 680 AppBannerSettingsHelper::GetHomescreenLanguageOption() { | 403 AppBannerSettingsHelper::GetHomescreenLanguageOption() { |
| 681 std::string param = variations::GetVariationParamValue( | 404 std::string param = variations::GetVariationParamValue( |
| 682 kBannerParamsKey, kBannerParamsLanguageKey); | 405 kBannerParamsKey, kBannerParamsLanguageKey); |
| 683 unsigned int language_option = 0; | 406 unsigned int language_option = 0; |
| 684 | 407 |
| 685 if (param.empty() || !base::StringToUint(param, &language_option) || | 408 if (param.empty() || !base::StringToUint(param, &language_option) || |
| 686 language_option < LANGUAGE_OPTION_MIN || | 409 language_option < LANGUAGE_OPTION_MIN || |
| 687 language_option > LANGUAGE_OPTION_MAX) { | 410 language_option > LANGUAGE_OPTION_MAX) { |
| 688 return LANGUAGE_OPTION_DEFAULT; | 411 return LANGUAGE_OPTION_DEFAULT; |
| 689 } | 412 } |
| 690 | 413 |
| 691 return static_cast<LanguageOption>(language_option); | 414 return static_cast<LanguageOption>(language_option); |
| 692 } | 415 } |
| 693 | |
| 694 bool AppBannerSettingsHelper::ShouldUseSiteEngagementScore() { | |
| 695 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| 696 switches::kEnableSiteEngagementAppBanner)) { | |
| 697 return true; | |
| 698 } | |
| 699 | |
| 700 // Assume any value which is not "0" or "false" indicates that we should use | |
| 701 // site engagement. | |
| 702 std::string param = variations::GetVariationParamValue( | |
| 703 kBannerParamsKey, kBannerSiteEngagementParamsKey); | |
| 704 | |
| 705 return (!param.empty() && param != "0" && param != "false"); | |
| 706 } | |
| OLD | NEW |