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

Side by Side Diff: components/ntp_snippets/remote/ntp_snippets_fetcher.h

Issue 2578173002: NTP: Extract JSON requests from Fetcher. (Closed)
Patch Set: No outside accesses to ntp_snippets::internal::* Created 4 years 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 #ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_FETCHER_H_ 5 #ifndef COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_FETCHER_H_
6 #define COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_FETCHER_H_ 6 #define COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_FETCHER_H_
7 7
8 #include <memory> 8 #include <memory>
9 #include <queue> 9 #include <queue>
10 #include <set>
11 #include <string> 10 #include <string>
12 #include <utility> 11 #include <utility>
13 #include <vector> 12 #include <vector>
14 13
15 #include "base/callback.h" 14 #include "base/callback.h"
16 #include "base/memory/weak_ptr.h" 15 #include "base/memory/weak_ptr.h"
17 #include "base/optional.h" 16 #include "base/optional.h"
18 #include "base/time/tick_clock.h" 17 #include "base/time/tick_clock.h"
19 #include "base/time/time.h"
20 #include "components/ntp_snippets/category.h" 18 #include "components/ntp_snippets/category.h"
21 #include "components/ntp_snippets/category_info.h" 19 #include "components/ntp_snippets/category_info.h"
22 #include "components/ntp_snippets/remote/ntp_snippet.h" 20 #include "components/ntp_snippets/remote/ntp_snippet.h"
21 #include "components/ntp_snippets/remote/ntp_snippets_json_request.h"
22 #include "components/ntp_snippets/remote/ntp_snippets_request_params.h"
23 #include "components/ntp_snippets/remote/request_throttler.h" 23 #include "components/ntp_snippets/remote/request_throttler.h"
24 #include "components/ntp_snippets/status.h" 24 #include "components/ntp_snippets/status.h"
25 #include "components/translate/core/browser/language_model.h" 25 #include "components/translate/core/browser/language_model.h"
26 #include "google_apis/gaia/oauth2_token_service.h"
27 #include "net/http/http_request_headers.h"
28 #include "net/url_request/url_request_context_getter.h" 26 #include "net/url_request/url_request_context_getter.h"
29 27
30 class PrefService; 28 class PrefService;
31 class SigninManagerBase; 29 class SigninManagerBase;
32 30
33 namespace base { 31 namespace base {
34 class Value; 32 class Value;
35 } // namespace base 33 } // namespace base
36 34
37 namespace ntp_snippets { 35 namespace ntp_snippets {
(...skipping 10 matching lines...) Expand all
48 // Provides the CategoryInfo data for article suggestions. If |title| is 46 // Provides the CategoryInfo data for article suggestions. If |title| is
49 // nullopt, then the default, hard-coded title will be used. 47 // nullopt, then the default, hard-coded title will be used.
50 CategoryInfo BuildArticleCategoryInfo( 48 CategoryInfo BuildArticleCategoryInfo(
51 const base::Optional<base::string16>& title); 49 const base::Optional<base::string16>& title);
52 50
53 // Provides the CategoryInfo data for other remote suggestions. 51 // Provides the CategoryInfo data for other remote suggestions.
54 CategoryInfo BuildRemoteCategoryInfo(const base::string16& title, 52 CategoryInfo BuildRemoteCategoryInfo(const base::string16& title,
55 bool allow_fetching_more_results); 53 bool allow_fetching_more_results);
56 54
57 // Fetches snippet data for the NTP from the server. 55 // Fetches snippet data for the NTP from the server.
56 // TODO(fhorschig): Untangle cyclic dependencies by introducing a
57 // NTPSnippetsFetcherInterface. (Would be good for mock implementations, too)
58 class NTPSnippetsFetcher : public OAuth2TokenService::Consumer, 58 class NTPSnippetsFetcher : public OAuth2TokenService::Consumer,
59 public OAuth2TokenService::Observer { 59 public OAuth2TokenService::Observer {
60 public: 60 public:
61 // Callbacks for JSON parsing, needed because the parsing is platform-
62 // dependent.
63 using SuccessCallback =
64 base::Callback<void(std::unique_ptr<base::Value> result)>;
65 using ErrorCallback = base::Callback<void(const std::string& error)>;
66 using ParseJSONCallback =
67 base::Callback<void(const std::string& raw_json_string,
68 const SuccessCallback& success_callback,
69 const ErrorCallback& error_callback)>;
70
71 struct FetchedCategory { 61 struct FetchedCategory {
72 Category category; 62 Category category;
73 CategoryInfo info; 63 CategoryInfo info;
74 NTPSnippet::PtrVector snippets; 64 NTPSnippet::PtrVector snippets;
75 65
76 FetchedCategory(Category c, CategoryInfo&& info); 66 FetchedCategory(Category c, CategoryInfo&& info);
77 FetchedCategory(FetchedCategory&&); // = default, in .cc 67 FetchedCategory(FetchedCategory&&); // = default, in .cc
78 ~FetchedCategory(); // = default, in .cc 68 ~FetchedCategory(); // = default, in .cc
79 FetchedCategory& operator=(FetchedCategory&&); // = default, in .cc 69 FetchedCategory& operator=(FetchedCategory&&); // = default, in .cc
80 }; 70 };
81 using FetchedCategoriesVector = std::vector<FetchedCategory>; 71 using FetchedCategoriesVector = std::vector<FetchedCategory>;
82 using OptionalFetchedCategories = base::Optional<FetchedCategoriesVector>; 72 using OptionalFetchedCategories = base::Optional<FetchedCategoriesVector>;
83 73
84 // Enumeration listing all possible outcomes for fetch attempts. Used for UMA
85 // histograms, so do not change existing values. Insert new values at the end,
86 // and update the histogram definition.
87 enum class FetchResult {
88 SUCCESS,
89 DEPRECATED_EMPTY_HOSTS,
90 URL_REQUEST_STATUS_ERROR,
91 HTTP_ERROR,
92 JSON_PARSE_ERROR,
93 INVALID_SNIPPET_CONTENT_ERROR,
94 OAUTH_TOKEN_ERROR,
95 INTERACTIVE_QUOTA_ERROR,
96 NON_INTERACTIVE_QUOTA_ERROR,
97 RESULT_MAX
98 };
99
100 // |snippets| contains parsed snippets if a fetch succeeded. If problems 74 // |snippets| contains parsed snippets if a fetch succeeded. If problems
101 // occur, |snippets| contains no value (no actual vector in base::Optional). 75 // occur, |snippets| contains no value (no actual vector in base::Optional).
102 // Error details can be retrieved using last_status(). 76 // Error details can be retrieved using last_status().
103 using SnippetsAvailableCallback = 77 using SnippetsAvailableCallback =
104 base::OnceCallback<void(Status status, 78 base::OnceCallback<void(Status status,
105 OptionalFetchedCategories fetched_categories)>; 79 OptionalFetchedCategories fetched_categories)>;
106 80
107 // Enumeration listing all possible variants of dealing with personalization.
108 enum class Personalization { kPersonal, kNonPersonal, kBoth };
109
110 // Contains all the parameters for one fetch.
111 struct Params {
112 Params();
113 Params(const Params&);
114 ~Params();
115
116 // BCP 47 language code specifying the user's UI language.
117 std::string language_code;
118
119 // A set of suggestion IDs that should not be returned again.
120 std::set<std::string> excluded_ids;
121
122 // Maximum number of snippets to fetch.
123 int count_to_fetch = 0;
124
125 // Whether this is an interactive request, i.e. triggered by an explicit
126 // user action. Typically, non-interactive requests are subject to a daily
127 // quota.
128 bool interactive_request = false;
129
130 // If set, only return results for this category.
131 base::Optional<Category> exclusive_category;
132 };
133
134 NTPSnippetsFetcher( 81 NTPSnippetsFetcher(
135 SigninManagerBase* signin_manager, 82 SigninManagerBase* signin_manager,
136 OAuth2TokenService* token_service, 83 OAuth2TokenService* token_service,
137 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter, 84 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
138 PrefService* pref_service, 85 PrefService* pref_service,
139 CategoryFactory* category_factory, 86 CategoryFactory* category_factory,
140 translate::LanguageModel* language_model, 87 translate::LanguageModel* language_model,
141 const ParseJSONCallback& parse_json_callback, 88 const ParseJSONCallback& parse_json_callback,
142 const std::string& api_key, 89 const std::string& api_key,
143 const UserClassifier* user_classifier); 90 const UserClassifier* user_classifier);
144 ~NTPSnippetsFetcher() override; 91 ~NTPSnippetsFetcher() override;
145 92
146 // Initiates a fetch from the server. When done (successfully or not), calls 93 // Initiates a fetch from the server. When done (successfully or not), calls
147 // the subscriber of SetCallback(). 94 // the callback.
148 // 95 //
149 // If an ongoing fetch exists, it will be silently abandoned and a new one 96 // If an ongoing fetch exists, both fetches won't influence each other (i.e.
150 // started, without triggering an additional callback (i.e. the callback will 97 // every callback will be called exactly once).
151 // only be called once). 98 void FetchSnippets(const NTPSnippetsRequestParams& params,
152 void FetchSnippets(const Params& params, SnippetsAvailableCallback callback); 99 SnippetsAvailableCallback callback);
100
101 std::string PersonalizationModeString() const;
153 102
154 // Debug string representing the status/result of the last fetch attempt. 103 // Debug string representing the status/result of the last fetch attempt.
155 const std::string& last_status() const { return last_status_; } 104 const std::string& last_status() const { return last_status_; }
156 105
157 // Returns the last JSON fetched from the server. 106 // Returns the last JSON fetched from the server.
158 const std::string& last_json() const { 107 const std::string& last_json() const {
159 return last_fetch_json_; 108 return last_fetch_json_;
160 } 109 }
161 110
162 // Returns the personalization setting of the fetcher. 111 // Returns the personalization setting of the fetcher as used in tests.
163 Personalization personalization() const { return personalization_; } 112 Personalization personalization() const { return personalization_; }
tschumann 2016/12/20 10:22:26 Lets' add a TODO() to reconsider the tests -- they
fhorschig 2016/12/20 10:30:32 Added TODO here, too. (I added a TODO to the tests
164 113
165 // Returns the URL endpoint used by the fetcher. 114 // Returns the URL endpoint used by the fetcher.
166 const GURL& fetch_url() const { return fetch_url_; } 115 const GURL& fetch_url() const { return fetch_url_; }
167 116
168 // Overrides internal clock for testing purposes. 117 // Overrides internal clock for testing purposes.
169 void SetTickClockForTesting(std::unique_ptr<base::TickClock> tick_clock) { 118 void SetTickClockForTesting(std::unique_ptr<base::TickClock> tick_clock) {
170 tick_clock_ = std::move(tick_clock); 119 tick_clock_ = std::move(tick_clock);
171 } 120 }
172 121
173 private: 122 private:
174 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest, 123 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest,
175 BuildRequestAuthenticated); 124 BuildRequestAuthenticated);
176 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest, 125 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest,
177 BuildRequestUnauthenticated); 126 BuildRequestUnauthenticated);
178 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest, 127 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest,
179 BuildRequestExcludedIds); 128 BuildRequestExcludedIds);
180 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest, 129 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest,
181 BuildRequestNoUserClass); 130 BuildRequestNoUserClass);
182 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest, 131 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest,
183 BuildRequestWithTwoLanguages); 132 BuildRequestWithTwoLanguages);
184 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest, 133 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest,
185 BuildRequestWithUILanguageOnly); 134 BuildRequestWithUILanguageOnly);
186 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest, 135 FRIEND_TEST_ALL_PREFIXES(ChromeReaderSnippetsFetcherTest,
187 BuildRequestWithOtherLanguageOnly); 136 BuildRequestWithOtherLanguageOnly);
188 friend class ChromeReaderSnippetsFetcherTest; 137 friend class ChromeReaderSnippetsFetcherTest;
189 138
190 enum FetchAPI { 139 void FetchSnippetsNonAuthenticated(
191 CHROME_READER_API, 140 internal::NTPSnippetsJsonRequest::Builder builder,
192 CHROME_CONTENT_SUGGESTIONS_API, 141 SnippetsAvailableCallback callback);
193 }; 142 void FetchSnippetsAuthenticated(
194 143 internal::NTPSnippetsJsonRequest::Builder builder,
195 class JsonRequest; 144 SnippetsAvailableCallback callback,
196 145 const std::string& account_id,
197 // A class that builds authenticated and non-authenticated JsonRequests. 146 const std::string& oauth_access_token);
198 // This class is only in the header for testing. 147 void StartRequest(internal::NTPSnippetsJsonRequest::Builder builder,
199 // TODO(fhorschig): Move into separate file with snippets::internal namespace. 148 SnippetsAvailableCallback callback);
200 class RequestBuilder {
201 public:
202 RequestBuilder();
203 RequestBuilder(RequestBuilder&&);
204 ~RequestBuilder();
205
206 // Builds a Request object that contains all data to fetch new snippets.
207 std::unique_ptr<JsonRequest> Build() const;
208
209 RequestBuilder& SetAuthentication(const std::string& account_id,
210 const std::string& auth_header);
211 RequestBuilder& SetCreationTime(base::TimeTicks creation_time);
212 RequestBuilder& SetFetchAPI(FetchAPI fetch_api);
213 // The language_model borrowed from the fetcher needs to stay alive until
214 // the request body is built.
215 RequestBuilder& SetLanguageModel(
216 const translate::LanguageModel* language_model);
217 RequestBuilder& SetParams(const Params& params);
218 RequestBuilder& SetParseJsonCallback(ParseJSONCallback callback);
219 RequestBuilder& SetPersonalization(Personalization personalization);
220 // The tick_clock borrowed from the fetcher will be injected into the
221 // request. It will be used at build time and after the fetch returned.
222 // It has to be alive until the request is destroyed.
223 RequestBuilder& SetTickClock(base::TickClock* tick_clock);
224 RequestBuilder& SetUrl(const GURL& url);
225 RequestBuilder& SetUrlRequestContextGetter(
226 const scoped_refptr<net::URLRequestContextGetter>& context_getter);
227 RequestBuilder& SetUserClassifier(const UserClassifier& user_classifier);
228
229 // These preview methods allow to inspect the Request without exposing it
230 // publicly.
231 // TODO(fhorschig): Remove these when moving the RequestBuilder to
232 // snippets::internal and trigger the request to intercept the request.
233 std::string PreviewRequestBodyForTesting() { return BuildBody(); }
234 std::string PreviewRequestHeadersForTesting() { return BuildHeaders(); }
235 RequestBuilder& SetUserClassForTesting(const std::string& user_class) {
236 user_class_ = user_class;
237 return *this;
238 }
239
240 private:
241 std::string BuildHeaders() const;
242 std::string BuildBody() const;
243 std::unique_ptr<net::URLFetcher> BuildURLFetcher(
244 net::URLFetcherDelegate* request,
245 const std::string& headers,
246 const std::string& body) const;
247
248 bool ReturnOnlyPersonalizedResults() const {
249 return !obfuscated_gaia_id_.empty() &&
250 personalization_ == NTPSnippetsFetcher::Personalization::kPersonal;
251 }
252
253 void PrepareLanguages(
254 translate::LanguageModel::LanguageInfo* ui_language,
255 translate::LanguageModel::LanguageInfo* other_top_language) const;
256
257 // Only required, if the request needs to be sent.
258 std::string auth_header_;
259 base::TickClock* tick_clock_;
260 FetchAPI fetch_api_;
261 Params params_;
262 ParseJSONCallback parse_json_callback_;
263 Personalization personalization_;
264 GURL url_;
265 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
266
267 // Optional properties.
268 std::string obfuscated_gaia_id_;
269 std::string user_class_;
270 const translate::LanguageModel* language_model_;
271
272 DISALLOW_COPY_AND_ASSIGN(RequestBuilder);
273 };
274
275 void FetchSnippetsNonAuthenticated(RequestBuilder builder,
276 SnippetsAvailableCallback callback);
277 void FetchSnippetsAuthenticated(RequestBuilder builder,
278 SnippetsAvailableCallback callback,
279 const std::string& account_id,
280 const std::string& oauth_access_token);
281 void StartRequest(RequestBuilder builder, SnippetsAvailableCallback callback);
282 149
283 void StartTokenRequest(); 150 void StartTokenRequest();
284 151
285 // OAuth2TokenService::Consumer overrides: 152 // OAuth2TokenService::Consumer overrides:
286 void OnGetTokenSuccess(const OAuth2TokenService::Request* request, 153 void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
287 const std::string& access_token, 154 const std::string& access_token,
288 const base::Time& expiration_time) override; 155 const base::Time& expiration_time) override;
289 void OnGetTokenFailure(const OAuth2TokenService::Request* request, 156 void OnGetTokenFailure(const OAuth2TokenService::Request* request,
290 const GoogleServiceAuthError& error) override; 157 const GoogleServiceAuthError& error) override;
291 158
292 // OAuth2TokenService::Observer overrides: 159 // OAuth2TokenService::Observer overrides:
293 void OnRefreshTokenAvailable(const std::string& account_id) override; 160 void OnRefreshTokenAvailable(const std::string& account_id) override;
294 161
295 void JsonRequestDone(std::unique_ptr<JsonRequest> request, 162 void JsonRequestDone(
296 SnippetsAvailableCallback callback, 163 std::unique_ptr<internal::NTPSnippetsJsonRequest> request,
297 std::unique_ptr<base::Value> result, 164 SnippetsAvailableCallback callback,
298 FetchResult status_code, 165 std::unique_ptr<base::Value> result,
299 const std::string& error_details); 166 internal::FetchResult status_code,
167 const std::string& error_details);
300 void FetchFinished(OptionalFetchedCategories categories, 168 void FetchFinished(OptionalFetchedCategories categories,
301 SnippetsAvailableCallback callback, 169 SnippetsAvailableCallback callback,
302 FetchResult status_code, 170 internal::FetchResult status_code,
303 const std::string& error_details); 171 const std::string& error_details);
304 172
305 bool JsonToSnippets(const base::Value& parsed, 173 bool JsonToSnippets(const base::Value& parsed,
306 NTPSnippetsFetcher::FetchedCategoriesVector* categories); 174 NTPSnippetsFetcher::FetchedCategoriesVector* categories);
307 175
308 bool DemandQuotaForRequest(bool interactive_request); 176 bool DemandQuotaForRequest(bool interactive_request);
309 177
310 // Does the fetcher use authentication to get personalized results? 178 // Does the fetcher use authentication to get personalized results?
311 bool NeedsAuthentication() const; 179 bool NeedsAuthentication() const;
312 180
313 // Authentication for signed-in users. 181 // Authentication for signed-in users.
314 SigninManagerBase* signin_manager_; 182 SigninManagerBase* signin_manager_;
315 OAuth2TokenService* token_service_; 183 OAuth2TokenService* token_service_;
316 std::unique_ptr<OAuth2TokenService::Request> oauth_request_; 184 std::unique_ptr<OAuth2TokenService::Request> oauth_request_;
317 bool waiting_for_refresh_token_ = false; 185 bool waiting_for_refresh_token_ = false;
318 186
319 // When a token request gets canceled, we want to retry once. 187 // When a token request gets canceled, we want to retry once.
320 bool oauth_token_retried_ = false; 188 bool oauth_token_retried_ = false;
321 189
322 // Holds the URL request context. 190 // Holds the URL request context.
323 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_; 191 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
324 192
325 // Stores requests that wait for an access token. 193 // Stores requests that wait for an access token.
326 std::queue<std::pair<RequestBuilder, SnippetsAvailableCallback>> 194 std::queue<std::pair<internal::NTPSnippetsJsonRequest::Builder,
195 SnippetsAvailableCallback>>
327 pending_requests_; 196 pending_requests_;
328 197
329 // Weak references, not owned. 198 // Weak references, not owned.
330 CategoryFactory* const category_factory_; 199 CategoryFactory* const category_factory_;
331 translate::LanguageModel* const language_model_; 200 translate::LanguageModel* const language_model_;
332 201
333 const ParseJSONCallback parse_json_callback_; 202 const ParseJSONCallback parse_json_callback_;
334 203
335 // API endpoint for fetching snippets. 204 // API endpoint for fetching snippets.
336 const GURL fetch_url_; 205 const GURL fetch_url_;
337 // Which API to use 206 // Which API to use
338 const FetchAPI fetch_api_; 207 const internal::FetchAPI fetch_api_;
339 208
340 // API key to use for non-authenticated requests. 209 // API key to use for non-authenticated requests.
341 const std::string api_key_; 210 const std::string api_key_;
342 211
343 // The variant of the fetching to use, loaded from variation parameters. 212 // The variant of the fetching to use, loaded from variation parameters.
344 Personalization personalization_; 213 Personalization personalization_;
345 214
346 // Allow for an injectable tick clock for testing. 215 // Allow for an injectable tick clock for testing.
347 std::unique_ptr<base::TickClock> tick_clock_; 216 std::unique_ptr<base::TickClock> tick_clock_;
348 217
349 // Classifier that tells us how active the user is. Not owned. 218 // Classifier that tells us how active the user is. Not owned.
350 const UserClassifier* user_classifier_; 219 const UserClassifier* user_classifier_;
351 220
352 // Request throttlers for limiting requests for different classes of users. 221 // Request throttlers for limiting requests for different classes of users.
353 RequestThrottler request_throttler_rare_ntp_user_; 222 RequestThrottler request_throttler_rare_ntp_user_;
354 RequestThrottler request_throttler_active_ntp_user_; 223 RequestThrottler request_throttler_active_ntp_user_;
355 RequestThrottler request_throttler_active_suggestions_consumer_; 224 RequestThrottler request_throttler_active_suggestions_consumer_;
356 225
357 // Info on the last finished fetch. 226 // Info on the last finished fetch.
358 std::string last_status_; 227 std::string last_status_;
359 std::string last_fetch_json_; 228 std::string last_fetch_json_;
360 229
361 base::WeakPtrFactory<NTPSnippetsFetcher> weak_ptr_factory_; 230 base::WeakPtrFactory<NTPSnippetsFetcher> weak_ptr_factory_;
362 231
363 DISALLOW_COPY_AND_ASSIGN(NTPSnippetsFetcher); 232 DISALLOW_COPY_AND_ASSIGN(NTPSnippetsFetcher);
364 }; 233 };
365 } // namespace ntp_snippets 234 } // namespace ntp_snippets
366 235
367 #endif // COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_FETCHER_H_ 236 #endif // COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_FETCHER_H_
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698