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