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