Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: chrome/browser/extensions/apps_promo.cc

Issue 7820003: Add support to download web store promo logos over https. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix sync failures Created 9 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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/extensions/apps_promo.h" 5 #include "chrome/browser/extensions/apps_promo.h"
6 6
7 #include "base/base64.h"
7 #include "base/command_line.h" 8 #include "base/command_line.h"
8 #include "base/metrics/histogram.h" 9 #include "base/metrics/histogram.h"
9 #include "chrome/browser/browser_process.h" 10 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/prefs/pref_service.h" 11 #include "chrome/browser/prefs/pref_service.h"
12 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/ui/webui/ntp/shown_sections_handler.h" 13 #include "chrome/browser/ui/webui/ntp/shown_sections_handler.h"
12 #include "chrome/common/chrome_switches.h" 14 #include "chrome/common/chrome_switches.h"
13 #include "chrome/common/extensions/extension.h" 15 #include "chrome/common/extensions/extension.h"
16 #include "chrome/common/chrome_notification_types.h"
14 #include "chrome/common/pref_names.h" 17 #include "chrome/common/pref_names.h"
18 #include "content/common/url_constants.h"
19 #include "content/common/notification_service.h"
20 #include "net/base/load_flags.h"
21 #include "net/url_request/url_request_status.h"
15 22
16 const int AppsPromo::kDefaultAppsCounterMax = 10; 23 const int AppsPromo::kDefaultAppsCounterMax = 10;
17 24
18 namespace { 25 namespace {
19 26
20 // The default logo for the promo. 27 // The default logo for the promo.
21 const char kDefaultPromoLogo[] = "chrome://theme/IDR_WEBSTORE_ICON"; 28 const char kDefaultLogo[] = "chrome://theme/IDR_WEBSTORE_ICON";
29
30 // The default promo data (this is only used for testing with
31 // --force-apps-promo-visible).
32 const char kDefaultHeader[] = "Browse thousands of apps and games for Chrome";
33 const char kDefaultButton[] = "Visit the Chrome Web Store";
34 const char kDefaultExpire[] = "No thanks";
35 const char kDefaultLink[] = "https://chrome.google.com/webstore";
36
37 // Http success status code.
38 const int kHttpSuccess = 200;
39
40 // The match pattern for valid logo URLs.
41 const char kValidLogoPattern[] = "https://*.google.com/*.png";
42
43 // The prefix for 'data' URL images.
44 const char kPNGDataURLPrefix[] = "data:image/png;base64,";
22 45
23 // Returns the string pref at |path|, using |fallback| as the default (if there 46 // Returns the string pref at |path|, using |fallback| as the default (if there
24 // is no pref value present). |fallback| is used for debugging in concert with 47 // is no pref value present). |fallback| is used for debugging in concert with
25 // --force-apps-promo-visible. 48 // --force-apps-promo-visible.
26 std::string GetStringPref(const char* path, const std::string& fallback) { 49 std::string GetStringPref(const char* path, const std::string& fallback) {
27 PrefService* local_state = g_browser_process->local_state(); 50 PrefService* local_state = g_browser_process->local_state();
28 std::string retval(local_state->GetString(path)); 51 std::string retval(local_state->GetString(path));
29 if (retval.empty() && CommandLine::ForCurrentProcess()->HasSwitch( 52 if (retval.empty() && CommandLine::ForCurrentProcess()->HasSwitch(
30 switches::kForceAppsPromoVisible)) { 53 switches::kForceAppsPromoVisible)) {
31 retval = fallback; 54 retval = fallback;
32 } 55 }
33 return retval; 56 return retval;
34 } 57 }
35 58
36 } // namespace 59 } // namespace
37 60
61 AppsPromo::PromoData::PromoData() {}
62 AppsPromo::PromoData::PromoData(const std::string& id,
63 const std::string& header,
64 const std::string& button,
65 const GURL& link,
66 const std::string& expire,
67 const GURL& logo,
68 const int user_group)
69 : id(id),
70 header(header),
71 button(button),
72 link(link),
73 expire(expire),
74 logo(logo),
75 user_group(user_group) {}
76
77 AppsPromo::PromoData::~PromoData() {}
78
38 // static 79 // static
39 void AppsPromo::RegisterPrefs(PrefService* local_state) { 80 void AppsPromo::RegisterPrefs(PrefService* local_state) {
40 std::string empty; 81 std::string empty;
41 local_state->RegisterBooleanPref(prefs::kNTPWebStoreEnabled, false); 82 local_state->RegisterBooleanPref(prefs::kNTPWebStoreEnabled, false);
42 local_state->RegisterStringPref(prefs::kNTPWebStorePromoId, empty); 83 local_state->RegisterStringPref(prefs::kNTPWebStorePromoId, empty);
43 local_state->RegisterStringPref(prefs::kNTPWebStorePromoHeader, empty); 84 local_state->RegisterStringPref(prefs::kNTPWebStorePromoHeader, empty);
44 local_state->RegisterStringPref(prefs::kNTPWebStorePromoButton, empty); 85 local_state->RegisterStringPref(prefs::kNTPWebStorePromoButton, empty);
45 local_state->RegisterStringPref(prefs::kNTPWebStorePromoLink, empty); 86 local_state->RegisterStringPref(prefs::kNTPWebStorePromoLink, empty);
46 local_state->RegisterStringPref(prefs::kNTPWebStorePromoLogo, empty); 87 local_state->RegisterStringPref(prefs::kNTPWebStorePromoLogo, empty);
88 local_state->RegisterStringPref(prefs::kNTPWebStorePromoLogoSource, empty);
47 local_state->RegisterStringPref(prefs::kNTPWebStorePromoExpire, empty); 89 local_state->RegisterStringPref(prefs::kNTPWebStorePromoExpire, empty);
48 local_state->RegisterIntegerPref(prefs::kNTPWebStorePromoUserGroup, 0); 90 local_state->RegisterIntegerPref(prefs::kNTPWebStorePromoUserGroup, 0);
49 } 91 }
50 92
51 // static 93 // static
52 void AppsPromo::RegisterUserPrefs(PrefService* prefs) { 94 void AppsPromo::RegisterUserPrefs(PrefService* prefs) {
53 // Set the default value for the counter to max+1 since we don't install 95 // Set the default value for the counter to max+1 since we don't install
54 // default apps for new users. 96 // default apps for new users.
55 prefs->RegisterIntegerPref(prefs::kAppsPromoCounter, 97 prefs->RegisterIntegerPref(prefs::kAppsPromoCounter,
56 kDefaultAppsCounterMax + 1, 98 kDefaultAppsCounterMax + 1,
57 PrefService::UNSYNCABLE_PREF); 99 PrefService::UNSYNCABLE_PREF);
58 prefs->RegisterBooleanPref(prefs::kDefaultAppsInstalled, 100 prefs->RegisterBooleanPref(prefs::kDefaultAppsInstalled,
59 false, 101 false,
60 PrefService::UNSYNCABLE_PREF); 102 PrefService::UNSYNCABLE_PREF);
61 prefs->RegisterStringPref(prefs::kNTPWebStorePromoLastId, 103 prefs->RegisterStringPref(prefs::kNTPWebStorePromoLastId,
62 std::string(), 104 std::string(),
63 PrefService::UNSYNCABLE_PREF); 105 PrefService::UNSYNCABLE_PREF);
64 prefs->RegisterBooleanPref(prefs::kNTPHideWebStorePromo, 106 prefs->RegisterBooleanPref(prefs::kNTPHideWebStorePromo,
65 false, 107 false,
66 PrefService::UNSYNCABLE_PREF); 108 PrefService::UNSYNCABLE_PREF);
67 } 109 }
68 110
69 // static 111 // static
70 void AppsPromo::ClearPromo() {
71 PrefService* local_state = g_browser_process->local_state();
72 local_state->ClearPref(prefs::kNTPWebStoreEnabled);
73 local_state->ClearPref(prefs::kNTPWebStorePromoId);
74 local_state->ClearPref(prefs::kNTPWebStorePromoHeader);
75 local_state->ClearPref(prefs::kNTPWebStorePromoButton);
76 local_state->ClearPref(prefs::kNTPWebStorePromoLink);
77 local_state->ClearPref(prefs::kNTPWebStorePromoLogo);
78 local_state->ClearPref(prefs::kNTPWebStorePromoExpire);
79 local_state->ClearPref(prefs::kNTPWebStorePromoUserGroup);
80 }
81
82 // static
83 bool AppsPromo::IsPromoSupportedForLocale() { 112 bool AppsPromo::IsPromoSupportedForLocale() {
84 PrefService* local_state = g_browser_process->local_state(); 113 PrefService* local_state = g_browser_process->local_state();
85 // PromoResourceService will clear the promo data if the current locale is 114 // PromoResourceService will clear the promo data if the current locale is
86 // not supported. 115 // not supported.
87 return local_state->HasPrefPath(prefs::kNTPWebStorePromoId) && 116 return local_state->HasPrefPath(prefs::kNTPWebStorePromoId) &&
88 local_state->HasPrefPath(prefs::kNTPWebStorePromoHeader) && 117 local_state->HasPrefPath(prefs::kNTPWebStorePromoHeader) &&
89 local_state->HasPrefPath(prefs::kNTPWebStorePromoButton) && 118 local_state->HasPrefPath(prefs::kNTPWebStorePromoButton) &&
90 local_state->HasPrefPath(prefs::kNTPWebStorePromoLink) && 119 local_state->HasPrefPath(prefs::kNTPWebStorePromoLink) &&
91 local_state->HasPrefPath(prefs::kNTPWebStorePromoLogo) && 120 local_state->HasPrefPath(prefs::kNTPWebStorePromoLogo) &&
92 local_state->HasPrefPath(prefs::kNTPWebStorePromoExpire) && 121 local_state->HasPrefPath(prefs::kNTPWebStorePromoExpire) &&
93 local_state->HasPrefPath(prefs::kNTPWebStorePromoUserGroup); 122 local_state->HasPrefPath(prefs::kNTPWebStorePromoUserGroup);
94 } 123 }
95 124
96 // static 125 // static
97 bool AppsPromo::IsWebStoreSupportedForLocale() { 126 bool AppsPromo::IsWebStoreSupportedForLocale() {
98 PrefService* local_state = g_browser_process->local_state(); 127 PrefService* local_state = g_browser_process->local_state();
99 return local_state->GetBoolean(prefs::kNTPWebStoreEnabled); 128 return local_state->GetBoolean(prefs::kNTPWebStoreEnabled);
100 } 129 }
101 130
102 // static 131 // static
103 std::string AppsPromo::GetPromoButtonText() {
104 return GetStringPref(prefs::kNTPWebStorePromoButton, "Click here now");
105 }
106
107 // static
108 std::string AppsPromo::GetPromoId() {
109 return GetStringPref(prefs::kNTPWebStorePromoId, "");
110 }
111
112 // static
113 std::string AppsPromo::GetPromoHeaderText() {
114 return GetStringPref(prefs::kNTPWebStorePromoHeader, "Get great apps!");
115 }
116
117 // static
118 GURL AppsPromo::GetPromoLink() {
119 return GURL(GetStringPref(prefs::kNTPWebStorePromoLink,
120 "https://chrome.google.com/webstore"));
121 }
122
123 // static
124 GURL AppsPromo::GetPromoLogo() {
125 PrefService* local_state = g_browser_process->local_state();
126 GURL logo_url(local_state->GetString(prefs::kNTPWebStorePromoLogo));
127 if (logo_url.is_valid() && logo_url.SchemeIs("data"))
128 return logo_url;
129 return GURL(kDefaultPromoLogo);
130 }
131
132 // static
133 std::string AppsPromo::GetPromoExpireText() {
134 return GetStringPref(prefs::kNTPWebStorePromoExpire, "No thanks.");
135 }
136
137 // static
138 int AppsPromo::GetPromoUserGroup() {
139 PrefService* local_state = g_browser_process->local_state();
140 return local_state->GetInteger(prefs::kNTPWebStorePromoUserGroup);
141 }
142
143 // static
144 void AppsPromo::SetPromo(const std::string& id,
145 const std::string& header_text,
146 const std::string& button_text,
147 const GURL& link,
148 const std::string& expire_text,
149 const GURL& logo,
150 const int user_group) {
151 PrefService* local_state = g_browser_process->local_state();
152 local_state->SetString(prefs::kNTPWebStorePromoId, id);
153 local_state->SetString(prefs::kNTPWebStorePromoButton, button_text);
154 local_state->SetString(prefs::kNTPWebStorePromoHeader, header_text);
155 local_state->SetString(prefs::kNTPWebStorePromoLink, link.spec());
156 local_state->SetString(prefs::kNTPWebStorePromoLogo, logo.spec());
157 local_state->SetString(prefs::kNTPWebStorePromoExpire, expire_text);
158 local_state->SetInteger(prefs::kNTPWebStorePromoUserGroup, user_group);
159 }
160
161 // static
162 void AppsPromo::SetWebStoreSupportedForLocale(bool supported) { 132 void AppsPromo::SetWebStoreSupportedForLocale(bool supported) {
163 PrefService* local_state = g_browser_process->local_state(); 133 PrefService* local_state = g_browser_process->local_state();
164 local_state->SetBoolean(prefs::kNTPWebStoreEnabled, supported); 134 local_state->SetBoolean(prefs::kNTPWebStoreEnabled, supported);
165 } 135 }
166 136
137 // static
138 void AppsPromo::ClearPromo() {
139 PrefService* local_state = g_browser_process->local_state();
140 local_state->ClearPref(prefs::kNTPWebStoreEnabled);
141 local_state->ClearPref(prefs::kNTPWebStorePromoId);
142 local_state->ClearPref(prefs::kNTPWebStorePromoHeader);
143 local_state->ClearPref(prefs::kNTPWebStorePromoButton);
144 local_state->ClearPref(prefs::kNTPWebStorePromoLink);
145 local_state->ClearPref(prefs::kNTPWebStorePromoLogo);
146 local_state->ClearPref(prefs::kNTPWebStorePromoLogoSource);
147 local_state->ClearPref(prefs::kNTPWebStorePromoExpire);
148 local_state->ClearPref(prefs::kNTPWebStorePromoUserGroup);
149 }
150
151 // static
152 AppsPromo::PromoData AppsPromo::GetPromo() {
153 PromoData data;
154 PrefService* local_state = g_browser_process->local_state();
155
156 data.id = GetStringPref(prefs::kNTPWebStorePromoId, "");
157 data.link = GURL(GetStringPref(prefs::kNTPWebStorePromoLink, kDefaultLink));
158 data.user_group = local_state->GetInteger(prefs::kNTPWebStorePromoUserGroup);
159 data.header = GetStringPref(prefs::kNTPWebStorePromoHeader, kDefaultHeader);
160 data.button = GetStringPref(prefs::kNTPWebStorePromoButton, kDefaultButton);
161 data.expire = GetStringPref(prefs::kNTPWebStorePromoExpire, kDefaultExpire);
162
163 GURL logo_url(local_state->GetString(prefs::kNTPWebStorePromoLogo));
164 if (logo_url.is_valid() && logo_url.SchemeIs(chrome::kDataScheme))
165 data.logo = logo_url;
166 else
167 data.logo = GURL(kDefaultLogo);
168
169 return data;
170 }
171
172 // static
173 void AppsPromo::SetPromo(AppsPromo::PromoData data) {
174 PrefService* local_state = g_browser_process->local_state();
175 local_state->SetString(prefs::kNTPWebStorePromoId, data.id);
176 local_state->SetString(prefs::kNTPWebStorePromoButton, data.button);
177 local_state->SetString(prefs::kNTPWebStorePromoHeader, data.header);
178 local_state->SetString(prefs::kNTPWebStorePromoLink, data.link.spec());
179 local_state->SetString(prefs::kNTPWebStorePromoLogo, data.logo.spec());
180 local_state->SetString(prefs::kNTPWebStorePromoExpire, data.expire);
181 local_state->SetInteger(prefs::kNTPWebStorePromoUserGroup, data.user_group);
182 }
183
184 // static
185 GURL AppsPromo::GetSourcePromoLogoURL() {
186 return GURL(GetStringPref(prefs::kNTPWebStorePromoLogoSource, ""));
187 }
188
189 // static
190 void AppsPromo::SetSourcePromoLogoURL(GURL logo_source) {
191 PrefService* local_state = g_browser_process->local_state();
192 local_state->SetString(prefs::kNTPWebStorePromoLogoSource,
193 logo_source.spec());
194 }
195
167 AppsPromo::AppsPromo(PrefService* prefs) 196 AppsPromo::AppsPromo(PrefService* prefs)
168 : prefs_(prefs) { 197 : prefs_(prefs) {
169 // Poppit, Entanglement 198 // Poppit, Entanglement
170 old_default_app_ids_.insert("mcbkbpnkkkipelfledbfocopglifcfmi"); 199 old_default_app_ids_.insert("mcbkbpnkkkipelfledbfocopglifcfmi");
171 old_default_app_ids_.insert("aciahcmjmecflokailenpkdchphgkefd"); 200 old_default_app_ids_.insert("aciahcmjmecflokailenpkdchphgkefd");
172 } 201 }
173 202
174 AppsPromo::~AppsPromo() {} 203 AppsPromo::~AppsPromo() {}
175 204
176 bool AppsPromo::ShouldShowPromo(const ExtensionIdSet& installed_ids, 205 bool AppsPromo::ShouldShowPromo(const ExtensionIdSet& installed_ids,
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
239 // current locale. 268 // current locale.
240 return IsWebStoreSupportedForLocale(); 269 return IsWebStoreSupportedForLocale();
241 #endif 270 #endif
242 } 271 }
243 272
244 void AppsPromo::ExpireDefaultApps() { 273 void AppsPromo::ExpireDefaultApps() {
245 SetPromoCounter(kDefaultAppsCounterMax + 1); 274 SetPromoCounter(kDefaultAppsCounterMax + 1);
246 } 275 }
247 276
248 void AppsPromo::MaximizeAppsIfNecessary() { 277 void AppsPromo::MaximizeAppsIfNecessary() {
249 std::string promo_id = GetPromoId(); 278 PromoData promo = GetPromo();
250 int maximize_setting = GetPromoUserGroup();
251 279
252 // Maximize the apps section of the NTP if this is the first time viewing the 280 // Maximize the apps section of the NTP if this is the first time viewing the
253 // specific promo and the current user group is targetted. 281 // specific promo and the current user group is targetted.
254 if (GetLastPromoId() != promo_id) { 282 if (GetLastPromoId() != promo.id) {
255 if ((maximize_setting & GetCurrentUserGroup()) != 0) 283 if ((promo.user_group & GetCurrentUserGroup()) != 0)
256 ShownSectionsHandler::SetShownSection(prefs_, APPS); 284 ShownSectionsHandler::SetShownSection(prefs_, APPS);
257 SetLastPromoId(promo_id); 285 SetLastPromoId(promo.id);
258 } 286 }
259 } 287 }
260 288
261 void AppsPromo::HidePromo() { 289 void AppsPromo::HidePromo() {
262 UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram, 290 UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
263 extension_misc::PROMO_CLOSE, 291 extension_misc::PROMO_CLOSE,
264 extension_misc::PROMO_BUCKET_BOUNDARY); 292 extension_misc::PROMO_BUCKET_BOUNDARY);
265 293
266 // Put the apps section into menu mode, and maximize the recent section. 294 // Put the apps section into menu mode, and maximize the recent section.
267 ShownSectionsHandler::SetShownSection(prefs_, MENU_APPS); 295 ShownSectionsHandler::SetShownSection(prefs_, MENU_APPS);
(...skipping 22 matching lines...) Expand all
290 bool AppsPromo::GetDefaultAppsInstalled() const { 318 bool AppsPromo::GetDefaultAppsInstalled() const {
291 return prefs_->GetBoolean(prefs::kDefaultAppsInstalled); 319 return prefs_->GetBoolean(prefs::kDefaultAppsInstalled);
292 } 320 }
293 321
294 AppsPromo::UserGroup AppsPromo::GetCurrentUserGroup() const { 322 AppsPromo::UserGroup AppsPromo::GetCurrentUserGroup() const {
295 const PrefService::Preference* last_promo_id 323 const PrefService::Preference* last_promo_id
296 = prefs_->FindPreference(prefs::kNTPWebStorePromoLastId); 324 = prefs_->FindPreference(prefs::kNTPWebStorePromoLastId);
297 CHECK(last_promo_id); 325 CHECK(last_promo_id);
298 return last_promo_id->IsDefaultValue() ? USERS_NEW : USERS_EXISTING; 326 return last_promo_id->IsDefaultValue() ? USERS_NEW : USERS_EXISTING;
299 } 327 }
328
329 AppsPromoLogoFetcher::AppsPromoLogoFetcher(
330 Profile* profile,
331 AppsPromo::PromoData promo_data)
332 : profile_(profile),
333 promo_data_(promo_data) {
334 if (SupportsLogoURL()) {
335 if (HaveCachedLogo()) {
336 promo_data_.logo = AppsPromo::GetPromo().logo;
337 SavePromo();
338 } else {
339 FetchLogo();
340 }
341 } else {
342 // We only care about the source URL when this fetches the logo.
343 AppsPromo::SetSourcePromoLogoURL(GURL());
344 SavePromo();
345 }
346 }
347
348 AppsPromoLogoFetcher::~AppsPromoLogoFetcher() {}
349
350 void AppsPromoLogoFetcher::OnURLFetchComplete(const URLFetcher* source) {
351 std::string data;
352 std::string base64_data;
353
354 CHECK(source == url_fetcher_.get());
355 CHECK(source->GetResponseAsString(&data));
356
357 if (source->status().is_success() &&
358 source->response_code() == kHttpSuccess &&
359 base::Base64Encode(data, &base64_data)) {
360 AppsPromo::SetSourcePromoLogoURL(promo_data_.logo);
361 promo_data_.logo = GURL(kPNGDataURLPrefix + base64_data);
362 } else {
363 // The logo wasn't downloaded correctly or we failed to encode it in
364 // base64. Reset the source URL so we fetch it again next time. AppsPromo
365 // will revert to the default logo.
366 AppsPromo::SetSourcePromoLogoURL(GURL());
367 }
368
369 SavePromo();
370 }
371
372 void AppsPromoLogoFetcher::FetchLogo() {
373 CHECK(promo_data_.logo.scheme() == chrome::kHttpsScheme);
374
375 url_fetcher_.reset(URLFetcher::Create(
376 0, promo_data_.logo, URLFetcher::GET, this));
377 url_fetcher_->set_request_context(
378 g_browser_process->system_request_context());
379 url_fetcher_->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES |
380 net::LOAD_DO_NOT_SAVE_COOKIES);
381 url_fetcher_->Start();
382 }
383
384 bool AppsPromoLogoFetcher::HaveCachedLogo() {
385 return promo_data_.logo == AppsPromo::GetSourcePromoLogoURL();
386 }
387
388 void AppsPromoLogoFetcher::SavePromo() {
389 AppsPromo::SetPromo(promo_data_);
390
391 NotificationService::current()->Notify(
392 chrome::NOTIFICATION_WEB_STORE_PROMO_LOADED,
393 Source<Profile>(profile_),
394 NotificationService::NoDetails());
395 }
396
397 bool AppsPromoLogoFetcher::SupportsLogoURL() {
398 URLPattern allowed_urls(URLPattern::SCHEME_HTTPS, kValidLogoPattern);
399 return allowed_urls.MatchesURL(promo_data_.logo);
400 }
OLDNEW
« no previous file with comments | « chrome/browser/extensions/apps_promo.h ('k') | chrome/browser/extensions/apps_promo_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698