Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 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/ui/extensions/application_launch.h"
      6 
      7 #include <string>
      8 
      9 #include "apps/launcher.h"
     10 #include "base/command_line.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "chrome/browser/app_mode/app_mode_utils.h"
     14 #include "chrome/browser/apps/per_app_settings_service.h"
     15 #include "chrome/browser/apps/per_app_settings_service_factory.h"
     16 #include "chrome/browser/extensions/extension_service.h"
     17 #include "chrome/browser/extensions/launch_util.h"
     18 #include "chrome/browser/extensions/tab_helper.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "chrome/browser/ui/app_list/app_list_service.h"
     21 #include "chrome/browser/ui/browser.h"
     22 #include "chrome/browser/ui/browser_commands.h"
     23 #include "chrome/browser/ui/browser_finder.h"
     24 #include "chrome/browser/ui/browser_tabstrip.h"
     25 #include "chrome/browser/ui/browser_window.h"
     26 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
     27 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
     28 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     29 #include "chrome/browser/web_applications/web_app.h"
     30 #include "chrome/common/chrome_switches.h"
     31 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     32 #include "chrome/common/url_constants.h"
     33 #include "content/public/browser/render_view_host.h"
     34 #include "content/public/browser/web_contents.h"
     35 #include "content/public/common/renderer_preferences.h"
     36 #include "extensions/browser/extension_prefs.h"
     37 #include "extensions/browser/extension_registry.h"
     38 #include "extensions/browser/extension_system.h"
     39 #include "extensions/common/constants.h"
     40 #include "extensions/common/extension.h"
     41 #include "extensions/common/features/feature.h"
     42 #include "extensions/common/features/feature_provider.h"
     43 #include "extensions/common/manifest_handlers/options_page_info.h"
     44 #include "ui/base/window_open_disposition.h"
     45 #include "ui/gfx/rect.h"
     46 
     47 #if defined(OS_MACOSX)
     48 #include "chrome/browser/ui/browser_commands_mac.h"
     49 #endif
     50 
     51 using content::WebContents;
     52 using extensions::Extension;
     53 using extensions::ExtensionPrefs;
     54 using extensions::ExtensionRegistry;
     55 
     56 namespace {
     57 
     58 // Attempts to launch a packaged app, prompting the user to enable it if
     59 // necessary. If a prompt is required it will be shown inside the AppList.
     60 // This class manages its own lifetime.
     61 class EnableViaAppListFlow : public ExtensionEnableFlowDelegate {
     62  public:
     63   EnableViaAppListFlow(ExtensionService* service,
     64                        Profile* profile,
     65                        chrome::HostDesktopType desktop_type,
     66                        const std::string& extension_id,
     67                        const base::Closure& callback)
     68       : service_(service),
     69         profile_(profile),
     70         desktop_type_(desktop_type),
     71         extension_id_(extension_id),
     72         callback_(callback) {
     73   }
     74 
     75   virtual ~EnableViaAppListFlow() {
     76   }
     77 
     78   void Run() {
     79     DCHECK(!service_->IsExtensionEnabled(extension_id_));
     80     flow_.reset(new ExtensionEnableFlow(profile_, extension_id_, this));
     81     flow_->StartForCurrentlyNonexistentWindow(
     82         base::Bind(&EnableViaAppListFlow::ShowAppList, base::Unretained(this)));
     83   }
     84 
     85  private:
     86   gfx::NativeWindow ShowAppList() {
     87     AppListService* app_list_service = AppListService::Get(desktop_type_);
     88     app_list_service->Show();
     89     return app_list_service->GetAppListWindow();
     90   }
     91 
     92   // ExtensionEnableFlowDelegate overrides.
     93   virtual void ExtensionEnableFlowFinished() OVERRIDE {
     94     const Extension* extension =
     95         service_->GetExtensionById(extension_id_, false);
     96     if (!extension)
     97       return;
     98     callback_.Run();
     99     delete this;
    100   }
    101 
    102   virtual void ExtensionEnableFlowAborted(bool user_initiated) OVERRIDE {
    103     delete this;
    104   }
    105 
    106   ExtensionService* service_;
    107   Profile* profile_;
    108   chrome::HostDesktopType desktop_type_;
    109   std::string extension_id_;
    110   base::Closure callback_;
    111   scoped_ptr<ExtensionEnableFlow> flow_;
    112 
    113   DISALLOW_COPY_AND_ASSIGN(EnableViaAppListFlow);
    114 };
    115 
    116 const Extension* GetExtension(const AppLaunchParams& params) {
    117   if (params.extension_id.empty())
    118     return NULL;
    119   ExtensionRegistry* registry = ExtensionRegistry::Get(params.profile);
    120   return registry->GetExtensionById(params.extension_id,
    121                                     ExtensionRegistry::ENABLED |
    122                                         ExtensionRegistry::DISABLED |
    123                                         ExtensionRegistry::TERMINATED);
    124 }
    125 
    126 ui::WindowShowState DetermineWindowShowState(
    127     Profile* profile,
    128     extensions::LaunchContainer container,
    129     const Extension* extension) {
    130   if (!extension || container != extensions::LAUNCH_CONTAINER_WINDOW)
    131     return ui::SHOW_STATE_DEFAULT;
    132 
    133   if (chrome::IsRunningInForcedAppMode())
    134     return ui::SHOW_STATE_FULLSCREEN;
    135 
    136 #if defined(USE_ASH)
    137   // In ash, LAUNCH_TYPE_FULLSCREEN launches in a maximized app window and
    138   // LAUNCH_TYPE_WINDOW launches in a normal app window.
    139   extensions::LaunchType launch_type =
    140       extensions::GetLaunchType(ExtensionPrefs::Get(profile), extension);
    141   if (launch_type == extensions::LAUNCH_TYPE_FULLSCREEN)
    142     return ui::SHOW_STATE_MAXIMIZED;
    143   else if (launch_type == extensions::LAUNCH_TYPE_WINDOW)
    144     return ui::SHOW_STATE_NORMAL;
    145 #endif
    146 
    147   return ui::SHOW_STATE_DEFAULT;
    148 }
    149 
    150 WebContents* OpenApplicationWindow(const AppLaunchParams& params) {
    151   Profile* const profile = params.profile;
    152   const Extension* const extension = GetExtension(params);
    153   const GURL url_input = params.override_url;
    154 
    155   DCHECK(!url_input.is_empty() || extension);
    156   GURL url = UrlForExtension(extension, url_input);
    157   std::string app_name = extension ?
    158       web_app::GenerateApplicationNameFromExtensionId(extension->id()) :
    159       web_app::GenerateApplicationNameFromURL(url);
    160 
    161   gfx::Rect initial_bounds;
    162   if (!params.override_bounds.IsEmpty()) {
    163     initial_bounds = params.override_bounds;
    164   } else if (extension) {
    165     initial_bounds.set_width(
    166         extensions::AppLaunchInfo::GetLaunchWidth(extension));
    167     initial_bounds.set_height(
    168         extensions::AppLaunchInfo::GetLaunchHeight(extension));
    169   }
    170 
    171   Browser::CreateParams browser_params(
    172       Browser::CreateParams::CreateForApp(app_name,
    173                                           true /* trusted_source */,
    174                                           initial_bounds,
    175                                           profile,
    176                                           params.desktop_type));
    177 
    178   browser_params.initial_show_state = DetermineWindowShowState(profile,
    179                                                                params.container,
    180                                                                extension);
    181 
    182   Browser* browser = new Browser(browser_params);
    183 
    184   WebContents* web_contents = chrome::AddSelectedTabWithURL(
    185       browser, url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
    186   web_contents->GetMutableRendererPrefs()->can_accept_load_drops = false;
    187   web_contents->GetRenderViewHost()->SyncRendererPrefs();
    188 
    189   browser->window()->Show();
    190 
    191   // TODO(jcampan): http://crbug.com/8123 we should not need to set the initial
    192   //                focus explicitly.
    193   web_contents->SetInitialFocus();
    194   return web_contents;
    195 }
    196 
    197 WebContents* OpenApplicationTab(const AppLaunchParams& launch_params) {
    198   const Extension* extension = GetExtension(launch_params);
    199   CHECK(extension);
    200   Profile* const profile = launch_params.profile;
    201   WindowOpenDisposition disposition = launch_params.disposition;
    202 
    203   Browser* browser = chrome::FindTabbedBrowser(profile,
    204                                                false,
    205                                                launch_params.desktop_type);
    206   WebContents* contents = NULL;
    207   if (!browser) {
    208     // No browser for this profile, need to open a new one.
    209     browser = new Browser(Browser::CreateParams(Browser::TYPE_TABBED,
    210                                                 profile,
    211                                                 launch_params.desktop_type));
    212     browser->window()->Show();
    213     // There's no current tab in this browser window, so add a new one.
    214     disposition = NEW_FOREGROUND_TAB;
    215   } else {
    216     // For existing browser, ensure its window is shown and activated.
    217     browser->window()->Show();
    218     browser->window()->Activate();
    219   }
    220 
    221   extensions::LaunchType launch_type =
    222       extensions::GetLaunchType(ExtensionPrefs::Get(profile), extension);
    223   UMA_HISTOGRAM_ENUMERATION("Extensions.AppTabLaunchType", launch_type, 100);
    224 
    225   int add_type = TabStripModel::ADD_ACTIVE;
    226   if (launch_type == extensions::LAUNCH_TYPE_PINNED)
    227     add_type |= TabStripModel::ADD_PINNED;
    228 
    229   GURL extension_url = UrlForExtension(extension, launch_params.override_url);
    230   chrome::NavigateParams params(browser, extension_url,
    231                                 ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
    232   params.tabstrip_add_types = add_type;
    233   params.disposition = disposition;
    234 
    235   if (disposition == CURRENT_TAB) {
    236     WebContents* existing_tab =
    237         browser->tab_strip_model()->GetActiveWebContents();
    238     TabStripModel* model = browser->tab_strip_model();
    239     int tab_index = model->GetIndexOfWebContents(existing_tab);
    240 
    241     existing_tab->OpenURL(content::OpenURLParams(
    242           extension_url,
    243           content::Referrer(existing_tab->GetURL(),
    244                             blink::WebReferrerPolicyDefault),
    245           disposition, ui::PAGE_TRANSITION_LINK, false));
    246     // Reset existing_tab as OpenURL() may have clobbered it.
    247     existing_tab = browser->tab_strip_model()->GetActiveWebContents();
    248     if (params.tabstrip_add_types & TabStripModel::ADD_PINNED) {
    249       model->SetTabPinned(tab_index, true);
    250       // Pinning may have moved the tab.
    251       tab_index = model->GetIndexOfWebContents(existing_tab);
    252     }
    253     if (params.tabstrip_add_types & TabStripModel::ADD_ACTIVE)
    254       model->ActivateTabAt(tab_index, true);
    255 
    256     contents = existing_tab;
    257   } else {
    258     chrome::Navigate(&params);
    259     contents = params.target_contents;
    260   }
    261 
    262   // On Chrome OS the host desktop type for a browser window is always set to
    263   // HOST_DESKTOP_TYPE_ASH. On Windows 8 it is only the case for Chrome ASH
    264   // in metro mode.
    265   if (browser->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH) {
    266     // In ash, LAUNCH_FULLSCREEN launches in the OpenApplicationWindow function
    267     // i.e. it should not reach here.
    268     DCHECK(launch_type != extensions::LAUNCH_TYPE_FULLSCREEN);
    269   } else {
    270     // TODO(skerner):  If we are already in full screen mode, and the user
    271     // set the app to open as a regular or pinned tab, what should happen?
    272     // Today we open the tab, but stay in full screen mode.  Should we leave
    273     // full screen mode in this case?
    274     if (launch_type == extensions::LAUNCH_TYPE_FULLSCREEN &&
    275         !browser->window()->IsFullscreen()) {
    276 #if defined(OS_MACOSX)
    277       chrome::ToggleFullscreenWithChromeOrFallback(browser);
    278 #else
    279       chrome::ToggleFullscreenMode(browser);
    280 #endif
    281     }
    282   }
    283   return contents;
    284 }
    285 
    286 WebContents* OpenEnabledApplication(const AppLaunchParams& params) {
    287   const Extension* extension = GetExtension(params);
    288   if (!extension)
    289     return NULL;
    290   Profile* profile = params.profile;
    291 
    292   WebContents* tab = NULL;
    293   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile);
    294   prefs->SetActiveBit(extension->id(), true);
    295 
    296   UMA_HISTOGRAM_ENUMERATION(
    297       "Extensions.AppLaunchContainer", params.container, 100);
    298 
    299   if (CanLaunchViaEvent(extension)) {
    300     // Remember what desktop the launch happened on so that when the app opens a
    301     // window we can open them on the right desktop.
    302     PerAppSettingsServiceFactory::GetForBrowserContext(profile)->
    303         SetDesktopLastLaunchedFrom(extension->id(), params.desktop_type);
    304 
    305     apps::LaunchPlatformAppWithCommandLine(
    306         profile, extension, params.command_line, params.current_directory);
    307     return NULL;
    308   }
    309 
    310   // Record v1 app launch. Platform app launch is recorded when dispatching
    311   // the onLaunched event.
    312   prefs->SetLastLaunchTime(extension->id(), base::Time::Now());
    313 
    314   switch (params.container) {
    315     case extensions::LAUNCH_CONTAINER_NONE: {
    316       NOTREACHED();
    317       break;
    318     }
    319     case extensions::LAUNCH_CONTAINER_PANEL:
    320     case extensions::LAUNCH_CONTAINER_WINDOW:
    321       tab = OpenApplicationWindow(params);
    322       break;
    323     case extensions::LAUNCH_CONTAINER_TAB: {
    324       tab = OpenApplicationTab(params);
    325       break;
    326     }
    327     default:
    328       NOTREACHED();
    329       break;
    330   }
    331   return tab;
    332 }
    333 
    334 }  // namespace
    335 
    336 AppLaunchParams::AppLaunchParams(Profile* profile,
    337                                  const extensions::Extension* extension,
    338                                  extensions::LaunchContainer container,
    339                                  WindowOpenDisposition disposition)
    340     : profile(profile),
    341       extension_id(extension ? extension->id() : std::string()),
    342       container(container),
    343       disposition(disposition),
    344       desktop_type(chrome::GetActiveDesktop()),
    345       override_url(),
    346       override_bounds(),
    347       command_line(CommandLine::NO_PROGRAM) {}
    348 
    349 AppLaunchParams::AppLaunchParams(Profile* profile,
    350                                  const extensions::Extension* extension,
    351                                  WindowOpenDisposition disposition)
    352     : profile(profile),
    353       extension_id(extension ? extension->id() : std::string()),
    354       container(extensions::LAUNCH_CONTAINER_NONE),
    355       disposition(disposition),
    356       desktop_type(chrome::GetActiveDesktop()),
    357       override_url(),
    358       override_bounds(),
    359       command_line(CommandLine::NO_PROGRAM) {
    360   // Look up the app preference to find out the right launch container. Default
    361   // is to launch as a regular tab.
    362   container =
    363       extensions::GetLaunchContainer(ExtensionPrefs::Get(profile), extension);
    364 }
    365 
    366 AppLaunchParams::AppLaunchParams(Profile* profile,
    367                                  const extensions::Extension* extension,
    368                                  int event_flags,
    369                                  chrome::HostDesktopType desktop_type)
    370     : profile(profile),
    371       extension_id(extension ? extension->id() : std::string()),
    372       container(extensions::LAUNCH_CONTAINER_NONE),
    373       disposition(ui::DispositionFromEventFlags(event_flags)),
    374       desktop_type(desktop_type),
    375       override_url(),
    376       override_bounds(),
    377       command_line(CommandLine::NO_PROGRAM) {
    378   if (disposition == NEW_FOREGROUND_TAB || disposition == NEW_BACKGROUND_TAB) {
    379     container = extensions::LAUNCH_CONTAINER_TAB;
    380   } else if (disposition == NEW_WINDOW) {
    381     container = extensions::LAUNCH_CONTAINER_WINDOW;
    382   } else {
    383     // Look at preference to find the right launch container.  If no preference
    384     // is set, launch as a regular tab.
    385     container =
    386         extensions::GetLaunchContainer(ExtensionPrefs::Get(profile), extension);
    387     disposition = NEW_FOREGROUND_TAB;
    388   }
    389 }
    390 
    391 AppLaunchParams::~AppLaunchParams() {
    392 }
    393 
    394 WebContents* OpenApplication(const AppLaunchParams& params) {
    395   return OpenEnabledApplication(params);
    396 }
    397 
    398 void OpenApplicationWithReenablePrompt(const AppLaunchParams& params) {
    399   const Extension* extension = GetExtension(params);
    400   if (!extension)
    401     return;
    402   Profile* profile = params.profile;
    403 
    404   ExtensionService* service =
    405       extensions::ExtensionSystem::Get(profile)->extension_service();
    406   if (!service->IsExtensionEnabled(extension->id()) ||
    407       extensions::ExtensionRegistry::Get(profile)->GetExtensionById(
    408           extension->id(), extensions::ExtensionRegistry::TERMINATED)) {
    409     (new EnableViaAppListFlow(
    410         service, profile, params.desktop_type, extension->id(),
    411         base::Bind(base::IgnoreResult(OpenEnabledApplication), params)))->Run();
    412     return;
    413   }
    414 
    415   OpenEnabledApplication(params);
    416 }
    417 
    418 WebContents* OpenAppShortcutWindow(Profile* profile,
    419                                    const GURL& url) {
    420   AppLaunchParams launch_params(
    421       profile,
    422       NULL,  // this is a URL app.  No extension.
    423       extensions::LAUNCH_CONTAINER_WINDOW,
    424       NEW_WINDOW);
    425   launch_params.override_url = url;
    426 
    427   WebContents* tab = OpenApplicationWindow(launch_params);
    428 
    429   if (!tab)
    430     return NULL;
    431 
    432   extensions::TabHelper::FromWebContents(tab)->UpdateShortcutOnLoadComplete();
    433 
    434   return tab;
    435 }
    436 
    437 bool CanLaunchViaEvent(const extensions::Extension* extension) {
    438   const extensions::FeatureProvider* feature_provider =
    439       extensions::FeatureProvider::GetAPIFeatures();
    440   extensions::Feature* feature = feature_provider->GetFeature("app.runtime");
    441   return feature->IsAvailableToExtension(extension).is_available();
    442 }
    443 
    444 GURL UrlForExtension(const Extension* extension, const GURL& override_url) {
    445   if (!extension)
    446     return override_url;
    447 
    448   GURL url;
    449   if (!override_url.is_empty()) {
    450     DCHECK(extension->web_extent().MatchesURL(override_url) ||
    451            override_url.GetOrigin() == extension->url());
    452     url = override_url;
    453   } else {
    454     url = extensions::AppLaunchInfo::GetFullLaunchURL(extension);
    455   }
    456 
    457   // For extensions lacking launch urls, determine a reasonable fallback.
    458   if (!url.is_valid()) {
    459     url = extensions::OptionsPageInfo::GetOptionsPage(extension);
    460     if (!url.is_valid())
    461       url = GURL(chrome::kChromeUIExtensionsURL);
    462   }
    463 
    464   return url;
    465 }
    466