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