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