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 3 // found in the LICENSE file. 4 5 #include "apps/launcher.h" 6 7 #include "base/command_line.h" 8 #include "base/file_util.h" 9 #include "base/files/file_path.h" 10 #include "base/logging.h" 11 #include "base/memory/ref_counted.h" 12 #include "base/strings/string_util.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "chrome/browser/extensions/api/app_runtime/app_runtime_api.h" 15 #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" 16 #include "chrome/browser/extensions/api/file_system/file_system_api.h" 17 #include "chrome/browser/extensions/event_names.h" 18 #include "chrome/browser/extensions/event_router.h" 19 #include "chrome/browser/extensions/extension_host.h" 20 #include "chrome/browser/extensions/extension_prefs.h" 21 #include "chrome/browser/extensions/extension_process_manager.h" 22 #include "chrome/browser/extensions/extension_service.h" 23 #include "chrome/browser/extensions/extension_system.h" 24 #include "chrome/browser/extensions/lazy_background_task_queue.h" 25 #include "chrome/browser/profiles/profile.h" 26 #include "chrome/browser/ui/extensions/app_metro_infobar_delegate_win.h" 27 #include "chrome/common/extensions/extension.h" 28 #include "chrome/common/extensions/extension_messages.h" 29 #include "content/public/browser/browser_thread.h" 30 #include "content/public/browser/render_process_host.h" 31 #include "content/public/browser/web_contents.h" 32 #include "net/base/mime_util.h" 33 #include "net/base/net_util.h" 34 35 #if defined(OS_CHROMEOS) 36 #include "chrome/browser/chromeos/drive/drive_integration_service.h" 37 #include "chrome/browser/chromeos/drive/file_errors.h" 38 #include "chrome/browser/chromeos/drive/file_system_interface.h" 39 #include "chrome/browser/chromeos/drive/file_system_util.h" 40 #endif 41 42 #if defined(OS_WIN) 43 #include "win8/util/win8_util.h" 44 #endif 45 46 using content::BrowserThread; 47 using extensions::app_file_handler_util::FileHandlerForId; 48 using extensions::app_file_handler_util::FileHandlerCanHandleFile; 49 using extensions::app_file_handler_util::FirstFileHandlerForFile; 50 using extensions::app_file_handler_util::CreateFileEntry; 51 using extensions::app_file_handler_util::GrantedFileEntry; 52 using extensions::Extension; 53 using extensions::ExtensionHost; 54 using extensions::ExtensionSystem; 55 56 namespace apps { 57 58 namespace { 59 60 const char kFallbackMimeType[] = "application/octet-stream"; 61 62 bool MakePathAbsolute(const base::FilePath& current_directory, 63 base::FilePath* file_path) { 64 DCHECK(file_path); 65 if (file_path->IsAbsolute()) 66 return true; 67 68 if (current_directory.empty()) { 69 *file_path = base::MakeAbsoluteFilePath(*file_path); 70 return !file_path->empty(); 71 } 72 73 if (!current_directory.IsAbsolute()) 74 return false; 75 76 *file_path = current_directory.Append(*file_path); 77 return true; 78 } 79 80 bool GetAbsolutePathFromCommandLine(const CommandLine* command_line, 81 const base::FilePath& current_directory, 82 base::FilePath* path) { 83 if (!command_line || !command_line->GetArgs().size()) 84 return false; 85 86 base::FilePath relative_path(command_line->GetArgs()[0]); 87 base::FilePath absolute_path(relative_path); 88 if (!MakePathAbsolute(current_directory, &absolute_path)) { 89 LOG(WARNING) << "Cannot make absolute path from " << relative_path.value(); 90 return false; 91 } 92 *path = absolute_path; 93 return true; 94 } 95 96 // Helper method to launch the platform app |extension| with no data. This 97 // should be called in the fallback case, where it has been impossible to 98 // load or obtain file launch data. 99 void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) { 100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 101 extensions::AppEventRouter::DispatchOnLaunchedEvent(profile, extension); 102 } 103 104 // Class to handle launching of platform apps to open a specific path. 105 // An instance of this class is created for each launch. The lifetime of these 106 // instances is managed by reference counted pointers. As long as an instance 107 // has outstanding tasks on a message queue it will be retained; once all 108 // outstanding tasks are completed it will be deleted. 109 class PlatformAppPathLauncher 110 : public base::RefCountedThreadSafe<PlatformAppPathLauncher> { 111 public: 112 PlatformAppPathLauncher(Profile* profile, 113 const Extension* extension, 114 const base::FilePath& file_path) 115 : profile_(profile), extension_(extension), file_path_(file_path) {} 116 117 void Launch() { 118 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 119 if (file_path_.empty()) { 120 LaunchPlatformAppWithNoData(profile_, extension_); 121 return; 122 } 123 124 DCHECK(file_path_.IsAbsolute()); 125 126 #if defined(OS_CHROMEOS) 127 if (drive::util::IsUnderDriveMountPoint(file_path_)) { 128 GetMimeTypeAndLaunchForDriveFile(); 129 return; 130 } 131 #endif 132 133 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind( 134 &PlatformAppPathLauncher::GetMimeTypeAndLaunch, this)); 135 } 136 137 void LaunchWithHandler(const std::string& handler_id) { 138 handler_id_ = handler_id; 139 Launch(); 140 } 141 142 private: 143 friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>; 144 145 virtual ~PlatformAppPathLauncher() {} 146 147 void GetMimeTypeAndLaunch() { 148 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 149 150 // If the file doesn't exist, or is a directory, launch with no launch data. 151 if (!base::PathExists(file_path_) || 152 base::DirectoryExists(file_path_)) { 153 LOG(WARNING) << "No file exists with path " << file_path_.value(); 154 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 155 &PlatformAppPathLauncher::LaunchWithNoLaunchData, this)); 156 return; 157 } 158 159 std::string mime_type; 160 if (!net::GetMimeTypeFromFile(file_path_, &mime_type)) 161 mime_type = kFallbackMimeType; 162 163 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 164 &PlatformAppPathLauncher::LaunchWithMimeType, this, mime_type)); 165 } 166 167 #if defined(OS_CHROMEOS) 168 void GetMimeTypeAndLaunchForDriveFile() { 169 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 170 171 drive::DriveIntegrationService* service = 172 drive::DriveIntegrationServiceFactory::FindForProfile(profile_); 173 if (!service) { 174 LaunchWithNoLaunchData(); 175 return; 176 } 177 178 service->file_system()->GetFileByPath( 179 drive::util::ExtractDrivePath(file_path_), 180 base::Bind(&PlatformAppPathLauncher::OnGotDriveFile, this)); 181 } 182 183 void OnGotDriveFile(drive::FileError error, 184 const base::FilePath& file_path, 185 scoped_ptr<drive::ResourceEntry> entry) { 186 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 187 188 if (error != drive::FILE_ERROR_OK || 189 !entry || entry->file_specific_info().is_hosted_document()) { 190 LaunchWithNoLaunchData(); 191 return; 192 } 193 194 const std::string& mime_type = 195 entry->file_specific_info().content_mime_type(); 196 LaunchWithMimeType(mime_type.empty() ? kFallbackMimeType : mime_type); 197 } 198 #endif // defined(OS_CHROMEOS) 199 200 void LaunchWithNoLaunchData() { 201 // This method is required as an entry point on the UI thread. 202 LaunchPlatformAppWithNoData(profile_, extension_); 203 } 204 205 void LaunchWithMimeType(const std::string& mime_type) { 206 // Find file handler from the platform app for the file being opened. 207 const extensions::FileHandlerInfo* handler = NULL; 208 if (!handler_id_.empty()) 209 handler = FileHandlerForId(*extension_, handler_id_); 210 else 211 handler = FirstFileHandlerForFile(*extension_, mime_type, file_path_); 212 if (handler && !FileHandlerCanHandleFile(*handler, mime_type, file_path_)) { 213 LOG(WARNING) << "Extension does not provide a valid file handler for " 214 << file_path_.value(); 215 LaunchWithNoLaunchData(); 216 return; 217 } 218 219 // If this app doesn't have a file handler that supports the file, launch 220 // with no launch data. 221 if (!handler) { 222 LOG(WARNING) << "Extension does not provide a valid file handler for " 223 << file_path_.value(); 224 LaunchWithNoLaunchData(); 225 return; 226 } 227 228 if (handler_id_.empty()) 229 handler_id_ = handler->id; 230 231 // Access needs to be granted to the file for the process associated with 232 // the extension. To do this the ExtensionHost is needed. This might not be 233 // available, or it might be in the process of being unloaded, in which case 234 // the lazy background task queue is used to load the extension and then 235 // call back to us. 236 extensions::LazyBackgroundTaskQueue* queue = 237 ExtensionSystem::Get(profile_)->lazy_background_task_queue(); 238 if (queue->ShouldEnqueueTask(profile_, extension_)) { 239 queue->AddPendingTask(profile_, extension_->id(), base::Bind( 240 &PlatformAppPathLauncher::GrantAccessToFileAndLaunch, 241 this, mime_type)); 242 return; 243 } 244 245 ExtensionProcessManager* process_manager = 246 ExtensionSystem::Get(profile_)->process_manager(); 247 ExtensionHost* host = 248 process_manager->GetBackgroundHostForExtension(extension_->id()); 249 DCHECK(host); 250 GrantAccessToFileAndLaunch(mime_type, host); 251 } 252 253 void GrantAccessToFileAndLaunch(const std::string& mime_type, 254 ExtensionHost* host) { 255 // If there was an error loading the app page, |host| will be NULL. 256 if (!host) { 257 LOG(ERROR) << "Could not load app page for " << extension_->id(); 258 return; 259 } 260 261 GrantedFileEntry file_entry = CreateFileEntry( 262 profile_, 263 extension_->id(), 264 host->render_process_host()->GetID(), 265 file_path_, 266 false); 267 extensions::AppEventRouter::DispatchOnLaunchedEventWithFileEntry( 268 profile_, extension_, handler_id_, mime_type, file_entry); 269 } 270 271 // The profile the app should be run in. 272 Profile* profile_; 273 // The extension providing the app. 274 const Extension* extension_; 275 // The path to be passed through to the app. 276 const base::FilePath file_path_; 277 // The ID of the file handler used to launch the app. 278 std::string handler_id_; 279 280 DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher); 281 }; 282 283 } // namespace 284 285 void LaunchPlatformAppWithCommandLine(Profile* profile, 286 const Extension* extension, 287 const CommandLine* command_line, 288 const base::FilePath& current_directory) { 289 #if defined(OS_WIN) 290 // On Windows 8's single window Metro mode we can not launch platform apps. 291 // Offer to switch Chrome to desktop mode. 292 if (win8::IsSingleWindowMetroMode()) { 293 AppMetroInfoBarDelegateWin::Create( 294 profile, AppMetroInfoBarDelegateWin::LAUNCH_PACKAGED_APP, 295 extension->id()); 296 return; 297 } 298 #endif 299 300 base::FilePath path; 301 if (!GetAbsolutePathFromCommandLine(command_line, current_directory, &path)) { 302 LaunchPlatformAppWithNoData(profile, extension); 303 return; 304 } 305 306 // TODO(benwells): add a command-line argument to provide a handler ID. 307 LaunchPlatformAppWithPath(profile, extension, path); 308 } 309 310 void LaunchPlatformAppWithPath(Profile* profile, 311 const Extension* extension, 312 const base::FilePath& file_path) { 313 // launcher will be freed when nothing has a reference to it. The message 314 // queue will retain a reference for any outstanding task, so when the 315 // launcher has finished it will be freed. 316 scoped_refptr<PlatformAppPathLauncher> launcher = 317 new PlatformAppPathLauncher(profile, extension, file_path); 318 launcher->Launch(); 319 } 320 321 void LaunchPlatformApp(Profile* profile, const Extension* extension) { 322 LaunchPlatformAppWithCommandLine(profile, extension, NULL, base::FilePath()); 323 } 324 325 void LaunchPlatformAppWithFileHandler(Profile* profile, 326 const Extension* extension, 327 const std::string& handler_id, 328 const base::FilePath& file_path) { 329 scoped_refptr<PlatformAppPathLauncher> launcher = 330 new PlatformAppPathLauncher(profile, extension, file_path); 331 launcher->LaunchWithHandler(handler_id); 332 } 333 334 void RestartPlatformApp(Profile* profile, const Extension* extension) { 335 #if defined(OS_WIN) 336 // On Windows 8's single window Metro mode we can not launch platform apps. 337 // In restart we are just making sure launch doesn't slip through. 338 if (win8::IsSingleWindowMetroMode()) 339 return; 340 #endif 341 extensions::EventRouter* event_router = 342 ExtensionSystem::Get(profile)->event_router(); 343 bool listening_to_restart = event_router-> 344 ExtensionHasEventListener(extension->id(), 345 extensions::event_names::kOnRestarted); 346 347 if (listening_to_restart) { 348 extensions::AppEventRouter::DispatchOnRestartedEvent(profile, extension); 349 return; 350 } 351 352 extensions::ExtensionPrefs* extension_prefs = ExtensionSystem::Get(profile)-> 353 extension_service()->extension_prefs(); 354 bool had_windows = extension_prefs->IsActive(extension->id()); 355 extension_prefs->SetIsActive(extension->id(), false); 356 bool listening_to_launch = event_router-> 357 ExtensionHasEventListener(extension->id(), 358 extensions::event_names::kOnLaunched); 359 360 if (listening_to_launch && had_windows) 361 LaunchPlatformAppWithNoData(profile, extension); 362 } 363 364 } // namespace apps 365