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

Side by Side Diff: components/ntp_snippets/ntp_snippets_fetcher.cc

Issue 1922083004: Allow fetching personalized snippets from ChromeReader. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Minor fixes Created 4 years, 7 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
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 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 "components/ntp_snippets/ntp_snippets_fetcher.h" 5 #include "components/ntp_snippets/ntp_snippets_fetcher.h"
6 6
7 #include <stdlib.h>
8
7 #include "base/command_line.h" 9 #include "base/command_line.h"
8 #include "base/files/file_path.h" 10 #include "base/files/file_path.h"
9 #include "base/files/file_util.h" 11 #include "base/files/file_util.h"
10 #include "base/metrics/sparse_histogram.h" 12 #include "base/metrics/sparse_histogram.h"
11 #include "base/path_service.h" 13 #include "base/path_service.h"
12 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h" 15 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h" 16 #include "base/strings/stringprintf.h"
15 #include "base/values.h" 17 #include "base/values.h"
16 #include "components/data_use_measurement/core/data_use_user_data.h" 18 #include "components/data_use_measurement/core/data_use_user_data.h"
19 #include "components/ntp_snippets/ntp_snippets_constants.h"
17 #include "components/ntp_snippets/switches.h" 20 #include "components/ntp_snippets/switches.h"
21 #include "components/signin/core/browser/profile_oauth2_token_service.h"
22 #include "components/signin/core/browser/signin_manager.h"
23 #include "components/signin/core/browser/signin_manager_base.h"
24 #include "components/signin/core/browser/signin_tracker.h"
Marc Treib 2016/05/09 12:33:33 Is this needed?
jkrcal 2016/05/09 15:30:12 Done.
25 #include "components/variations/variations_associated_data.h"
18 #include "google_apis/google_api_keys.h" 26 #include "google_apis/google_api_keys.h"
19 #include "net/base/load_flags.h" 27 #include "net/base/load_flags.h"
20 #include "net/http/http_request_headers.h" 28 #include "net/http/http_request_headers.h"
21 #include "net/http/http_response_headers.h" 29 #include "net/http/http_response_headers.h"
22 #include "net/http/http_status_code.h" 30 #include "net/http/http_status_code.h"
23 #include "net/url_request/url_fetcher.h" 31 #include "net/url_request/url_fetcher.h"
32 #include "third_party/icu/source/common/unicode/uloc.h"
33 #include "third_party/icu/source/common/unicode/utypes.h"
24 34
25 using net::URLFetcher; 35 using net::URLFetcher;
26 using net::URLRequestContextGetter; 36 using net::URLRequestContextGetter;
27 using net::HttpRequestHeaders; 37 using net::HttpRequestHeaders;
28 using net::URLRequestStatus; 38 using net::URLRequestStatus;
29 39
30 namespace ntp_snippets { 40 namespace ntp_snippets {
31 41
32 namespace { 42 namespace {
33 43
34 const char kStatusMessageEmptyHosts[] = "Cannot fetch for empty hosts list."; 44 const char kStatusMessageEmptyHosts[] = "Cannot fetch for empty hosts list.";
35 const char kStatusMessageURLRequestErrorFormat[] = "URLRequestStatus error %d"; 45 const char kStatusMessageURLRequestErrorFormat[] = "URLRequestStatus error %d";
36 const char kStatusMessageHTTPErrorFormat[] = "HTTP error %d"; 46 const char kStatusMessageHTTPErrorFormat[] = "HTTP error %d";
37 const char kStatusMessageJsonErrorFormat[] = "Received invalid JSON (error %s)"; 47 const char kStatusMessageJsonErrorFormat[] = "Received invalid JSON (error %s)";
38 const char kStatusMessageInvalidList[] = "Invalid / empty list."; 48 const char kStatusMessageInvalidList[] = "Invalid / empty list.";
39 49
40 const char kContentSnippetsServerFormat[] = 50 const char kApiScope[] = "https://www.googleapis.com/auth/webhistory";
41 "https://chromereader-pa.googleapis.com/v1/fetch?key=%s"; 51 const char kSnippetsServerAuthorized[] =
Marc Treib 2016/05/09 12:33:33 Just kSnippetsServer, it's used for both cases.
jkrcal 2016/05/09 15:30:13 Done.
52 "https://chromereader-pa.googleapis.com/v1/fetch";
53 const char kSnippetsServerNonAuthorizedFormat[] =
54 "%s?key=%s";
Marc Treib 2016/05/09 12:33:33 Fits on the previous line
jkrcal 2016/05/09 15:30:13 Done.
55 const char kAuthorizationRequestHeaderFormat[] = "Bearer %s";
56
57 // Variation parameter for the variant of fetching to use.
58 const char kVariantName[] = "fetching_variant";
59
60 // Constants listing possible values of the "fetching_variant" parameter.
61 const char kVariantRestrictedString[] = "restricted";
62 const char kVariantPersonalizedString[] = "personalized";
63 const char kVariantRestrictedPersonalizedString[] = "restricted_personalized";
42 64
43 const char kRequestParameterFormat[] = 65 const char kRequestParameterFormat[] =
44 "{" 66 "{"
45 " \"response_detail_level\": \"STANDARD\"," 67 " \"response_detail_level\": \"STANDARD\","
68 "%s" // If authenticated - an obfuscated Gaia ID will be inserted here
46 " \"advanced_options\": {" 69 " \"advanced_options\": {"
47 " \"local_scoring_params\": {" 70 " \"local_scoring_params\": {"
48 " \"content_params\": {" 71 " \"content_params\": {"
49 " \"only_return_personalized_results\": false" 72 " \"only_return_personalized_results\": false"
73 "%s" // If authenticated - user segment (lang code) will be inserted here
50 " }," 74 " },"
51 " \"content_restricts\": {" 75 " \"content_restricts\": {"
52 " \"type\": \"METADATA\"," 76 " \"type\": \"METADATA\","
53 " \"value\": \"TITLE\"" 77 " \"value\": \"TITLE\""
54 " }," 78 " },"
55 " \"content_restricts\": {" 79 " \"content_restricts\": {"
56 " \"type\": \"METADATA\"," 80 " \"type\": \"METADATA\","
57 " \"value\": \"SNIPPET\"" 81 " \"value\": \"SNIPPET\""
58 " }," 82 " },"
59 " \"content_restricts\": {" 83 " \"content_restricts\": {"
60 " \"type\": \"METADATA\"," 84 " \"type\": \"METADATA\","
61 " \"value\": \"THUMBNAIL\"" 85 " \"value\": \"THUMBNAIL\""
62 " }" 86 " }"
63 "%s" 87 "%s" // If host restricted - host restrictions will be inserted here
64 " }," 88 " },"
65 " \"global_scoring_params\": {" 89 " \"global_scoring_params\": {"
66 " \"num_to_return\": %i" 90 " \"num_to_return\": %i,"
91 " \"sort_type\": 1"
67 " }" 92 " }"
68 " }" 93 " }"
69 "}"; 94 "}";
70 95
96 const char kAuthorizationFormat[] = " \"obfuscated_gaia_id\": \"%s\",";
Marc Treib 2016/05/09 12:33:33 This isn't really authorization... kGaiaIdFormat?
jkrcal 2016/05/09 15:30:12 Done.
97 const char kUserSegmentFormat[] = " \"user_segment\": \"%s\"";
71 const char kHostRestrictFormat[] = 98 const char kHostRestrictFormat[] =
72 " ,\"content_selectors\": {" 99 " ,\"content_selectors\": {"
73 " \"type\": \"HOST_RESTRICT\"," 100 " \"type\": \"HOST_RESTRICT\","
74 " \"value\": \"%s\"" 101 " \"value\": \"%s\""
75 " }"; 102 " }";
76 103
77 } // namespace 104 } // namespace
78 105
79 NTPSnippetsFetcher::NTPSnippetsFetcher( 106 NTPSnippetsFetcher::NTPSnippetsFetcher(
107 SigninManagerBase* signin_manager,
108 OAuth2TokenService* token_service,
80 scoped_refptr<URLRequestContextGetter> url_request_context_getter, 109 scoped_refptr<URLRequestContextGetter> url_request_context_getter,
81 const ParseJSONCallback& parse_json_callback, 110 const ParseJSONCallback& parse_json_callback,
82 bool is_stable_channel) 111 bool is_stable_channel)
83 : url_request_context_getter_(url_request_context_getter), 112 : OAuth2TokenService::Consumer("NTP_snippets"),
Marc Treib 2016/05/09 12:33:33 I'd make this "ntp_snippets", most of the other im
jkrcal 2016/05/09 15:30:13 Done.
113 signin_manager_(signin_manager),
114 token_service_(token_service),
115 waiting_for_refresh_token_(false),
116 url_request_context_getter_(url_request_context_getter),
84 parse_json_callback_(parse_json_callback), 117 parse_json_callback_(parse_json_callback),
85 is_stable_channel_(is_stable_channel), 118 is_stable_channel_(is_stable_channel),
86 weak_ptr_factory_(this) {} 119 weak_ptr_factory_(this) {
120 // Parse the variation parameters and set the defaults if missing.
121 std::string variant = variations::GetVariationParamValue(
122 ntp_snippets::kStudyName, kVariantName);
123 if (variant == kVariantRestrictedString) {
124 variant_ = kRestricted;
125 } else if (variant == kVariantPersonalizedString) {
126 variant_ = kPersonalized;
127 } else {
128 variant_ = kRestrictedPersonalized;
129 if (!variant.empty() && variant != kVariantRestrictedPersonalizedString)
130 DLOG(WARNING) << "Unknown fetching variant provided: " << variant;
Marc Treib 2016/05/09 12:33:33 I think this might be worth a non-D LOG. Bernhard,
jkrcal 2016/05/09 15:30:13 Done. (And happy to change it back if Bernhard is
Bernhard Bauer 2016/05/09 17:45:06 Nah, just make it LOG_IF if you're not doing anyth
jkrcal 2016/05/09 19:04:16 Done.
131 }
132 }
87 133
88 NTPSnippetsFetcher::~NTPSnippetsFetcher() {} 134 NTPSnippetsFetcher::~NTPSnippetsFetcher() {
135 if (waiting_for_refresh_token_)
136 token_service_->RemoveObserver(this);
137 }
89 138
90 void NTPSnippetsFetcher::SetCallback( 139 void NTPSnippetsFetcher::SetCallback(
91 const SnippetsAvailableCallback& callback) { 140 const SnippetsAvailableCallback& callback) {
92 snippets_available_callback_ = callback; 141 snippets_available_callback_ = callback;
93 } 142 }
94 143
95 void NTPSnippetsFetcher::FetchSnippetsFromHosts( 144 void NTPSnippetsFetcher::FetchSnippetsFromHosts(
96 const std::set<std::string>& hosts, int count) { 145 const std::set<std::string>& hosts,
97 std::string host_restricts; 146 const std::string& language_code,
147 int count) {
148 hosts_ = hosts;
149
98 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( 150 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
99 switches::kDontRestrict)) { 151 switches::kDontRestrict)) {
100 if (hosts.empty()) { 152 if (hosts_.empty()) {
101 if (!snippets_available_callback_.is_null()) { 153 if (!snippets_available_callback_.is_null()) {
102 snippets_available_callback_.Run(NTPSnippet::PtrVector(), 154 snippets_available_callback_.Run(NTPSnippet::PtrVector(),
103 kStatusMessageEmptyHosts); 155 kStatusMessageEmptyHosts);
104 } 156 }
105 return; 157 return;
106 } 158 }
107 for (const std::string& host : hosts)
108 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str());
109 } 159 }
110 const std::string& key = is_stable_channel_ 160
111 ? google_apis::GetAPIKey() 161 // Translate the BCP 47 |language_code| into a posix locale string
Marc Treib 2016/05/09 12:33:33 nit: Period after comment, also in a few other pla
jkrcal 2016/05/09 15:30:12 Done.
112 : google_apis::GetNonStableAPIKey(); 162 char locale[ULOC_FULLNAME_CAPACITY];
113 std::string url = 163 UErrorCode error;
114 base::StringPrintf(kContentSnippetsServerFormat, key.c_str()); 164 uloc_forLanguageTag(language_code.c_str(), locale, ULOC_FULLNAME_CAPACITY,
115 url_fetcher_ = URLFetcher::Create(GURL(url), URLFetcher::POST, this); 165 nullptr, &error);
166 DLOG_IF(WARNING, U_ZERO_ERROR != error) <<
167 "Error in translating language code to a locale string: " << error;
168 locale_ = locale;
169
170 count_ = count;
171
172 bool use_authentication = UseAuthentication();
173
174 if (use_authentication && signin_manager_->IsAuthenticated()) {
175 // Signed-in: get OAuth token --> fetch snippets.
176 StartTokenRequest();
177 } else if (use_authentication && signin_manager_->AuthInProgress()) {
178 // Currently signing in: wait for auth to finish (the refresh token) -->
179 // get OAuth token --> fetch snippets.
180 if (!waiting_for_refresh_token_) {
181 // Wait until we get a refresh token.
182 waiting_for_refresh_token_ = true;
183 token_service_->AddObserver(this);
184 }
185 } else {
186 // Not signed in: fetch snippets (without authentication).
187 FetchSnippetsNonAuthenticated();
188 }
189 }
190
191 void NTPSnippetsFetcher::FetchSnippetsImpl(const GURL& url,
192 const std::string& auth_header,
193 const std::string& request) {
194 url_fetcher_ = URLFetcher::Create(url, URLFetcher::POST, this);
195
116 url_fetcher_->SetRequestContext(url_request_context_getter_.get()); 196 url_fetcher_->SetRequestContext(url_request_context_getter_.get());
117 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 197 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
118 net::LOAD_DO_NOT_SAVE_COOKIES); 198 net::LOAD_DO_NOT_SAVE_COOKIES);
199
119 data_use_measurement::DataUseUserData::AttachToFetcher( 200 data_use_measurement::DataUseUserData::AttachToFetcher(
120 url_fetcher_.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS); 201 url_fetcher_.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS);
202
121 HttpRequestHeaders headers; 203 HttpRequestHeaders headers;
204 if (!auth_header.empty())
205 headers.SetHeader("Authorization", auth_header);
122 headers.SetHeader("Content-Type", "application/json; charset=UTF-8"); 206 headers.SetHeader("Content-Type", "application/json; charset=UTF-8");
123 url_fetcher_->SetExtraRequestHeaders(headers.ToString()); 207 url_fetcher_->SetExtraRequestHeaders(headers.ToString());
124 url_fetcher_->SetUploadData("application/json", 208 url_fetcher_->SetUploadData("application/json", request);
125 base::StringPrintf(kRequestParameterFormat,
126 host_restricts.c_str(),
127 count));
128
129 // Fetchers are sometimes cancelled because a network change was detected. 209 // Fetchers are sometimes cancelled because a network change was detected.
130 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); 210 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
131 // Try to make fetching the files bit more robust even with poor connection. 211 // Try to make fetching the files bit more robust even with poor connection.
132 url_fetcher_->SetMaxRetriesOn5xx(3); 212 url_fetcher_->SetMaxRetriesOn5xx(3);
133 url_fetcher_->Start(); 213 url_fetcher_->Start();
134 } 214 }
135 215
216 std::string NTPSnippetsFetcher::GetHostRestricts() const {
217 std::string host_restricts;
218 if (variant_ == kRestricted || variant_ == kRestrictedPersonalized) {
219 for (const std::string& host : hosts_)
220 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str());
221 }
222 return host_restricts;
223 }
224
225 bool NTPSnippetsFetcher::UseAuthentication() {
226 return (variant_ == kPersonalized ||
227 (variant_ == kRestrictedPersonalized && !hosts_.empty()));
Marc Treib 2016/05/09 12:33:33 Why are we checking hosts_ here?
jkrcal 2016/05/09 15:30:13 You are right, this is weird (especially w.r.t. c
228 }
229
230 void NTPSnippetsFetcher::FetchSnippetsNonAuthenticated() {
231 // When not providing OAuth token, we need to pass the Google API key
232 const std::string& key = is_stable_channel_
233 ? google_apis::GetAPIKey()
234 : google_apis::GetNonStableAPIKey();
235 GURL url(base::StringPrintf(kSnippetsServerNonAuthorizedFormat,
236 kSnippetsServerAuthorized,
237 key.c_str()));
238
239 FetchSnippetsImpl(url, std::string(),
240 base::StringPrintf(kRequestParameterFormat,
241 std::string().c_str(),
Marc Treib 2016/05/09 12:33:33 I think in this case, "" would be preferred.
jkrcal 2016/05/09 15:30:13 Okay :)
242 std::string().c_str(),
243 GetHostRestricts().c_str(),
244 count_));
245 }
246
247 void NTPSnippetsFetcher::FetchSnippetsAuthenticated(
248 const std::string& account_id,
249 const std::string& oauth_access_token) {
250 std::string auth =
251 base::StringPrintf(kAuthorizationFormat, account_id.c_str());
252 std::string user_segment =
253 base::StringPrintf(kUserSegmentFormat, locale_.c_str());
254
255 FetchSnippetsImpl(GURL(kSnippetsServerAuthorized),
256 base::StringPrintf(kAuthorizationRequestHeaderFormat,
257 oauth_access_token.c_str()),
258 base::StringPrintf(kRequestParameterFormat,
259 auth.c_str(),
260 user_segment.c_str(),
261 GetHostRestricts().c_str(),
262 count_));
263 }
264
265 void NTPSnippetsFetcher::StartTokenRequest() {
266 OAuth2TokenService::ScopeSet scopes;
267 scopes.insert(kApiScope);
268 oauth_request_ = token_service_->StartRequest(
269 signin_manager_->GetAuthenticatedAccountId(), scopes, this);
270 }
271
272 ////////////////////////////////////////////////////////////////////////////////
273 // OAuth2TokenService::Consumer overrides
274 void NTPSnippetsFetcher::OnGetTokenSuccess(
275 const OAuth2TokenService::Request* request,
276 const std::string& access_token,
277 const base::Time& expiration_time) {
278 oauth_request_.reset();
Marc Treib 2016/05/09 12:33:33 Mmh, this is actually the same as the |request| pa
jkrcal 2016/05/09 15:30:13 Done. (Thanks for pointing it out!)
279
280 FetchSnippetsAuthenticated(request->GetAccountId(), access_token);
281 }
282
283 void NTPSnippetsFetcher::OnGetTokenFailure(
284 const OAuth2TokenService::Request* request,
285 const GoogleServiceAuthError& error) {
286 oauth_request_.reset();
287 DLOG(ERROR) << "Unable to get token: " << error.ToString()
288 << " - fetching the snippets without authentication.";
289
290 // Fallback to fetching non-authenticated tokens.
291 FetchSnippetsNonAuthenticated();
292 }
293
294 ////////////////////////////////////////////////////////////////////////////////
295 // OAuth2TokenService::Observer overrides
296 void NTPSnippetsFetcher::OnRefreshTokenAvailable(
297 const std::string& account_id) {
Marc Treib 2016/05/09 12:33:33 Misaligned, but should fit on the previous line? A
jkrcal 2016/05/09 15:30:13 Thanks for pointing out the misalignment. (No, it
Marc Treib 2016/05/09 16:48:24 Can we only ever have one refresh token? I think w
Bernhard Bauer 2016/05/09 17:45:07 Yeah, I'm pretty certain this can happen.
jkrcal 2016/05/09 19:04:16 Now I get your point. I was not aware this listens
298 token_service_->RemoveObserver(this);
299 waiting_for_refresh_token_ = false;
300 StartTokenRequest();
301 }
302
136 //////////////////////////////////////////////////////////////////////////////// 303 ////////////////////////////////////////////////////////////////////////////////
137 // URLFetcherDelegate overrides 304 // URLFetcherDelegate overrides
138 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) { 305 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) {
139 DCHECK_EQ(url_fetcher_.get(), source); 306 DCHECK_EQ(url_fetcher_.get(), source);
140 307
141 std::string message; 308 std::string message;
142 const URLRequestStatus& status = source->GetStatus(); 309 const URLRequestStatus& status = source->GetStatus();
143 310
144 UMA_HISTOGRAM_SPARSE_SLOWLY( 311 UMA_HISTOGRAM_SPARSE_SLOWLY(
145 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode", 312 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode",
146 status.is_success() ? source->GetResponseCode() : status.error()); 313 status.is_success() ? source->GetResponseCode() : status.error());
147 314
148 if (!status.is_success()) { 315 if (!status.is_success()) {
149 message = base::StringPrintf(kStatusMessageURLRequestErrorFormat, 316 message = base::StringPrintf(kStatusMessageURLRequestErrorFormat,
150 status.error()); 317 status.error());
151 } else if (source->GetResponseCode() != net::HTTP_OK) { 318 } else if (source->GetResponseCode() != net::HTTP_OK) {
319 // TODO(jkrcal): https://crbug.com/609084
320 // We need to deal with the edge case again where the auth
321 // token expires just before we send the request (in which case we need to
322 // fetch a new auth token). We should extract that into a common class
323 // instead of adding it to every single class that uses auth tokens.
152 message = base::StringPrintf(kStatusMessageHTTPErrorFormat, 324 message = base::StringPrintf(kStatusMessageHTTPErrorFormat,
153 source->GetResponseCode()); 325 source->GetResponseCode());
154 } 326 }
155 327
156 if (!message.empty()) { 328 if (!message.empty()) {
329 std::string error_response;
330 source->GetResponseAsString(&error_response);
157 DLOG(WARNING) << message << " while trying to download " 331 DLOG(WARNING) << message << " while trying to download "
158 << source->GetURL().spec(); 332 << source->GetURL().spec() << ": " << error_response;
159 if (!snippets_available_callback_.is_null()) 333 if (!snippets_available_callback_.is_null())
160 snippets_available_callback_.Run(NTPSnippet::PtrVector(), message); 334 snippets_available_callback_.Run(NTPSnippet::PtrVector(), message);
161 } else { 335 } else {
162 bool stores_result_to_string = source->GetResponseAsString( 336 bool stores_result_to_string = source->GetResponseAsString(
163 &last_fetch_json_); 337 &last_fetch_json_);
164 DCHECK(stores_result_to_string); 338 DCHECK(stores_result_to_string);
165 339
166 parse_json_callback_.Run( 340 parse_json_callback_.Run(
167 last_fetch_json_, 341 last_fetch_json_,
168 base::Bind(&NTPSnippetsFetcher::OnJsonParsed, 342 base::Bind(&NTPSnippetsFetcher::OnJsonParsed,
(...skipping 25 matching lines...) Expand all
194 LOG(WARNING) << "Received invalid JSON (" << error << "): " 368 LOG(WARNING) << "Received invalid JSON (" << error << "): "
195 << last_fetch_json_; 369 << last_fetch_json_;
196 if (!snippets_available_callback_.is_null()) { 370 if (!snippets_available_callback_.is_null()) {
197 snippets_available_callback_.Run( 371 snippets_available_callback_.Run(
198 NTPSnippet::PtrVector(), 372 NTPSnippet::PtrVector(),
199 base::StringPrintf(kStatusMessageJsonErrorFormat, error.c_str())); 373 base::StringPrintf(kStatusMessageJsonErrorFormat, error.c_str()));
200 } 374 }
201 } 375 }
202 376
203 } // namespace ntp_snippets 377 } // namespace ntp_snippets
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698