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/app_window.h"
     11 #include "apps/app_window_registry.h"
     12 #include "apps/launcher.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/profiles/profile.h"
     19 #include "chrome/browser/profiles/profile_manager.h"
     20 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
     21 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
     22 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
     23 #include "chrome/browser/web_applications/web_app_mac.h"
     24 #include "chrome/common/extensions/extension_constants.h"
     25 #include "content/public/browser/notification_details.h"
     26 #include "content/public/browser/notification_service.h"
     27 #include "content/public/browser/notification_source.h"
     28 #include "extensions/browser/extension_host.h"
     29 #include "extensions/browser/extension_registry.h"
     30 #include "ui/base/cocoa/focus_window_set.h"
     31 
     32 using extensions::ExtensionRegistry;
     33 
     34 namespace {
     35 
     36 typedef apps::AppWindowRegistry::AppWindowList AppWindowList;
     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   AppWindowList windows =
     52       apps::AppWindowRegistry::Get(profile)->GetAppWindowsForApp(app_id);
     53   for (AppWindowList::const_reverse_iterator it = windows.rbegin();
     54        it != windows.rend();
     55        ++it) {
     56     if (hidden)
     57       (*it)->GetBaseWindow()->HideWithApp();
     58     else
     59       (*it)->GetBaseWindow()->ShowWithApp();
     60   }
     61 }
     62 
     63 bool FocusWindows(const AppWindowList& windows) {
     64   if (windows.empty())
     65     return false;
     66 
     67   std::set<gfx::NativeWindow> native_windows;
     68   for (AppWindowList::const_iterator it = windows.begin(); it != windows.end();
     69        ++it) {
     70     native_windows.insert((*it)->GetNativeWindow());
     71   }
     72   // Allow workspace switching. For the browser process, we can reasonably rely
     73   // on OS X to switch spaces for us and honor relevant user settings. But shims
     74   // don't have windows, so we have to do it ourselves.
     75   ui::FocusWindowSet(native_windows);
     76   return true;
     77 }
     78 
     79 // Attempts to launch a packaged app, prompting the user to enable it if
     80 // necessary. The prompt is shown in its own window.
     81 // This class manages its own lifetime.
     82 class EnableViaPrompt : public ExtensionEnableFlowDelegate {
     83  public:
     84   EnableViaPrompt(Profile* profile,
     85                   const std::string& extension_id,
     86                   const base::Callback<void()>& callback)
     87       : profile_(profile),
     88         extension_id_(extension_id),
     89         callback_(callback) {
     90   }
     91 
     92   virtual ~EnableViaPrompt() {
     93   }
     94 
     95   void Run() {
     96     flow_.reset(new ExtensionEnableFlow(profile_, extension_id_, this));
     97     flow_->StartForCurrentlyNonexistentWindow(
     98         base::Callback<gfx::NativeWindow(void)>());
     99   }
    100 
    101  private:
    102   // ExtensionEnableFlowDelegate overrides.
    103   virtual void ExtensionEnableFlowFinished() OVERRIDE {
    104     callback_.Run();
    105     delete this;
    106   }
    107 
    108   virtual void ExtensionEnableFlowAborted(bool user_initiated) OVERRIDE {
    109     callback_.Run();
    110     delete this;
    111   }
    112 
    113   Profile* profile_;
    114   std::string extension_id_;
    115   base::Callback<void()> callback_;
    116   scoped_ptr<ExtensionEnableFlow> flow_;
    117 
    118   DISALLOW_COPY_AND_ASSIGN(EnableViaPrompt);
    119 };
    120 
    121 }  // namespace
    122 
    123 namespace apps {
    124 
    125 bool ExtensionAppShimHandler::Delegate::ProfileExistsForPath(
    126     const base::FilePath& path) {
    127   ProfileManager* profile_manager = g_browser_process->profile_manager();
    128   // Check for the profile name in the profile info cache to ensure that we
    129   // never access any directory that isn't a known profile.
    130   base::FilePath full_path = profile_manager->user_data_dir().Append(path);
    131   ProfileInfoCache& cache = profile_manager->GetProfileInfoCache();
    132   return cache.GetIndexOfProfileWithPath(full_path) != std::string::npos;
    133 }
    134 
    135 Profile* ExtensionAppShimHandler::Delegate::ProfileForPath(
    136     const base::FilePath& path) {
    137   ProfileManager* profile_manager = g_browser_process->profile_manager();
    138   base::FilePath full_path = profile_manager->user_data_dir().Append(path);
    139   Profile* profile = profile_manager->GetProfileByPath(full_path);
    140 
    141   // Use IsValidProfile to check if the profile has been created.
    142   return profile && profile_manager->IsValidProfile(profile) ? profile : NULL;
    143 }
    144 
    145 void ExtensionAppShimHandler::Delegate::LoadProfileAsync(
    146     const base::FilePath& path,
    147     base::Callback<void(Profile*)> callback) {
    148   ProfileManager* profile_manager = g_browser_process->profile_manager();
    149   base::FilePath full_path = profile_manager->user_data_dir().Append(path);
    150   profile_manager->CreateProfileAsync(
    151       full_path,
    152       base::Bind(&ProfileLoadedCallback, callback),
    153       base::string16(), base::string16(), std::string());
    154 }
    155 
    156 AppWindowList ExtensionAppShimHandler::Delegate::GetWindows(
    157     Profile* profile,
    158     const std::string& extension_id) {
    159   return AppWindowRegistry::Get(profile)->GetAppWindowsForApp(extension_id);
    160 }
    161 
    162 const extensions::Extension*
    163 ExtensionAppShimHandler::Delegate::GetAppExtension(
    164     Profile* profile,
    165     const std::string& extension_id) {
    166   ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
    167   const extensions::Extension* extension =
    168       registry->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
    169   return extension && extension->is_platform_app() ? extension : NULL;
    170 }
    171 
    172 void ExtensionAppShimHandler::Delegate::EnableExtension(
    173     Profile* profile,
    174     const std::string& extension_id,
    175     const base::Callback<void()>& callback) {
    176   (new EnableViaPrompt(profile, extension_id, callback))->Run();
    177 }
    178 
    179 void ExtensionAppShimHandler::Delegate::LaunchApp(
    180     Profile* profile,
    181     const extensions::Extension* extension,
    182     const std::vector<base::FilePath>& files) {
    183   CoreAppLauncherHandler::RecordAppLaunchType(
    184       extension_misc::APP_LAUNCH_CMD_LINE_APP, extension->GetType());
    185   if (files.empty()) {
    186     apps::LaunchPlatformApp(profile, extension);
    187   } else {
    188     for (std::vector<base::FilePath>::const_iterator it = files.begin();
    189          it != files.end(); ++it) {
    190       apps::LaunchPlatformAppWithPath(profile, extension, *it);
    191     }
    192   }
    193 }
    194 
    195 void ExtensionAppShimHandler::Delegate::LaunchShim(
    196     Profile* profile,
    197     const extensions::Extension* extension) {
    198   web_app::MaybeLaunchShortcut(
    199       web_app::ShortcutInfoForExtensionAndProfile(extension, profile));
    200 }
    201 
    202 void ExtensionAppShimHandler::Delegate::MaybeTerminate() {
    203   AppShimHandler::MaybeTerminate();
    204 }
    205 
    206 ExtensionAppShimHandler::ExtensionAppShimHandler()
    207     : delegate_(new Delegate),
    208       weak_factory_(this) {
    209   // This is instantiated in BrowserProcessImpl::PreMainMessageLoopRun with
    210   // AppShimHostManager. Since PROFILE_CREATED is not fired until
    211   // ProfileManager::GetLastUsedProfile/GetLastOpenedProfiles, this should catch
    212   // notifications for all profiles.
    213   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
    214                  content::NotificationService::AllBrowserContextsAndSources());
    215   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
    216                  content::NotificationService::AllBrowserContextsAndSources());
    217 }
    218 
    219 ExtensionAppShimHandler::~ExtensionAppShimHandler() {}
    220 
    221 AppShimHandler::Host* ExtensionAppShimHandler::FindHost(
    222     Profile* profile,
    223     const std::string& app_id) {
    224   HostMap::iterator it = hosts_.find(make_pair(profile, app_id));
    225   return it == hosts_.end() ? NULL : it->second;
    226 }
    227 
    228 // static
    229 void ExtensionAppShimHandler::QuitAppForWindow(AppWindow* app_window) {
    230   ExtensionAppShimHandler* handler =
    231       g_browser_process->platform_part()->app_shim_host_manager()->
    232           extension_app_shim_handler();
    233   Host* host = handler->FindHost(
    234       Profile::FromBrowserContext(app_window->browser_context()),
    235       app_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     AppWindowRegistry::Get(
    241         Profile::FromBrowserContext(app_window->browser_context()))
    242         ->CloseAllAppWindowsForApp(app_window->extension_id());
    243   }
    244 }
    245 
    246 void ExtensionAppShimHandler::HideAppForWindow(AppWindow* app_window) {
    247   ExtensionAppShimHandler* handler =
    248       g_browser_process->platform_part()->app_shim_host_manager()->
    249           extension_app_shim_handler();
    250   Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
    251   Host* host = handler->FindHost(profile, app_window->extension_id());
    252   if (host)
    253     host->OnAppHide();
    254   else
    255     SetAppHidden(profile, app_window->extension_id(), true);
    256 }
    257 
    258 void ExtensionAppShimHandler::FocusAppForWindow(AppWindow* app_window) {
    259   ExtensionAppShimHandler* handler =
    260       g_browser_process->platform_part()->app_shim_host_manager()->
    261           extension_app_shim_handler();
    262   Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
    263   const std::string& app_id = app_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::AppWindowRegistry::Get(profile)->GetAppWindowsForApp(app_id));
    272   }
    273 }
    274 
    275 // static
    276 bool ExtensionAppShimHandler::RequestUserAttentionForWindow(
    277     AppWindow* app_window) {
    278   ExtensionAppShimHandler* handler =
    279       g_browser_process->platform_part()->app_shim_host_manager()->
    280           extension_app_shim_handler();
    281   Profile* profile = Profile::FromBrowserContext(app_window->browser_context());
    282   Host* host = handler->FindHost(profile, app_window->extension_id());
    283   if (host) {
    284     // Bring the window to the front without showing it.
    285     AppWindowRegistry::Get(profile)->AppWindowActivated(app_window);
    286     host->OnAppRequestUserAttention();
    287     return true;
    288   } else {
    289     // Just show the app.
    290     SetAppHidden(profile, app_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 AppWindowList 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 AppWindowList windows = delegate_->GetWindows(profile, app_id);
    450   for (AppWindowRegistry::const_iterator it = windows.begin();
    451        it != windows.end();
    452        ++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