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/app_shim/extension_app_shim_handler_mac.h" 6 7 #include "apps/app_lifetime_monitor_factory.h" 8 #include "apps/app_shim/app_shim_host_manager_mac.h" 9 #include "apps/app_shim/app_shim_messages.h" 10 #include "apps/native_app_window.h" 11 #include "apps/shell_window.h" 12 #include "apps/shell_window_registry.h" 13 #include "base/files/file_path.h" 14 #include "base/logging.h" 15 #include "chrome/browser/browser_process.h" 16 #include "chrome/browser/chrome_notification_types.h" 17 #include "chrome/browser/extensions/extension_host.h" 18 #include "chrome/browser/extensions/extension_service.h" 19 #include "chrome/browser/extensions/extension_system.h" 20 #include "chrome/browser/lifetime/application_lifetime.h" 21 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/profiles/profile_manager.h" 23 #include "chrome/browser/ui/extensions/application_launch.h" 24 #include "chrome/browser/ui/web_applications/web_app_ui.h" 25 #include "chrome/browser/web_applications/web_app_mac.h" 26 #include "content/public/browser/notification_details.h" 27 #include "content/public/browser/notification_service.h" 28 #include "content/public/browser/notification_source.h" 29 #include "ui/base/cocoa/focus_window_set.h" 30 31 namespace { 32 33 void ProfileLoadedCallback(base::Callback<void(Profile*)> callback, 34 Profile* profile, 35 Profile::CreateStatus status) { 36 if (status == Profile::CREATE_STATUS_INITIALIZED) { 37 callback.Run(profile); 38 return; 39 } 40 41 // This should never get an error since it only loads existing profiles. 42 DCHECK_EQ(Profile::CREATE_STATUS_CREATED, status); 43 } 44 45 void TerminateIfNoShellWindows() { 46 bool shell_windows_left = 47 apps::ShellWindowRegistry::IsShellWindowRegisteredInAnyProfile(0); 48 if (!shell_windows_left) 49 chrome::AttemptExit(); 50 } 51 52 } // namespace 53 54 namespace apps { 55 56 typedef ShellWindowRegistry::ShellWindowList ShellWindowList; 57 58 bool ExtensionAppShimHandler::Delegate::ProfileExistsForPath( 59 const base::FilePath& path) { 60 ProfileManager* profile_manager = g_browser_process->profile_manager(); 61 // Check for the profile name in the profile info cache to ensure that we 62 // never access any directory that isn't a known profile. 63 base::FilePath full_path = profile_manager->user_data_dir().Append(path); 64 ProfileInfoCache& cache = profile_manager->GetProfileInfoCache(); 65 return cache.GetIndexOfProfileWithPath(full_path) != std::string::npos; 66 } 67 68 Profile* ExtensionAppShimHandler::Delegate::ProfileForPath( 69 const base::FilePath& path) { 70 ProfileManager* profile_manager = g_browser_process->profile_manager(); 71 base::FilePath full_path = profile_manager->user_data_dir().Append(path); 72 Profile* profile = profile_manager->GetProfileByPath(full_path); 73 74 // Use IsValidProfile to check if the profile has been created. 75 return profile && profile_manager->IsValidProfile(profile) ? profile : NULL; 76 } 77 78 void ExtensionAppShimHandler::Delegate::LoadProfileAsync( 79 const base::FilePath& path, 80 base::Callback<void(Profile*)> callback) { 81 ProfileManager* profile_manager = g_browser_process->profile_manager(); 82 base::FilePath full_path = profile_manager->user_data_dir().Append(path); 83 profile_manager->CreateProfileAsync( 84 full_path, 85 base::Bind(&ProfileLoadedCallback, callback), 86 string16(), string16(), std::string()); 87 } 88 89 ShellWindowList ExtensionAppShimHandler::Delegate::GetWindows( 90 Profile* profile, 91 const std::string& extension_id) { 92 return ShellWindowRegistry::Get(profile)->GetShellWindowsForApp(extension_id); 93 } 94 95 const extensions::Extension* 96 ExtensionAppShimHandler::Delegate::GetAppExtension( 97 Profile* profile, 98 const std::string& extension_id) { 99 ExtensionService* extension_service = 100 extensions::ExtensionSystem::Get(profile)->extension_service(); 101 DCHECK(extension_service); 102 const extensions::Extension* extension = 103 extension_service->GetExtensionById(extension_id, false); 104 return extension && extension->is_platform_app() ? extension : NULL; 105 } 106 107 void ExtensionAppShimHandler::Delegate::LaunchApp( 108 Profile* profile, 109 const extensions::Extension* extension) { 110 chrome::OpenApplication( 111 chrome::AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB)); 112 } 113 114 void ExtensionAppShimHandler::Delegate::LaunchShim( 115 Profile* profile, 116 const extensions::Extension* extension) { 117 web_app::MaybeLaunchShortcut( 118 web_app::ShortcutInfoForExtensionAndProfile(extension, profile)); 119 } 120 121 void ExtensionAppShimHandler::Delegate::MaybeTerminate() { 122 // Post this to give ShellWindows a chance to remove themselves from the 123 // registry. 124 base::MessageLoop::current()->PostTask( 125 FROM_HERE, 126 base::Bind(&TerminateIfNoShellWindows)); 127 } 128 129 ExtensionAppShimHandler::ExtensionAppShimHandler() 130 : delegate_(new Delegate), 131 browser_opened_ever_(false), 132 weak_factory_(this) { 133 // This is instantiated in BrowserProcessImpl::PreMainMessageLoopRun with 134 // AppShimHostManager. Since PROFILE_CREATED is not fired until 135 // ProfileManager::GetLastUsedProfile/GetLastOpenedProfiles, this should catch 136 // notifications for all profiles. 137 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED, 138 content::NotificationService::AllBrowserContextsAndSources()); 139 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, 140 content::NotificationService::AllBrowserContextsAndSources()); 141 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, 142 content::NotificationService::AllBrowserContextsAndSources()); 143 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED, 144 content::NotificationService::AllBrowserContextsAndSources()); 145 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED, 146 content::NotificationService::AllBrowserContextsAndSources()); 147 } 148 149 ExtensionAppShimHandler::~ExtensionAppShimHandler() {} 150 151 AppShimHandler::Host* ExtensionAppShimHandler::FindHost( 152 Profile* profile, 153 const std::string& app_id) { 154 HostMap::iterator it = hosts_.find(make_pair(profile, app_id)); 155 return it == hosts_.end() ? NULL : it->second; 156 } 157 158 // static 159 void ExtensionAppShimHandler::QuitAppForWindow(ShellWindow* shell_window) { 160 ExtensionAppShimHandler* handler = 161 g_browser_process->platform_part()->app_shim_host_manager()-> 162 extension_app_shim_handler(); 163 Host* host = handler->FindHost(shell_window->profile(), 164 shell_window->extension_id()); 165 if (host) { 166 handler->OnShimQuit(host); 167 } else { 168 // App shims might be disabled or the shim is still starting up. 169 ShellWindowRegistry::Get(shell_window->profile())-> 170 CloseAllShellWindowsForApp(shell_window->extension_id()); 171 } 172 } 173 174 void ExtensionAppShimHandler::OnShimLaunch(Host* host, 175 AppShimLaunchType launch_type) { 176 const std::string& app_id = host->GetAppId(); 177 DCHECK(extensions::Extension::IdIsValid(app_id)); 178 179 const base::FilePath& profile_path = host->GetProfilePath(); 180 DCHECK(!profile_path.empty()); 181 182 if (!delegate_->ProfileExistsForPath(profile_path)) { 183 // User may have deleted the profile this shim was originally created for. 184 // TODO(jackhou): Add some UI for this case and remove the LOG. 185 LOG(ERROR) << "Requested directory is not a known profile '" 186 << profile_path.value() << "'."; 187 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_PROFILE_NOT_FOUND); 188 return; 189 } 190 191 Profile* profile = delegate_->ProfileForPath(profile_path); 192 193 if (profile) { 194 OnProfileLoaded(host, launch_type, profile); 195 return; 196 } 197 198 // If the profile is not loaded, this must have been a launch by the shim. 199 // Load the profile asynchronously, the host will be registered in 200 // OnProfileLoaded. 201 DCHECK_EQ(APP_SHIM_LAUNCH_NORMAL, launch_type); 202 delegate_->LoadProfileAsync( 203 profile_path, 204 base::Bind(&ExtensionAppShimHandler::OnProfileLoaded, 205 weak_factory_.GetWeakPtr(), 206 host, launch_type)); 207 208 // Return now. OnAppLaunchComplete will be called when the app is activated. 209 } 210 211 void ExtensionAppShimHandler::OnProfileLoaded(Host* host, 212 AppShimLaunchType launch_type, 213 Profile* profile) { 214 const std::string& app_id = host->GetAppId(); 215 // TODO(jackhou): Add some UI for this case and remove the LOG. 216 const extensions::Extension* extension = 217 delegate_->GetAppExtension(profile, app_id); 218 if (!extension) { 219 LOG(ERROR) << "Attempted to launch nonexistent app with id '" 220 << app_id << "'."; 221 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_APP_NOT_FOUND); 222 return; 223 } 224 225 // The first host to claim this (profile, app_id) becomes the main host. 226 // For any others, focus or relaunch the app. 227 if (!hosts_.insert(make_pair(make_pair(profile, app_id), host)).second) { 228 OnShimFocus(host, 229 launch_type == APP_SHIM_LAUNCH_NORMAL ? 230 APP_SHIM_FOCUS_REOPEN : APP_SHIM_FOCUS_NORMAL); 231 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_DUPLICATE_HOST); 232 return; 233 } 234 235 // TODO(jeremya): Handle the case that launching the app fails. Probably we 236 // need to watch for 'app successfully launched' or at least 'background page 237 // exists/was created' and time out with failure if we don't see that sign of 238 // life within a certain window. 239 if (launch_type == APP_SHIM_LAUNCH_NORMAL) 240 delegate_->LaunchApp(profile, extension); 241 else 242 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS); 243 } 244 245 void ExtensionAppShimHandler::OnShimClose(Host* host) { 246 // This might be called when shutting down. Don't try to look up the profile 247 // since profile_manager might not be around. 248 for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) { 249 HostMap::iterator current = it++; 250 if (current->second == host) 251 hosts_.erase(current); 252 } 253 } 254 255 void ExtensionAppShimHandler::OnShimFocus(Host* host, 256 AppShimFocusType focus_type) { 257 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath())); 258 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath()); 259 260 const ShellWindowList windows = 261 delegate_->GetWindows(profile, host->GetAppId()); 262 std::set<gfx::NativeWindow> native_windows; 263 for (ShellWindowList::const_iterator it = windows.begin(); 264 it != windows.end(); ++it) { 265 native_windows.insert((*it)->GetNativeWindow()); 266 } 267 if (!native_windows.empty()) { 268 ui::FocusWindowSet(native_windows); 269 return; 270 } 271 272 if (focus_type == APP_SHIM_FOCUS_REOPEN) { 273 const extensions::Extension* extension = 274 delegate_->GetAppExtension(profile, host->GetAppId()); 275 if (extension) { 276 delegate_->LaunchApp(profile, extension); 277 } else { 278 // Extensions may have been uninstalled or disabled since the shim 279 // started. 280 host->OnAppClosed(); 281 } 282 } 283 } 284 285 void ExtensionAppShimHandler::OnShimSetHidden(Host* host, bool hidden) { 286 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath())); 287 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath()); 288 289 const ShellWindowList windows = 290 delegate_->GetWindows(profile, host->GetAppId()); 291 for (ShellWindowList::const_reverse_iterator it = windows.rbegin(); 292 it != windows.rend(); ++it) { 293 if (hidden) 294 (*it)->GetBaseWindow()->Hide(); 295 else 296 (*it)->GetBaseWindow()->ShowInactive(); 297 } 298 } 299 300 void ExtensionAppShimHandler::OnShimQuit(Host* host) { 301 DCHECK(delegate_->ProfileExistsForPath(host->GetProfilePath())); 302 Profile* profile = delegate_->ProfileForPath(host->GetProfilePath()); 303 304 const std::string& app_id = host->GetAppId(); 305 const ShellWindowList windows = 306 delegate_->GetWindows(profile, app_id); 307 for (ShellWindowRegistry::const_iterator it = windows.begin(); 308 it != windows.end(); ++it) { 309 (*it)->GetBaseWindow()->Close(); 310 } 311 312 DCHECK_NE(0u, hosts_.count(make_pair(profile, app_id))); 313 host->OnAppClosed(); 314 315 if (!browser_opened_ever_ && hosts_.empty()) 316 delegate_->MaybeTerminate(); 317 } 318 319 void ExtensionAppShimHandler::set_delegate(Delegate* delegate) { 320 delegate_.reset(delegate); 321 } 322 323 void ExtensionAppShimHandler::Observe( 324 int type, 325 const content::NotificationSource& source, 326 const content::NotificationDetails& details) { 327 if (type == chrome::NOTIFICATION_BROWSER_OPENED) { 328 registrar_.Remove( 329 this, chrome::NOTIFICATION_BROWSER_OPENED, 330 content::NotificationService::AllBrowserContextsAndSources()); 331 browser_opened_ever_ = true; 332 return; 333 } 334 335 Profile* profile = content::Source<Profile>(source).ptr(); 336 if (profile->IsOffTheRecord()) 337 return; 338 339 switch (type) { 340 case chrome::NOTIFICATION_PROFILE_CREATED: { 341 AppLifetimeMonitorFactory::GetForProfile(profile)->AddObserver(this); 342 break; 343 } 344 case chrome::NOTIFICATION_PROFILE_DESTROYED: { 345 AppLifetimeMonitorFactory::GetForProfile(profile)->RemoveObserver(this); 346 // Shut down every shim associated with this profile. 347 for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) { 348 // Increment the iterator first as OnAppClosed may call back to 349 // OnShimClose and invalidate the iterator. 350 HostMap::iterator current = it++; 351 if (profile->IsSameProfile(current->first.first)) 352 current->second->OnAppClosed(); 353 } 354 break; 355 } 356 case chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED: 357 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: { 358 std::string app_id; 359 if (type == chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED) { 360 app_id = content::Details<extensions::ExtensionHost>(details).ptr() 361 ->extension_id(); 362 } else { 363 app_id = content::Details<extensions::Extension>(details).ptr()->id(); 364 } 365 Host* host = FindHost(profile, app_id); 366 if (host) 367 host->OnAppClosed(); 368 break; 369 } 370 default: { 371 NOTREACHED(); // Unexpected notification. 372 break; 373 } 374 } 375 } 376 377 void ExtensionAppShimHandler::OnAppStart(Profile* profile, 378 const std::string& app_id) {} 379 380 void ExtensionAppShimHandler::OnAppActivated(Profile* profile, 381 const std::string& app_id) { 382 const extensions::Extension* extension = 383 delegate_->GetAppExtension(profile, app_id); 384 if (!extension) 385 return; 386 387 Host* host = FindHost(profile, app_id); 388 if (host) { 389 host->OnAppLaunchComplete(APP_SHIM_LAUNCH_SUCCESS); 390 OnShimFocus(host, APP_SHIM_FOCUS_NORMAL); 391 return; 392 } 393 394 delegate_->LaunchShim(profile, extension); 395 } 396 397 void ExtensionAppShimHandler::OnAppDeactivated(Profile* profile, 398 const std::string& app_id) {} 399 400 void ExtensionAppShimHandler::OnAppStop(Profile* profile, 401 const std::string& app_id) {} 402 403 void ExtensionAppShimHandler::OnChromeTerminating() {} 404 405 } // namespace apps 406