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 "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