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

Side by Side Diff: apps/launcher.cc

Issue 300063006: Files.app: Let Files.app pass mutliple files to file handlers. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 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 | Annotate | Revision Log
« no previous file with comments | « apps/launcher.h ('k') | chrome/browser/chromeos/file_manager/file_tasks.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 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 "apps/launcher.h" 5 #include "apps/launcher.h"
6 6
7 #include "apps/browser/api/app_runtime/app_runtime_api.h" 7 #include "apps/browser/api/app_runtime/app_runtime_api.h"
8 #include "apps/browser/file_handler_util.h" 8 #include "apps/browser/file_handler_util.h"
9 #include "apps/common/api/app_runtime.h" 9 #include "apps/common/api/app_runtime.h"
10 #include "base/command_line.h" 10 #include "base/command_line.h"
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
84 } 84 }
85 85
86 // Helper method to launch the platform app |extension| with no data. This 86 // Helper method to launch the platform app |extension| with no data. This
87 // should be called in the fallback case, where it has been impossible to 87 // should be called in the fallback case, where it has been impossible to
88 // load or obtain file launch data. 88 // load or obtain file launch data.
89 void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) { 89 void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) {
90 DCHECK_CURRENTLY_ON(BrowserThread::UI); 90 DCHECK_CURRENTLY_ON(BrowserThread::UI);
91 AppEventRouter::DispatchOnLaunchedEvent(profile, extension); 91 AppEventRouter::DispatchOnLaunchedEvent(profile, extension);
92 } 92 }
93 93
94 // Class to handle launching of platform apps to open a specific path. 94 // Class to handle launching of platform apps to open specific paths.
95 // An instance of this class is created for each launch. The lifetime of these 95 // An instance of this class is created for each launch. The lifetime of these
96 // instances is managed by reference counted pointers. As long as an instance 96 // instances is managed by reference counted pointers. As long as an instance
97 // has outstanding tasks on a message queue it will be retained; once all 97 // has outstanding tasks on a message queue it will be retained; once all
98 // outstanding tasks are completed it will be deleted. 98 // outstanding tasks are completed it will be deleted.
99 class PlatformAppPathLauncher 99 class PlatformAppPathLauncher
100 : public base::RefCountedThreadSafe<PlatformAppPathLauncher> { 100 : public base::RefCountedThreadSafe<PlatformAppPathLauncher> {
101 public: 101 public:
102 PlatformAppPathLauncher(Profile* profile, 102 PlatformAppPathLauncher(Profile* profile,
103 const Extension* extension, 103 const Extension* extension,
104 const std::vector<base::FilePath>& file_paths)
105 : profile_(profile), extension_(extension), file_paths_(file_paths) {}
106
107 PlatformAppPathLauncher(Profile* profile,
108 const Extension* extension,
104 const base::FilePath& file_path) 109 const base::FilePath& file_path)
105 : profile_(profile), extension_(extension), file_path_(file_path) {} 110 : profile_(profile), extension_(extension) {
111 if (!file_path.empty())
112 file_paths_.push_back(file_path);
113 }
106 114
107 void Launch() { 115 void Launch() {
108 DCHECK_CURRENTLY_ON(BrowserThread::UI); 116 DCHECK_CURRENTLY_ON(BrowserThread::UI);
109 if (file_path_.empty()) { 117 if (file_paths_.empty()) {
110 LaunchPlatformAppWithNoData(profile_, extension_); 118 LaunchPlatformAppWithNoData(profile_, extension_);
111 return; 119 return;
112 } 120 }
113 121
114 DCHECK(file_path_.IsAbsolute()); 122 for (size_t i = 0; i < file_paths_.size(); ++i) {
123 DCHECK(file_paths_[i].IsAbsolute());
124 }
115 125
116 if (HasFileSystemWritePermission(extension_)) { 126 if (HasFileSystemWritePermission(extension_)) {
117 std::vector<base::FilePath> paths;
118 paths.push_back(file_path_);
119 CheckWritableFiles( 127 CheckWritableFiles(
120 paths, 128 file_paths_,
121 profile_, 129 profile_,
122 false, 130 false,
123 base::Bind(&PlatformAppPathLauncher::OnFileValid, this), 131 base::Bind(&PlatformAppPathLauncher::OnFileValid, this),
124 base::Bind(&PlatformAppPathLauncher::OnFileInvalid, this)); 132 base::Bind(&PlatformAppPathLauncher::OnFileInvalid, this));
125 return; 133 return;
126 } 134 }
127 135
128 OnFileValid(); 136 OnFileValid();
129 } 137 }
130 138
(...skipping 12 matching lines...) Expand all
143 } 151 }
144 152
145 private: 153 private:
146 friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>; 154 friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>;
147 155
148 virtual ~PlatformAppPathLauncher() {} 156 virtual ~PlatformAppPathLauncher() {}
149 157
150 void MakePathAbsolute(const base::FilePath& current_directory) { 158 void MakePathAbsolute(const base::FilePath& current_directory) {
151 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 159 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
152 160
153 if (!DoMakePathAbsolute(current_directory, &file_path_)) { 161 for (std::vector<base::FilePath>::iterator it = file_paths_.begin();
154 LOG(WARNING) << "Cannot make absolute path from " << file_path_.value(); 162 it != file_paths_.end();) {
155 file_path_ = base::FilePath(); 163 if (!DoMakePathAbsolute(current_directory, &*it)) {
164 LOG(WARNING) << "Cannot make absolute path from " << it->value();
165 file_paths_.erase(it);
166 } else {
167 ++it;
168 }
156 } 169 }
157 170
158 BrowserThread::PostTask(BrowserThread::UI, 171 BrowserThread::PostTask(BrowserThread::UI,
159 FROM_HERE, 172 FROM_HERE,
160 base::Bind(&PlatformAppPathLauncher::Launch, this)); 173 base::Bind(&PlatformAppPathLauncher::Launch, this));
161 } 174 }
162 175
163 void OnFileValid() { 176 void OnFileValid() { StepToFillMimeTypes(); }
177
178 void OnFileInvalid(const base::FilePath& /* error_path */) {
179 LaunchWithNoLaunchData();
180 }
181
182 void StepToFillMimeTypes() {
183 DCHECK_CURRENTLY_ON(BrowserThread::UI);
184 DCHECK(file_paths_.size() >= mime_types_.size());
185
186 if (file_paths_.size() == mime_types_.size()) {
187 LaunchWithMimeType();
188 return;
189 }
190
164 #if defined(OS_CHROMEOS) 191 #if defined(OS_CHROMEOS)
165 if (drive::util::IsUnderDriveMountPoint(file_path_)) { 192 if (drive::util::IsUnderDriveMountPoint(file_paths_[mime_types_.size()])) {
166 PlatformAppPathLauncher::GetMimeTypeAndLaunchForDriveFile(); 193 GetMimeTypeForDriveFileAndStep();
167 return; 194 return;
168 } 195 }
169 #endif 196 #endif
170 197
171 BrowserThread::PostTask( 198 BrowserThread::PostTask(
172 BrowserThread::FILE, 199 BrowserThread::FILE,
173 FROM_HERE, 200 FROM_HERE,
174 base::Bind(&PlatformAppPathLauncher::GetMimeTypeAndLaunch, this)); 201 base::Bind(&PlatformAppPathLauncher::GetMimeTypeAndStep, this));
175 } 202 }
176 203
177 void OnFileInvalid(const base::FilePath& /* error_path */) { 204 void GetMimeTypeAndStep() {
178 LaunchWithNoLaunchData();
179 }
180
181 void GetMimeTypeAndLaunch() {
182 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 205 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
183 206
184 // If the file doesn't exist, or is a directory, launch with no launch data. 207 // If the file doesn't exist, or is a directory, launch with no launch data.
185 if (!base::PathExists(file_path_) || 208 if (!base::PathExists(file_paths_[0]) ||
kinaba 2014/05/27 07:08:26 0 => mime_types.size() ? (I'd rather recommend you
hirono 2014/05/28 02:23:27 Done.
186 base::DirectoryExists(file_path_)) { 209 base::DirectoryExists(file_paths_[0])) {
187 LOG(WARNING) << "No file exists with path " << file_path_.value(); 210 LOG(WARNING) << "No file exists with path " << file_paths_[0].value();
188 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 211 BrowserThread::PostTask(
189 &PlatformAppPathLauncher::LaunchWithNoLaunchData, this)); 212 BrowserThread::UI,
213 FROM_HERE,
214 base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this));
190 return; 215 return;
191 } 216 }
192 217
193 std::string mime_type; 218 std::string mime_type;
194 if (!net::GetMimeTypeFromFile(file_path_, &mime_type)) { 219 if (!net::GetMimeTypeFromFile(file_paths_[0], &mime_type)) {
195 // If MIME type of the file can't be determined by its path, 220 // If MIME type of the file can't be determined by its path,
196 // try to sniff it by its content. 221 // try to sniff it by its content.
197 std::vector<char> content(net::kMaxBytesToSniff); 222 std::vector<char> content(net::kMaxBytesToSniff);
198 int bytes_read = base::ReadFile(file_path_, &content[0], content.size()); 223 int bytes_read =
224 base::ReadFile(file_paths_[0], &content[0], content.size());
199 if (bytes_read >= 0) { 225 if (bytes_read >= 0) {
200 net::SniffMimeType(&content[0], 226 net::SniffMimeType(&content[0],
201 bytes_read, 227 bytes_read,
202 net::FilePathToFileURL(file_path_), 228 net::FilePathToFileURL(file_paths_[0]),
203 std::string(), // type_hint (passes no hint) 229 std::string(), // type_hint (passes no hint)
204 &mime_type); 230 &mime_type);
205 } 231 }
206 if (mime_type.empty()) 232 if (mime_type.empty())
207 mime_type = kFallbackMimeType; 233 mime_type = kFallbackMimeType;
208 } 234 }
209 235 mime_types_.push_back(mime_type);
210 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 236 BrowserThread::PostTask(
211 &PlatformAppPathLauncher::LaunchWithMimeType, this, mime_type)); 237 BrowserThread::UI,
238 FROM_HERE,
239 base::Bind(&PlatformAppPathLauncher::StepToFillMimeTypes, this));
212 } 240 }
213 241
214 #if defined(OS_CHROMEOS) 242 #if defined(OS_CHROMEOS)
215 void GetMimeTypeAndLaunchForDriveFile() { 243 void GetMimeTypeForDriveFileAndStep() {
216 DCHECK_CURRENTLY_ON(BrowserThread::UI); 244 DCHECK_CURRENTLY_ON(BrowserThread::UI);
217 245
218 drive::FileSystemInterface* file_system = 246 drive::FileSystemInterface* const file_system =
219 drive::util::GetFileSystemByProfile(profile_); 247 drive::util::GetFileSystemByProfile(profile_);
220 if (!file_system) { 248 if (!file_system) {
221 LaunchWithNoLaunchData(); 249 LaunchWithNoLaunchData();
222 return; 250 return;
223 } 251 }
224 252
225 file_system->GetFile( 253 file_system->GetFile(
226 drive::util::ExtractDrivePath(file_path_), 254 drive::util::ExtractDrivePath(file_paths_[mime_types_.size()]),
227 base::Bind(&PlatformAppPathLauncher::OnGotDriveFile, this)); 255 base::Bind(&PlatformAppPathLauncher::OnGotDriveFile, this));
228 } 256 }
229 257
230 void OnGotDriveFile(drive::FileError error, 258 void OnGotDriveFile(drive::FileError error,
231 const base::FilePath& file_path, 259 const base::FilePath& file_path,
232 scoped_ptr<drive::ResourceEntry> entry) { 260 scoped_ptr<drive::ResourceEntry> entry) {
233 DCHECK_CURRENTLY_ON(BrowserThread::UI); 261 DCHECK_CURRENTLY_ON(BrowserThread::UI);
234 262
235 if (error != drive::FILE_ERROR_OK || 263 if (error != drive::FILE_ERROR_OK ||
236 !entry || entry->file_specific_info().is_hosted_document()) { 264 !entry || entry->file_specific_info().is_hosted_document()) {
237 LaunchWithNoLaunchData(); 265 LaunchWithNoLaunchData();
238 return; 266 return;
239 } 267 }
240 268
241 const std::string& mime_type = 269 const std::string& mime_type =
242 entry->file_specific_info().content_mime_type(); 270 entry->file_specific_info().content_mime_type();
243 LaunchWithMimeType(mime_type.empty() ? kFallbackMimeType : mime_type); 271 mime_types_.push_back(mime_type.empty() ? kFallbackMimeType : mime_type);
272
273 StepToFillMimeTypes();
244 } 274 }
245 #endif // defined(OS_CHROMEOS) 275 #endif // defined(OS_CHROMEOS)
246 276
247 void LaunchWithNoLaunchData() { 277 void LaunchWithNoLaunchData() {
248 // This method is required as an entry point on the UI thread. 278 // This method is required as an entry point on the UI thread.
249 LaunchPlatformAppWithNoData(profile_, extension_); 279 LaunchPlatformAppWithNoData(profile_, extension_);
250 } 280 }
251 281
252 void LaunchWithMimeType(const std::string& mime_type) { 282 void LaunchWithMimeType() {
283 DCHECK(file_paths_.size() == mime_types_.size());
284
253 // Find file handler from the platform app for the file being opened. 285 // Find file handler from the platform app for the file being opened.
254 const extensions::FileHandlerInfo* handler = NULL; 286 const extensions::FileHandlerInfo* handler = NULL;
255 if (!handler_id_.empty()) 287 if (!handler_id_.empty()) {
256 handler = FileHandlerForId(*extension_, handler_id_); 288 handler = FileHandlerForId(*extension_, handler_id_);
257 else 289 if (handler) {
258 handler = FirstFileHandlerForFile(*extension_, mime_type, file_path_); 290 for (size_t i = 0; i < file_paths_.size(); ++i) {
259 if (handler && !FileHandlerCanHandleFile(*handler, mime_type, file_path_)) { 291 if (!FileHandlerCanHandleFile(
260 LOG(WARNING) << "Extension does not provide a valid file handler for " 292 *handler, mime_types_[i], file_paths_[i])) {
261 << file_path_.value(); 293 LOG(WARNING)
262 LaunchWithNoLaunchData(); 294 << "Extension does not provide a valid file handler for "
263 return; 295 << file_paths_[i].value();
296 handler = NULL;
297 break;
298 }
299 }
300 }
301 } else {
302 std::set<std::pair<base::FilePath, std::string> > path_and_file_type_set;
303 for (size_t i = 0; i < file_paths_.size(); ++i) {
304 path_and_file_type_set.insert(
305 std::make_pair(file_paths_[i], mime_types_[i]));
306 }
307 const std::vector<const extensions::FileHandlerInfo*>& handlers =
308 extensions::app_file_handler_util::FindFileHandlersForFiles(
309 *extension_, path_and_file_type_set);
310 if (!handlers.empty())
311 handler = handlers[0];
264 } 312 }
265 313
266 // If this app doesn't have a file handler that supports the file, launch 314 // If this app doesn't have a file handler that supports the file, launch
267 // with no launch data. 315 // with no launch data.
268 if (!handler) { 316 if (!handler) {
269 LOG(WARNING) << "Extension does not provide a valid file handler for " 317 LOG(WARNING) << "Extension does not provide a valid file handler.";
270 << file_path_.value();
271 LaunchWithNoLaunchData(); 318 LaunchWithNoLaunchData();
272 return; 319 return;
273 } 320 }
274 321
275 if (handler_id_.empty()) 322 if (handler_id_.empty())
276 handler_id_ = handler->id; 323 handler_id_ = handler->id;
277 324
278 // Access needs to be granted to the file for the process associated with 325 // Access needs to be granted to the file for the process associated with
279 // the extension. To do this the ExtensionHost is needed. This might not be 326 // the extension. To do this the ExtensionHost is needed. This might not be
280 // available, or it might be in the process of being unloaded, in which case 327 // available, or it might be in the process of being unloaded, in which case
281 // the lazy background task queue is used to load the extension and then 328 // the lazy background task queue is used to load the extension and then
282 // call back to us. 329 // call back to us.
283 extensions::LazyBackgroundTaskQueue* queue = 330 extensions::LazyBackgroundTaskQueue* const queue =
284 ExtensionSystem::Get(profile_)->lazy_background_task_queue(); 331 ExtensionSystem::Get(profile_)->lazy_background_task_queue();
285 if (queue->ShouldEnqueueTask(profile_, extension_)) { 332 if (queue->ShouldEnqueueTask(profile_, extension_)) {
286 queue->AddPendingTask(profile_, extension_->id(), base::Bind( 333 queue->AddPendingTask(
287 &PlatformAppPathLauncher::GrantAccessToFileAndLaunch, 334 profile_,
288 this, mime_type)); 335 extension_->id(),
336 base::Bind(&PlatformAppPathLauncher::GrantAccessToFileAndLaunch,
337 this));
289 return; 338 return;
290 } 339 }
291 340
292 extensions::ProcessManager* process_manager = 341 extensions::ProcessManager* const process_manager =
293 ExtensionSystem::Get(profile_)->process_manager(); 342 ExtensionSystem::Get(profile_)->process_manager();
294 ExtensionHost* host = 343 ExtensionHost* const host =
295 process_manager->GetBackgroundHostForExtension(extension_->id()); 344 process_manager->GetBackgroundHostForExtension(extension_->id());
296 DCHECK(host); 345 DCHECK(host);
297 GrantAccessToFileAndLaunch(mime_type, host); 346 GrantAccessToFileAndLaunch(host);
298 } 347 }
299 348
300 void GrantAccessToFileAndLaunch(const std::string& mime_type, 349 void GrantAccessToFileAndLaunch(ExtensionHost* host) {
301 ExtensionHost* host) {
302 // If there was an error loading the app page, |host| will be NULL. 350 // If there was an error loading the app page, |host| will be NULL.
303 if (!host) { 351 if (!host) {
304 LOG(ERROR) << "Could not load app page for " << extension_->id(); 352 LOG(ERROR) << "Could not load app page for " << extension_->id();
305 return; 353 return;
306 } 354 }
307 355
308 GrantedFileEntry file_entry = 356 std::vector<GrantedFileEntry> file_entries;
309 CreateFileEntry(profile_, 357 for (size_t i = 0; i < file_paths_.size(); ++i) {
310 extension_, 358 file_entries.push_back(
311 host->render_process_host()->GetID(), 359 CreateFileEntry(profile_,
312 file_path_, 360 extension_,
313 false); 361 host->render_process_host()->GetID(),
314 AppEventRouter::DispatchOnLaunchedEventWithFileEntry( 362 file_paths_[i],
315 profile_, extension_, handler_id_, mime_type, file_entry); 363 false));
364 }
365
366 AppEventRouter::DispatchOnLaunchedEventWithFileEntries(
367 profile_, extension_, handler_id_, mime_types_, file_entries);
316 } 368 }
317 369
318 // The profile the app should be run in. 370 // The profile the app should be run in.
319 Profile* profile_; 371 Profile* profile_;
320 // The extension providing the app. 372 // The extension providing the app.
321 // TODO(benwells): Hold onto the extension ID instead of a pointer as it 373 // TODO(benwells): Hold onto the extension ID instead of a pointer as it
322 // is possible the extension will be unloaded while we're doing our thing. 374 // is possible the extension will be unloaded while we're doing our thing.
323 // See http://crbug.com/372270 for details. 375 // See http://crbug.com/372270 for details.
324 const Extension* extension_; 376 const Extension* extension_;
325 // The path to be passed through to the app. 377 // The path to be passed through to the app.
326 base::FilePath file_path_; 378 std::vector<base::FilePath> file_paths_;
379 std::vector<std::string> mime_types_;
327 // The ID of the file handler used to launch the app. 380 // The ID of the file handler used to launch the app.
328 std::string handler_id_; 381 std::string handler_id_;
329 382
330 DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher); 383 DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher);
331 }; 384 };
332 385
333 } // namespace 386 } // namespace
334 387
335 void LaunchPlatformAppWithCommandLine(Profile* profile, 388 void LaunchPlatformAppWithCommandLine(Profile* profile,
336 const Extension* extension, 389 const Extension* extension,
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
384 launcher->Launch(); 437 launcher->Launch();
385 } 438 }
386 439
387 void LaunchPlatformApp(Profile* profile, const Extension* extension) { 440 void LaunchPlatformApp(Profile* profile, const Extension* extension) {
388 LaunchPlatformAppWithCommandLine(profile, 441 LaunchPlatformAppWithCommandLine(profile,
389 extension, 442 extension,
390 CommandLine(CommandLine::NO_PROGRAM), 443 CommandLine(CommandLine::NO_PROGRAM),
391 base::FilePath()); 444 base::FilePath());
392 } 445 }
393 446
394 void LaunchPlatformAppWithFileHandler(Profile* profile, 447 void LaunchPlatformAppWithFileHandler(
395 const Extension* extension, 448 Profile* profile,
396 const std::string& handler_id, 449 const Extension* extension,
397 const base::FilePath& file_path) { 450 const std::string& handler_id,
451 const std::vector<base::FilePath>& file_paths) {
398 scoped_refptr<PlatformAppPathLauncher> launcher = 452 scoped_refptr<PlatformAppPathLauncher> launcher =
399 new PlatformAppPathLauncher(profile, extension, file_path); 453 new PlatformAppPathLauncher(profile, extension, file_paths);
400 launcher->LaunchWithHandler(handler_id); 454 launcher->LaunchWithHandler(handler_id);
401 } 455 }
402 456
403 void RestartPlatformApp(Profile* profile, const Extension* extension) { 457 void RestartPlatformApp(Profile* profile, const Extension* extension) {
404 EventRouter* event_router = EventRouter::Get(profile); 458 EventRouter* event_router = EventRouter::Get(profile);
405 bool listening_to_restart = event_router-> 459 bool listening_to_restart = event_router->
406 ExtensionHasEventListener(extension->id(), 460 ExtensionHasEventListener(extension->id(),
407 app_runtime::OnRestarted::kEventName); 461 app_runtime::OnRestarted::kEventName);
408 462
409 if (listening_to_restart) { 463 if (listening_to_restart) {
(...skipping 16 matching lines...) Expand all
426 void LaunchPlatformAppWithUrl(Profile* profile, 480 void LaunchPlatformAppWithUrl(Profile* profile,
427 const Extension* extension, 481 const Extension* extension,
428 const std::string& handler_id, 482 const std::string& handler_id,
429 const GURL& url, 483 const GURL& url,
430 const GURL& referrer_url) { 484 const GURL& referrer_url) {
431 AppEventRouter::DispatchOnLaunchedEventWithUrl( 485 AppEventRouter::DispatchOnLaunchedEventWithUrl(
432 profile, extension, handler_id, url, referrer_url); 486 profile, extension, handler_id, url, referrer_url);
433 } 487 }
434 488
435 } // namespace apps 489 } // namespace apps
OLDNEW
« no previous file with comments | « apps/launcher.h ('k') | chrome/browser/chromeos/file_manager/file_tasks.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698