Home | History | Annotate | Download | only in apps
      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