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 "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h" 6 7 #include "apps/app_lifetime_monitor_factory.h" 8 #include "apps/launcher.h" 9 #include "base/files/file_path.h" 10 #include "base/logging.h" 11 #include "chrome/browser/apps/app_shim/app_shim_host_manager_mac.h" 12 #include "chrome/browser/browser_process.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/browser/profiles/profile_manager.h" 16 #include "chrome/browser/ui/extensions/extension_enable_flow.h" 17 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h" 18 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h" 19 #include "chrome/browser/web_applications/web_app_mac.h" 20 #include "chrome/common/extensions/extension_constants.h" 21 #include "chrome/common/mac/app_shim_messages.h" 22 #include "components/crx_file/id_util.h" 23 #include "content/public/browser/notification_details.h" 24 #include "content/public/browser/notification_service.h" 25 #include "content/public/browser/notification_source.h" 26 #include "extensions/browser/app_window/app_window.h" 27 #include "extensions/browser/app_window/app_window_registry.h" 28 #include "extensions/browser/app_window/native_app_window.h" 29 #include "extensions/browser/extension_host.h" 30 #include "extensions/browser/extension_registry.h" 31 #include "ui/base/cocoa/focus_window_set.h" 32 33 using extensions::AppWindow; 34 using extensions::AppWindowRegistry; 35 using extensions::ExtensionRegistry; 36 37 namespace { 38 39 typedef AppWindowRegistry::AppWindowList AppWindowList; 40 41 void ProfileLoadedCallback(base::Callback<void(Profile*)> callback, 42 Profile* profile, 43 Profile::CreateStatus status) { 44 if (status == Profile::CREATE_STATUS_INITIALIZED) { 45 callback.Run(profile); 46 return; 47 } 48 49 // This should never get an error since it only loads existing profiles. 50 DCHECK_EQ(Profile::CREATE_STATUS_CREATED, status); 51 } 52 53 void SetAppHidden(Profile* profile, const std::string& app_id, bool hidden) { 54 AppWindowList windows = 55 AppWindowRegistry::Get(profile)->GetAppWindowsForApp(app_id); 56 for (AppWindowList::const_reverse_iterator it = windows.rbegin(); 57 it != windows.rend(); 58 ++it) { 59 if (hidden) 60 (*it)->GetBaseWindow()->HideWithApp(); 61 else 62 (*it)->GetBaseWindow()->ShowWithApp(); 63 } 64 } 65 66 bool FocusWindows(const AppWindowList& windows) { 67 if (windows.empty()) 68 return false; 69 70 std::set<gfx::NativeWindow> native_windows; 71 for (AppWindowList::const_iterator it = windows.begin(); it != windows.end(); 72 ++it) { 73 native_windows.insert((*it)->GetNativeWindow()); 74 } 75 // Allow workspace switching. For the browser process, we can reasonably rely 76 // on OS X to switch spaces for us and honor relevant user settings. But shims 77 // don't have windows, so we have to do it ourselves. 78 ui::FocusWindowSet(native_windows); 79 return true; 80 } 81 82 // Attempts to launch a packaged app, prompting the user to enable it if 83 // necessary. The prompt is shown in its own window. 84 // This class manages its own lifetime. 85 class EnableViaPrompt : public ExtensionEnableFlowDelegate { 86 public: 87 EnableViaPrompt(Profile* profile, 88 const std::string& extension_id, 89 const base::Callback<void()>& callback) 90 : profile_(profile), 91 extension_id_(extension_id), 92 callback_(callback) { 93 } 94 95 virtual ~EnableViaPrompt() { 96 } 97 98 void Run() { 99 flow_.reset(new ExtensionEnableFlow(profile_, extension_id_, this)); 100 flow_->StartForCurrentlyNonexistentWindow( 101 base::Callback<gfx::NativeWindow(void)>()); 102 } 103 104 private: 105 // ExtensionEnableFlowDelegate overrides. 106 virtual void ExtensionEnableFlowFinished() OVERRIDE { 107 callback_.Run(); 108 delete this; 109 } 110 111 virtual void ExtensionEnableFlowAborted(bool user_initiated) OVERRIDE { 112 callback_.Run(); 113 delete this; 114 } 115 116 Profile* profile_; 117 std::string extension_id_; 118 base::Callback<void()> callback_; 119 scoped_ptr<ExtensionEnableFlow> flow_; 120 121 DISALLOW_COPY_AND_ASSIGN(EnableViaPrompt); 122 }; 123 124 } // namespace 125 126 namespace apps { 127 128 bool ExtensionAppShimHandler::Delegate::ProfileExistsForPath( 129 const base::FilePath& path) { 130 ProfileManager* profile_manager = g_browser_process->profile_manager(); 131 // Check for the profile name in the profile info cache to ensure that we 132 // never access any directory that isn't a known profile. 133 base::FilePath full_path = profile_manager->user_data_dir().Append(path); 134 ProfileInfoCache& cache = profile_manager->GetProfileInfoCache(); 135 return cache.GetIndexOfProfileWithPath(full_path) != std::string::npos; 136 } 137 138 Profile* ExtensionAppShimHandler::Delegate::ProfileForPath( 139 const base::FilePath& path) { 140 ProfileManager* profile_manager = g_browser_process->profile_manager(); 141 base::FilePath full_path = profile_manager->user_data_dir().Append(path); 142 Profile* profile = profile_manager->GetProfileByPath(full_path); 143 144 // Use IsValidProfile to check if the profile has been created. 145 return profile && profile_manager->IsValidProfile(profile) ? profile : NULL; 146 } 147 148 void ExtensionAppShimHandler::Delegate::LoadProfileAsync( 149 const base::FilePath& path, 150 base::Callback<void(Profile*)> callback) { 151 ProfileManager* profile_manager = g_browser_process->profile_manager(); 152 base::FilePath full_path = profile_manager->user_data_dir().Append(path); 153 profile_manager->CreateProfileAsync( 154 full_path, 155 base::Bind(&ProfileLoadedCallback, callback), 156 base::string16(), base::string16(), std::string()); 157 } 158 159 AppWindowList ExtensionAppShimHandler::Delegate::GetWindows( 160 Profile* profile, 161 const std::string& extension_id) { 162 return AppWindowRegistry::Get(profile)->GetAppWindowsForApp(extension_id); 163 } 164 165 const extensions::Extension* 166 ExtensionAppShimHandler::Delegate::GetAppExtension( 167 Profile* profile, 168 const std::string& extension_id) { 169 ExtensionRegistry* registry = ExtensionRegistry::Get(profile); 170 const extensions::Extension* extension = 171 registry->GetExtensionById(extension_id, ExtensionRegistry::ENABLED); 172 return extension && extension->is_platform_app() ? extension : NULL; 173 } 174 175 void ExtensionAppShimHandler::Delegate::EnableExtension( 176 Profile* profile, 177 const std::string& extension_id, 178 const base::Callback<void()>& callback) { 179 (new EnableViaPrompt(profile, extension_id, callback))->Run(); 180 } 181 182 void ExtensionAppShimHandler::Delegate::LaunchApp( 183 Profile* profile, 184 const extensions::Extension* extension, 185 const std::vector<base::FilePath>& files) { 186 CoreAppLauncherHandler::RecordAppLaunchType( 187 extension_misc::APP_LAUNCH_CMD_LINE_APP, extension->GetType()); 188 if (files.empty()) { 189 apps::LaunchPlatformApp(profile, extension); 190 } else { 191 for (std::vector<base::FilePath>::const_iterator it = files.begin(); 192 it != files.end(); ++it) { 193 apps::LaunchPlatformAppWithPath(profile, extension, *it); 194 } 195 } 196 } 197 198 void ExtensionAppShimHandler::Delegate::LaunchShim( 199 Profile* profile, 200 const extensions::Extension* extension) { 201 web_app::MaybeLaunchShortcut( 202 web_app::ShortcutInfoForExtensionAndProfile(extension, profile)); 203 } 204 205 void ExtensionAppShimHandler::Delegate::MaybeTerminate() { 206 AppShimHandler::MaybeTerminate(); 207 } 208 209 ExtensionAppShimHandler::ExtensionAppShimHandler() 210 : delegate_(new Delegate), 211 weak_factory_(this) { 212 // This is instantiated in BrowserProcessImpl::PreMainMessageLoopRun with 213 // AppShimHostManager. Since PROFILE_CREATED is not fired until 214 // ProfileManager::GetLastUsedProfile/GetLastOpenedProfiles, this should catch 215 // notifications for all profiles. 216 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED, 217 content::NotificationService::AllBrowserContextsAndSources()); 218 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, 219 content::NotificationService::AllBrowserContextsAndSources()); 220 } 221 222 ExtensionAppShimHandler::~ExtensionAppShimHandler() {} 223 224 AppShimHandler::Host* ExtensionAppShimHandler::FindHost( 225 Profile* profile, 226 const std::string& app_id) { 227 HostMap::iterator it = hosts_.find(make_pair(profile, app_id)); 228 return it == hosts_.end() ? NULL : it->second; 229 } 230 231 // static 232 void ExtensionAppShimHandler::QuitAppForWindow(AppWindow* app_window) { 233 ExtensionAppShimHandler* handler = GetInstance(); 234 Host* host = handler->FindHost( 235 Profile::FromBrowserContext(app_window->browser_context()), 236 app_window->extension_id()); 237 if (host) { 238 handler->OnShimQuit(host); 239 } else { 240 // App shims might be disabled or the shim is still starting up. 241 AppWindowRegistry::Get( 242 Profile::FromBrowserContext(app_window->browser_context())) 243 ->CloseAllAppWindowsForApp(app_window->extension_id()); 244 } 245 } 246 247 void ExtensionAppShimHandler::HideAppForWindow(AppWindow* app_window) { 248 ExtensionAppShimHandler* handler = GetInstance(); 249 Profile* profile = Profile::FromBrowserContext(app_window->browser_context()); 250 Host* host = handler->FindHost(profile, app_window->extension_id()); 251 if (host) 252 host->OnAppHide(); 253 else 254 SetAppHidden(profile, app_window->extension_id(), true); 255 } 256 257 void ExtensionAppShimHandler::FocusAppForWindow(AppWindow* app_window) { 258 ExtensionAppShimHandler* handler = GetInstance(); 259 Profile* profile = Profile::FromBrowserContext(app_window->browser_context()); 260 const std::string& app_id = app_window->extension_id(); 261 Host* host = handler->FindHost(profile, app_id); 262 if (host) { 263 handler->OnShimFocus(host, 264 APP_SHIM_FOCUS_NORMAL, 265 std::vector<base::FilePath>()); 266 } else { 267 FocusWindows(AppWindowRegistry::Get(profile)->GetAppWindowsForApp(app_id)); 268 } 269 } 270 271 // static 272 bool ExtensionAppShimHandler::ActivateAndRequestUserAttentionForWindow( 273 AppWindow* app_window) { 274 ExtensionAppShimHandler* handler = GetInstance(); 275 Profile* profile = Profile::FromBrowserContext(app_window->browser_context()); 276 Host* host = handler->FindHost(profile, app_window->extension_id()); 277 if (host) { 278 // Bring the window to the front without showing it. 279 AppWindowRegistry::Get(profile)->AppWindowActivated(app_window); 280 host->OnAppRequestUserAttention(APP_SHIM_ATTENTION_INFORMATIONAL); 281 return true; 282 } else { 283 // Just show the app. 284 SetAppHidden(profile, app_window->extension_id(), false); 285 return false; 286 } 287 } 288 289 // static 290 void ExtensionAppShimHandler::RequestUserAttentionForWindow( 291 AppWindow* app_window, 292 AppShimAttentionType attention_type) { 293 ExtensionAppShimHandler* handler = GetInstance(); 294 Profile* profile = Profile::FromBrowserContext(app_window->browser_context()); 295 Host* host = handler->FindHost(profile, app_window->extension_id()); 296 if (host) 297 host->OnAppRequestUserAttention(attention_type); 298 } 299 300 // static 301 void ExtensionAppShimHandler::OnChromeWillHide() { 302 // Send OnAppHide to all the shims so that they go into the hidden state. 303 // This is necessary so that when the shim is next focused, it will know to 304 // unhide. 305 ExtensionAppShimHandler* handler = GetInstance(); 306 for (HostMap::iterator it = handler->hosts_.begin(); 307 it != handler->hosts_.end(); 308 ++it) { 309 it->second->OnAppHide(); 310 } 311 } 312 313 void ExtensionAppShimHandler::OnShimLaunch( 314 Host* host, 315 AppShimLaunchType launch_type, 316 const std::vector<base::FilePath>& files) { 317 const std::string& app_id = host->GetAppId(); 318 DCHECK(crx_file::id_util::IdIsValid(app_id)); 319 320 const base::FilePath& profile_path = host->GetProfilePath(); 321 DCHECK(!profile_path.empty()); 322 323 if (!delegate_->ProfileExistsForPath(profile_path)) { 324 // User may have deleted the profile this shim was originally created for. 325 // TODO(jackhou): Add some UI for this case and remove the LOG. 326 LOG(ERROR) << "Requested directory is not a known profile '" 327 << profile_path.value() << "'."; 328 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND); 329 return; 330 } 331 332 Profile* profile = delegate_->ProfileForPath(profile_path); 333 334 if (profile) { 335 OnProfileLoaded(host, launch_type, files, profile); 336 return; 337 } 338 339 // If the profile is not loaded, this must have been a launch by the shim. 340 // Load the profile asynchronously, the host will be registered in 341 // OnProfileLoaded. 342 DCHECK_EQ(APP_SHIM_LAUNCH_NORMAL, launch_type); 343 delegate_->LoadProfileAsync( 344 profile_path, 345 base::Bind(&ExtensionAppShimHandler::OnProfileLoaded, 346 weak_factory_.GetWeakPtr(), 347 host, launch_type, files)); 348 349 // Return now. OnAppLaunchComplete will be called when the app is activated. 350 } 351 352 // static 353 ExtensionAppShimHandler* ExtensionAppShimHandler::GetInstance() { 354 return g_browser_process->platform_part() 355 ->app_shim_host_manager() 356 ->extension_app_shim_handler(); 357 } 358 359 void ExtensionAppShimHandler::OnProfileLoaded( 360 Host* host, 361 AppShimLaunchType launch_type, 362 const std::vector<base::FilePath>& files, 363 Profile* profile) { 364 const std::string& app_id = host->GetAppId(); 365 366 // The first host to claim this (profile, app_id) becomes the main host. 367 // For any others, focus or relaunch the app. 368 if (!hosts_.insert(make_pair(make_pair(profile, app_id), host)).second) { 369 OnShimFocus(host, 370 launch_type == APP_SHIM_LAUNCH_NORMAL ? 371 APP_SHIM_FOCUS_REOPEN : APP_SHIM_FOCUS_NORMAL, 372 files); 373 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST); 374 return; 375 } 376 377 if (launch_type != APP_SHIM_LAUNCH_NORMAL) { 378 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS); 379 return; 380 } 381 382 // TODO(jeremya): Handle the case that launching the app fails. Probably we 383 // need to watch for 'app successfully launched' or at least 'background page 384 // exists/was created' and time out with failure if we don't see that sign of 385 // life within a certain window. 386 const extensions::Extension* extension = 387 delegate_->GetAppExtension(profile, app_id); 388 if (extension) { 389 delegate_->LaunchApp(profile, extension, files); 390 return; 391 } 392 393 delegate_->EnableExtension( 394 profile, app_id, 395 base::Bind(&ExtensionAppShimHandler::OnExtensionEnabled, 396 weak_factory_.GetWeakPtr(), 397 host->GetProfilePath(), app_id, files)); 398 } 399 400 void ExtensionAppShimHandler::OnExtensionEnabled( 401 const base::FilePath& profile_path, 402 const std::string& app_id, 403 const std::vector<base::FilePath>& files) { 404 Profile* profile = delegate_->ProfileForPath(profile_path); 405 if (!profile) 406 return; 407 408 const extensions::Extension* extension = 409 delegate_->GetAppExtension(profile, app_id); 410 if (!extension || !delegate_->ProfileExistsForPath(profile_path)) { 411 // If !extension, the extension doesn't exist, or was not re-enabled. 412 // If the profile doesn't exist, it may have been deleted during the enable 413 // prompt. In this case, NOTIFICATION_PROFILE_DESTROYED may not be fired 414 // until later, so respond to the host now. 415 Host* host = FindHost(profile, app_id); 416 if (host) 417 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND); 418 return; 419 } 420 421 delegate_->LaunchApp(profile, extension, files); 422 } 423 424 425 void ExtensionAppShimHandler::OnShimClose(Host* host) { 426 // This might be called when shutting down. Don't try to look up the profile 427 // since profile_manager might not be around. 428 for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) { 429 HostMap::iterator current = it++; 430 if (current->second == host) 431 hosts_.erase(current); 432 } 433 } 434 435 void ExtensionAppShimHandler::OnShimFocus( 436 Host* host, 437 AppShimFocusType focus_type, 438 const std::vector<base::FilePath>& files) { 439 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath())); 440 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath()); 441 442 const AppWindowList windows = 443 delegate_->GetWindows(profile, host->GetAppId()); 444 bool windows_focused = FocusWindows(windows); 445 446 if (focus_type == APP_SHIM_FOCUS_NORMAL || 447 (focus_type == APP_SHIM_FOCUS_REOPEN && windows_focused)) { 448 return; 449 } 450 451 const extensions::Extension* extension = 452 delegate_->GetAppExtension(profile, host->GetAppId()); 453 if (extension) { 454 delegate_->LaunchApp(profile, extension, files); 455 } else { 456 // Extensions may have been uninstalled or disabled since the shim 457 // started. 458 host->OnAppClosed(); 459 } 460 } 461 462 void ExtensionAppShimHandler::OnShimSetHidden(Host* host, bool hidden) { 463 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath())); 464 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath()); 465 466 SetAppHidden(profile, host->GetAppId(), hidden); 467 } 468 469 void ExtensionAppShimHandler::OnShimQuit(Host* host) { 470 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath())); 471 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath()); 472 473 const std::string& app_id = host->GetAppId(); 474 const AppWindowList windows = delegate_->GetWindows(profile, app_id); 475 for (AppWindowRegistry::const_iterator it = windows.begin(); 476 it != windows.end(); 477 ++it) { 478 (*it)->GetBaseWindow()->Close(); 479 } 480 // Once the last window closes, flow will end up in OnAppDeactivated via 481 // AppLifetimeMonitor. 482 } 483 484 void ExtensionAppShimHandler::set_delegate(Delegate* delegate) { 485 delegate_.reset(delegate); 486 } 487 488 void ExtensionAppShimHandler::Observe( 489 int type, 490 const content::NotificationSource& source, 491 const content::NotificationDetails& details) { 492 Profile* profile = content::Source<Profile>(source).ptr(); 493 if (profile->IsOffTheRecord()) 494 return; 495 496 switch (type) { 497 case chrome::NOTIFICATION_PROFILE_CREATED: { 498 AppLifetimeMonitorFactory::GetForProfile(profile)->AddObserver(this); 499 break; 500 } 501 case chrome::NOTIFICATION_PROFILE_DESTROYED: { 502 AppLifetimeMonitorFactory::GetForProfile(profile)->RemoveObserver(this); 503 // Shut down every shim associated with this profile. 504 for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) { 505 // Increment the iterator first as OnAppClosed may call back to 506 // OnShimClose and invalidate the iterator. 507 HostMap::iterator current = it++; 508 if (profile->IsSameProfile(current->first.first)) { 509 Host* host = current->second; 510 host->OnAppClosed(); 511 } 512 } 513 break; 514 } 515 default: { 516 NOTREACHED(); // Unexpected notification. 517 break; 518 } 519 } 520 } 521 522 void ExtensionAppShimHandler::OnAppStart(Profile* profile, 523 const std::string& app_id) {} 524 525 void ExtensionAppShimHandler::OnAppActivated(Profile* profile, 526 const std::string& app_id) { 527 const extensions::Extension* extension = 528 delegate_->GetAppExtension(profile, app_id); 529 if (!extension) 530 return; 531 532 Host* host = FindHost(profile, app_id); 533 if (host) { 534 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS); 535 OnShimFocus(host, APP_SHIM_FOCUS_NORMAL, std::vector<base::FilePath>()); 536 return; 537 } 538 539 delegate_->LaunchShim(profile, extension); 540 } 541 542 void ExtensionAppShimHandler::OnAppDeactivated(Profile* profile, 543 const std::string& app_id) { 544 Host* host = FindHost(profile, app_id); 545 if (host) 546 host->OnAppClosed(); 547 548 if (hosts_.empty()) 549 delegate_->MaybeTerminate(); 550 } 551 552 void ExtensionAppShimHandler::OnAppStop(Profile* profile, 553 const std::string& app_id) {} 554 555 void ExtensionAppShimHandler::OnChromeTerminating() {} 556 557 } // namespace apps 558