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

Side by Side Diff: chrome/browser/profiles/profile_downloader.cc

Issue 8510069: Make ProfileImageDownloader available to non-chromeos code (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix build Created 9 years, 1 month 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/chromeos/login/profile_image_downloader.h" 5 #include "chrome/browser/profiles/profile_downloader.h"
6 6
7 #include <string> 7 #include <string>
8 #include <vector> 8 #include <vector>
9 9
10 #include "base/json/json_reader.h" 10 #include "base/json/json_reader.h"
11 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "base/message_loop.h" 12 #include "base/message_loop.h"
13 #include "base/string_split.h" 13 #include "base/string_split.h"
14 #include "base/string_util.h" 14 #include "base/string_util.h"
15 #include "base/stringprintf.h" 15 #include "base/stringprintf.h"
16 #include "chrome/browser/chromeos/login/helper.h"
17 #include "chrome/browser/chromeos/login/user_manager.h"
18 #include "chrome/browser/net/gaia/token_service.h" 16 #include "chrome/browser/net/gaia/token_service.h"
19 #include "chrome/browser/profiles/profile_manager.h" 17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_downloader_delegate.h"
20 #include "chrome/common/chrome_notification_types.h" 19 #include "chrome/common/chrome_notification_types.h"
21 #include "chrome/common/net/gaia/gaia_constants.h" 20 #include "chrome/common/net/gaia/gaia_constants.h"
22 #include "content/public/browser/browser_thread.h" 21 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/notification_details.h" 22 #include "content/public/browser/notification_details.h"
24 #include "content/public/browser/notification_observer.h" 23 #include "content/public/browser/notification_observer.h"
25 #include "content/public/browser/notification_registrar.h" 24 #include "content/public/browser/notification_registrar.h"
26 #include "content/public/browser/notification_source.h" 25 #include "content/public/browser/notification_source.h"
27 #include "content/public/browser/notification_types.h" 26 #include "content/public/browser/notification_types.h"
28 #include "content/public/common/url_fetcher.h" 27 #include "content/public/common/url_fetcher.h"
29 #include "googleurl/src/gurl.h" 28 #include "googleurl/src/gurl.h"
30 #include "skia/ext/image_operations.h" 29 #include "skia/ext/image_operations.h"
31 30
32 using content::BrowserThread; 31 using content::BrowserThread;
33 32
34 namespace chromeos {
35
36 namespace { 33 namespace {
37 34
38 // Template for optional authorization header. 35 // Template for optional authorization header.
39 const char kAuthorizationHeader[] = "Authorization: GoogleLogin auth=%s"; 36 const char kAuthorizationHeader[] = "Authorization: GoogleLogin auth=%s";
40 37
41 // URL requesting Picasa API for user info. 38 // URL requesting Picasa API for user info.
42 const char kUserEntryURL[] = 39 const char kUserEntryURL[] =
43 "http://picasaweb.google.com/data/entry/api/user/default?alt=json"; 40 "http://picasaweb.google.com/data/entry/api/user/default?alt=json";
44 // Path in JSON dictionary to user's photo thumbnail URL. 41 // Path in JSON dictionary to user's photo thumbnail URL.
45 const char kPhotoThumbnailURLPath[] = "entry.gphoto$thumbnail.$t"; 42 const char kPhotoThumbnailURLPath[] = "entry.gphoto$thumbnail.$t";
43
44 const char kNickNamePath[] = "entry.gphoto$nickname.$t";
45
46 // Path format for specifying thumbnail's size. 46 // Path format for specifying thumbnail's size.
47 const char kThumbnailSizeFormat[] = "s%d-c"; 47 const char kThumbnailSizeFormat[] = "s%d-c";
48 // Default Picasa thumbnail size. 48 // Default Picasa thumbnail size.
49 const int kDefaultThumbnailSize = 64; 49 const int kDefaultThumbnailSize = 64;
50 50
51 // Separator of URL path components. 51 // Separator of URL path components.
52 const char kURLPathSeparator = '/'; 52 const char kURLPathSeparator = '/';
53 53
54 // Photo ID of the Picasa Web Albums profile picture (base64 of 0). 54 // Photo ID of the Picasa Web Albums profile picture (base64 of 0).
55 const char kPicasaPhotoId[] = "AAAAAAAAAAA"; 55 const char kPicasaPhotoId[] = "AAAAAAAAAAA";
(...skipping 11 matching lines...) Expand all
67 const size_t kProfileImageURLPathComponentsCount = 7; 67 const size_t kProfileImageURLPathComponentsCount = 7;
68 68
69 // Index of path component with photo ID. 69 // Index of path component with photo ID.
70 const int kPhotoIdPathComponentIndex = 2; 70 const int kPhotoIdPathComponentIndex = 2;
71 71
72 // Index of path component with photo version. 72 // Index of path component with photo version.
73 const int kPhotoVersionPathComponentIndex = 3; 73 const int kPhotoVersionPathComponentIndex = 3;
74 74
75 } // namespace 75 } // namespace
76 76
77 std::string ProfileImageDownloader::GetProfileImageURL( 77 bool ProfileDownloader::GetProfileNickNameAndImageURL(const std::string& data,
78 const std::string& data) const { 78 string16* nick_name,
79 std::string* url) const {
80 DCHECK(nick_name);
81 DCHECK(url);
82 *nick_name = string16();
83 *url = std::string();
84
79 int error_code = -1; 85 int error_code = -1;
80 std::string error_message; 86 std::string error_message;
81 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError( 87 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError(
82 data, false, &error_code, &error_message)); 88 data, false, &error_code, &error_message));
83 if (!root_value.get()) { 89 if (!root_value.get()) {
84 LOG(ERROR) << "Error while parsing Picasa user entry response: " 90 LOG(ERROR) << "Error while parsing Picasa user entry response: "
85 << error_message; 91 << error_message;
86 return std::string(); 92 return false;
87 } 93 }
88 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) { 94 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) {
89 LOG(ERROR) << "JSON root is not a dictionary: " 95 LOG(ERROR) << "JSON root is not a dictionary: "
90 << root_value->GetType(); 96 << root_value->GetType();
91 return std::string(); 97 return false;
92 } 98 }
93 base::DictionaryValue* root_dictionary = 99 base::DictionaryValue* root_dictionary =
94 static_cast<base::DictionaryValue*>(root_value.get()); 100 static_cast<base::DictionaryValue*>(root_value.get());
95 101
102 if (!root_dictionary->GetString(kNickNamePath, nick_name)) {
103 LOG(ERROR) << "Can't find nick name path in JSON data: "
104 << data;
105 return false;
106 }
107
96 std::string thumbnail_url_string; 108 std::string thumbnail_url_string;
97 if (!root_dictionary->GetString( 109 if (!root_dictionary->GetString(
98 kPhotoThumbnailURLPath, &thumbnail_url_string)) { 110 kPhotoThumbnailURLPath, &thumbnail_url_string)) {
99 LOG(ERROR) << "Can't find thumbnail path in JSON data: " 111 LOG(ERROR) << "Can't find thumbnail path in JSON data: "
100 << data; 112 << data;
101 return std::string(); 113 return false;
102 } 114 }
103 115
104 // Try to change the size of thumbnail we are going to get. 116 // Try to change the size of thumbnail we are going to get.
105 // Typical URL looks like this: 117 // Typical URL looks like this:
106 // http://lh0.ggpht.com/-abcd1aBCDEf/AAAA/AAA_A/abc12/s64-c/1234567890.jpg 118 // http://lh0.ggpht.com/-abcd1aBCDEf/AAAA/AAA_A/abc12/s64-c/1234567890.jpg
107 std::string default_thumbnail_size_path_component( 119 std::string default_thumbnail_size_path_component(
108 base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize)); 120 base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize));
121 int image_size = delegate_->GetDesiredImageSideLength();
109 std::string new_thumbnail_size_path_component( 122 std::string new_thumbnail_size_path_component(
110 base::StringPrintf(kThumbnailSizeFormat, login::kUserImageSize)); 123 base::StringPrintf(kThumbnailSizeFormat, image_size));
111 size_t thumbnail_size_pos = 124 size_t thumbnail_size_pos =
112 thumbnail_url_string.find(default_thumbnail_size_path_component); 125 thumbnail_url_string.find(default_thumbnail_size_path_component);
113 if (thumbnail_size_pos != std::string::npos) { 126 if (thumbnail_size_pos != std::string::npos) {
114 size_t thumbnail_size_end = 127 size_t thumbnail_size_end =
115 thumbnail_size_pos + default_thumbnail_size_path_component.size(); 128 thumbnail_size_pos + default_thumbnail_size_path_component.size();
116 thumbnail_url_string = 129 thumbnail_url_string =
117 thumbnail_url_string.substr(0, thumbnail_size_pos) + 130 thumbnail_url_string.substr(0, thumbnail_size_pos) +
118 new_thumbnail_size_path_component + 131 new_thumbnail_size_path_component +
119 thumbnail_url_string.substr( 132 thumbnail_url_string.substr(
120 thumbnail_size_end, 133 thumbnail_size_end,
121 thumbnail_url_string.size() - thumbnail_size_end); 134 thumbnail_url_string.size() - thumbnail_size_end);
122 } else { 135 } else {
123 LOG(WARNING) << "Hasn't found thumbnail size part in image URL: " 136 LOG(WARNING) << "Hasn't found thumbnail size part in image URL: "
124 << thumbnail_url_string; 137 << thumbnail_url_string;
125 // Use the thumbnail URL we have. 138 // Use the thumbnail URL we have.
126 } 139 }
127 140
128 GURL thumbnail_url(thumbnail_url_string); 141 GURL thumbnail_url(thumbnail_url_string);
129 if (!thumbnail_url.is_valid()) { 142 if (!thumbnail_url.is_valid()) {
130 LOG(ERROR) << "Thumbnail URL is not valid: " << thumbnail_url_string; 143 LOG(ERROR) << "Thumbnail URL is not valid: " << thumbnail_url_string;
131 return std::string(); 144 return false;
132 } 145 }
133 return thumbnail_url.spec(); 146 *url = thumbnail_url.spec();
147 return true;
134 } 148 }
135 149
136 bool ProfileImageDownloader::IsDefaultProfileImageURL( 150 bool ProfileDownloader::IsDefaultProfileImageURL(const std::string& url) const {
137 const std::string& url) const {
138
139 GURL image_url_object(url); 151 GURL image_url_object(url);
140 DCHECK(image_url_object.is_valid()); 152 DCHECK(image_url_object.is_valid());
141 VLOG(1) << "URL to check for default image: " << image_url_object.spec(); 153 VLOG(1) << "URL to check for default image: " << image_url_object.spec();
142 std::vector<std::string> path_components; 154 std::vector<std::string> path_components;
143 base::SplitString(image_url_object.path(), 155 base::SplitString(image_url_object.path(),
144 kURLPathSeparator, 156 kURLPathSeparator,
145 &path_components); 157 &path_components);
146 158
147 if (path_components.size() != kProfileImageURLPathComponentsCount) 159 if (path_components.size() != kProfileImageURLPathComponentsCount)
148 return false; 160 return false;
149 161
150 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex]; 162 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex];
151 const std::string& photo_version = 163 const std::string& photo_version =
152 path_components[kPhotoVersionPathComponentIndex]; 164 path_components[kPhotoVersionPathComponentIndex];
153 165
154 // There are at least two pairs of (ID, version) for the default photo: 166 // There are at least two pairs of (ID, version) for the default photo:
155 // the default Google+ profile photo and the default Picasa profile photo. 167 // the default Google+ profile photo and the default Picasa profile photo.
156 return ((photo_id == kPicasaPhotoId && 168 return ((photo_id == kPicasaPhotoId &&
157 photo_version == kDefaultPicasaPhotoVersion) || 169 photo_version == kDefaultPicasaPhotoVersion) ||
158 (photo_id == kGooglePlusPhotoId && 170 (photo_id == kGooglePlusPhotoId &&
159 photo_version == kDefaultGooglePlusPhotoVersion)); 171 photo_version == kDefaultGooglePlusPhotoVersion));
160 } 172 }
161 173
162 ProfileImageDownloader::ProfileImageDownloader(Delegate* delegate) 174 ProfileDownloader::ProfileDownloader(ProfileDownloaderDelegate* delegate)
163 : delegate_(delegate) { 175 : delegate_(delegate) {
176 DCHECK(delegate_);
164 } 177 }
165 178
166 void ProfileImageDownloader::Start() { 179 void ProfileDownloader::Start() {
167 VLOG(1) << "Starting profile image downloader..."; 180 VLOG(1) << "Starting profile downloader...";
168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
169 182
170 TokenService* service = 183 TokenService* service = delegate_->GetBrowserProfile()->GetTokenService();
171 ProfileManager::GetDefaultProfile()->GetTokenService();
172 if (!service) { 184 if (!service) {
173 // This can happen in some test paths. 185 // This can happen in some test paths.
174 LOG(WARNING) << "User has no token service"; 186 LOG(WARNING) << "User has no token service";
175 if (delegate_) 187 delegate_->OnDownloadComplete(this, false);
176 delegate_->OnDownloadFailure();
177 return; 188 return;
178 } 189 }
179 if (service->HasTokenForService(GaiaConstants::kPicasaService)) { 190 if (service->HasTokenForService(GaiaConstants::kPicasaService)) {
180 auth_token_ = 191 auth_token_ =
181 service->GetTokenForService(GaiaConstants::kPicasaService); 192 service->GetTokenForService(GaiaConstants::kPicasaService);
182 StartFetchingImage(); 193 StartFetchingImage();
183 } else { 194 } else {
184 registrar_.Add(this, 195 registrar_.Add(this,
185 chrome::NOTIFICATION_TOKEN_AVAILABLE, 196 chrome::NOTIFICATION_TOKEN_AVAILABLE,
186 content::Source<TokenService>(service)); 197 content::Source<TokenService>(service));
187 registrar_.Add(this, 198 registrar_.Add(this,
188 chrome::NOTIFICATION_TOKEN_REQUEST_FAILED, 199 chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
189 content::Source<TokenService>(service)); 200 content::Source<TokenService>(service));
190 } 201 }
191 } 202 }
192 203
193 void ProfileImageDownloader::StartFetchingImage() { 204 const string16& ProfileDownloader::GetProfileFullName() const {
194 std::string email = UserManager::Get()->logged_in_user().email(); 205 return profile_full_name_;
195 if (email.empty()) 206 }
196 return; 207
208 const SkBitmap& ProfileDownloader::GetProfilePicture() const {
209 return profile_picture_;
210 }
211
212 void ProfileDownloader::StartFetchingImage() {
197 VLOG(1) << "Fetching user entry with token: " << auth_token_; 213 VLOG(1) << "Fetching user entry with token: " << auth_token_;
198 user_entry_fetcher_.reset(content::URLFetcher::Create( 214 user_entry_fetcher_.reset(content::URLFetcher::Create(
199 GURL(kUserEntryURL), content::URLFetcher::GET, this)); 215 GURL(kUserEntryURL), content::URLFetcher::GET, this));
200 user_entry_fetcher_->SetRequestContext( 216 user_entry_fetcher_->SetRequestContext(
201 ProfileManager::GetDefaultProfile()->GetRequestContext()); 217 delegate_->GetBrowserProfile()->GetRequestContext());
202 if (!auth_token_.empty()) { 218 if (!auth_token_.empty()) {
203 user_entry_fetcher_->SetExtraRequestHeaders( 219 user_entry_fetcher_->SetExtraRequestHeaders(
204 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); 220 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str()));
205 } 221 }
206 user_entry_fetcher_->Start(); 222 user_entry_fetcher_->Start();
207 } 223 }
208 224
209 ProfileImageDownloader::~ProfileImageDownloader() {} 225 ProfileDownloader::~ProfileDownloader() {}
210 226
211 void ProfileImageDownloader::OnURLFetchComplete( 227 void ProfileDownloader::OnURLFetchComplete(const content::URLFetcher* source) {
212 const content::URLFetcher* source) {
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
214 std::string data; 229 std::string data;
215 source->GetResponseAsString(&data); 230 source->GetResponseAsString(&data);
216 if (source->GetResponseCode() != 200) { 231 if (source->GetResponseCode() != 200) {
217 LOG(ERROR) << "Response code is " << source->GetResponseCode(); 232 LOG(ERROR) << "Response code is " << source->GetResponseCode();
218 LOG(ERROR) << "Url is " << source->GetURL().spec(); 233 LOG(ERROR) << "Url is " << source->GetURL().spec();
219 LOG(ERROR) << "Data is " << data; 234 LOG(ERROR) << "Data is " << data;
220 if (delegate_) 235 delegate_->OnDownloadComplete(this, false);
221 delegate_->OnDownloadFailure();
222 return; 236 return;
223 } 237 }
224 238
225 if (source == user_entry_fetcher_.get()) { 239 if (source == user_entry_fetcher_.get()) {
226 std::string image_url = GetProfileImageURL(data); 240 std::string image_url;
227 if (image_url.empty()) { 241 if (!GetProfileNickNameAndImageURL(data, &profile_full_name_, &image_url)) {
228 if (delegate_) 242 delegate_->OnDownloadComplete(this, false);
229 delegate_->OnDownloadFailure();
230 return; 243 return;
231 } 244 }
232 if (IsDefaultProfileImageURL(image_url)) { 245 if (IsDefaultProfileImageURL(image_url)) {
233 if (delegate_) 246 delegate_->OnDownloadComplete(this, true);
234 delegate_->OnDownloadDefaultImage();
235 return; 247 return;
236 } 248 }
237 VLOG(1) << "Fetching profile image from " << image_url; 249 VLOG(1) << "Fetching profile image from " << image_url;
238 profile_image_fetcher_.reset(content::URLFetcher::Create( 250 profile_image_fetcher_.reset(content::URLFetcher::Create(
239 GURL(image_url), content::URLFetcher::GET, this)); 251 GURL(image_url), content::URLFetcher::GET, this));
240 profile_image_fetcher_->SetRequestContext( 252 profile_image_fetcher_->SetRequestContext(
241 ProfileManager::GetDefaultProfile()->GetRequestContext()); 253 delegate_->GetBrowserProfile()->GetRequestContext());
242 if (!auth_token_.empty()) { 254 if (!auth_token_.empty()) {
243 profile_image_fetcher_->SetExtraRequestHeaders( 255 profile_image_fetcher_->SetExtraRequestHeaders(
244 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); 256 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str()));
245 } 257 }
246 profile_image_fetcher_->Start(); 258 profile_image_fetcher_->Start();
247 } else if (source == profile_image_fetcher_.get()) { 259 } else if (source == profile_image_fetcher_.get()) {
248 VLOG(1) << "Decoding the image..."; 260 VLOG(1) << "Decoding the image...";
249 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder( 261 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder(
250 this, data); 262 this, data);
251 image_decoder->Start(); 263 image_decoder->Start();
252 } 264 }
253 } 265 }
254 266
255 void ProfileImageDownloader::OnImageDecoded(const ImageDecoder* decoder, 267 void ProfileDownloader::OnImageDecoded(const ImageDecoder* decoder,
256 const SkBitmap& decoded_image) { 268 const SkBitmap& decoded_image) {
257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
258 SkBitmap resized_image = skia::ImageOperations::Resize( 270 int image_size = delegate_->GetDesiredImageSideLength();
271 profile_picture_ = skia::ImageOperations::Resize(
259 decoded_image, 272 decoded_image,
260 skia::ImageOperations::RESIZE_BEST, 273 skia::ImageOperations::RESIZE_BEST,
261 login::kUserImageSize, 274 image_size,
262 login::kUserImageSize); 275 image_size);
263 if (delegate_) 276 delegate_->OnDownloadComplete(this, true);
264 delegate_->OnDownloadSuccess(resized_image);
265 } 277 }
266 278
267 void ProfileImageDownloader::OnDecodeImageFailed(const ImageDecoder* decoder) { 279 void ProfileDownloader::OnDecodeImageFailed(const ImageDecoder* decoder) {
268 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 280 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
269 if (delegate_) 281 delegate_->OnDownloadComplete(this, false);
270 delegate_->OnDownloadFailure();
271 } 282 }
272 283
273 void ProfileImageDownloader::Observe( 284 void ProfileDownloader::Observe(
274 int type, 285 int type,
275 const content::NotificationSource& source, 286 const content::NotificationSource& source,
276 const content::NotificationDetails& details) { 287 const content::NotificationDetails& details) {
277 DCHECK(type == chrome::NOTIFICATION_TOKEN_AVAILABLE || 288 DCHECK(type == chrome::NOTIFICATION_TOKEN_AVAILABLE ||
278 type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED); 289 type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED);
279 290
280 TokenService::TokenAvailableDetails* token_details = 291 TokenService::TokenAvailableDetails* token_details =
281 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); 292 content::Details<TokenService::TokenAvailableDetails>(details).ptr();
282 if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) { 293 if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) {
283 if (token_details->service() == GaiaConstants::kPicasaService) { 294 if (token_details->service() == GaiaConstants::kPicasaService) {
284 registrar_.RemoveAll(); 295 registrar_.RemoveAll();
285 auth_token_ = token_details->token(); 296 auth_token_ = token_details->token();
286 StartFetchingImage(); 297 StartFetchingImage();
287 } 298 }
288 } else { 299 } else {
289 if (token_details->service() == GaiaConstants::kPicasaService) { 300 if (token_details->service() == GaiaConstants::kPicasaService) {
290 LOG(WARNING) << "ProfileImageDownloader: token request failed"; 301 LOG(WARNING) << "ProfileDownloader: token request failed";
291 if (delegate_) 302 delegate_->OnDownloadComplete(this, false);
292 delegate_->OnDownloadFailure();
293 } 303 }
294 } 304 }
295 } 305 }
296
297 } // namespace chromeos
OLDNEW
« no previous file with comments | « chrome/browser/profiles/profile_downloader.h ('k') | chrome/browser/profiles/profile_downloader_delegate.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698