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