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