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 "chrome/browser/app_mode/app_mode_utils.h"
     13 #include "chrome/browser/extensions/extension_prefs.h"
     14 #include "chrome/browser/extensions/extension_service.h"
     15 #include "chrome/browser/extensions/extension_system.h"
     16 #include "chrome/browser/extensions/tab_helper.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/browser/ui/browser.h"
     19 #include "chrome/browser/ui/browser_commands.h"
     20 #include "chrome/browser/ui/browser_finder.h"
     21 #include "chrome/browser/ui/browser_tabstrip.h"
     22 #include "chrome/browser/ui/browser_window.h"
     23 #include "chrome/browser/ui/host_desktop.h"
     24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     25 #include "chrome/browser/web_applications/web_app.h"
     26 #include "chrome/common/chrome_switches.h"
     27 #include "chrome/common/extensions/extension.h"
     28 #include "chrome/common/extensions/extension_constants.h"
     29 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     30 #include "chrome/common/extensions/manifest_url_handler.h"
     31 #include "chrome/common/url_constants.h"
     32 #include "content/public/browser/render_view_host.h"
     33 #include "content/public/browser/web_contents.h"
     34 #include "content/public/browser/web_contents_view.h"
     35 #include "content/public/common/renderer_preferences.h"
     36 #include "ui/base/window_open_disposition.h"
     37 #include "ui/gfx/rect.h"
     38 
     39 #if defined(OS_MACOSX)
     40 #include "chrome/browser/ui/browser_commands_mac.h"
     41 #endif
     42 
     43 #if defined(OS_WIN)
     44 #include "win8/util/win8_util.h"
     45 #endif
     46 
     47 using content::WebContents;
     48 using extensions::Extension;
     49 using extensions::ExtensionPrefs;
     50 
     51 namespace {
     52 
     53 // Get the launch URL for a given extension, with optional override/fallback.
     54 // |override_url|, if non-empty, will be preferred over the extension's
     55 // launch url.
     56 GURL UrlForExtension(const Extension* extension,
     57                      const GURL& override_url) {
     58   if (!extension)
     59     return override_url;
     60 
     61   GURL url;
     62   if (!override_url.is_empty()) {
     63     DCHECK(extension->web_extent().MatchesURL(override_url) ||
     64            override_url.GetOrigin() == extension->url());
     65     url = override_url;
     66   } else {
     67     url = extensions::AppLaunchInfo::GetFullLaunchURL(extension);
     68   }
     69 
     70   // For extensions lacking launch urls, determine a reasonable fallback.
     71   if (!url.is_valid()) {
     72     url = extensions::ManifestURL::GetOptionsPage(extension);
     73     if (!url.is_valid())
     74       url = GURL(chrome::kChromeUIExtensionsURL);
     75   }
     76 
     77   return url;
     78 }
     79 
     80 ui::WindowShowState DetermineWindowShowState(
     81     Profile* profile,
     82     extension_misc::LaunchContainer container,
     83     const Extension* extension) {
     84   if (!extension ||
     85       container != extension_misc::LAUNCH_WINDOW) {
     86     return ui::SHOW_STATE_DEFAULT;
     87   }
     88 
     89   if (chrome::IsRunningInForcedAppMode())
     90     return ui::SHOW_STATE_FULLSCREEN;
     91 
     92 #if defined(USE_ASH)
     93   // In ash, LAUNCH_FULLSCREEN launches in a maximized app window and
     94   // LAUNCH_WINDOW launches in a normal app window.
     95   ExtensionService* service =
     96       extensions::ExtensionSystem::Get(profile)->extension_service();
     97   ExtensionPrefs::LaunchType launch_type =
     98       service->extension_prefs()->GetLaunchType(
     99           extension, ExtensionPrefs::LAUNCH_DEFAULT);
    100   if (launch_type == ExtensionPrefs::LAUNCH_FULLSCREEN)
    101     return ui::SHOW_STATE_MAXIMIZED;
    102   else if (launch_type == ExtensionPrefs::LAUNCH_WINDOW)
    103     return ui::SHOW_STATE_NORMAL;
    104 #endif
    105 
    106   return ui::SHOW_STATE_DEFAULT;
    107 }
    108 
    109 WebContents* OpenApplicationWindow(
    110     Profile* profile,
    111     const Extension* extension,
    112     extension_misc::LaunchContainer container,
    113     const GURL& url_input,
    114     Browser** app_browser,
    115     const gfx::Rect& override_bounds) {
    116   DCHECK(!url_input.is_empty() || extension);
    117   GURL url = UrlForExtension(extension, url_input);
    118 
    119   std::string app_name;
    120   app_name = extension ?
    121       web_app::GenerateApplicationNameFromExtensionId(extension->id()) :
    122       web_app::GenerateApplicationNameFromURL(url);
    123 
    124   Browser::Type type = Browser::TYPE_POPUP;
    125 
    126   gfx::Rect window_bounds;
    127   if (extension) {
    128     window_bounds.set_width(
    129         extensions::AppLaunchInfo::GetLaunchWidth(extension));
    130     window_bounds.set_height(
    131         extensions::AppLaunchInfo::GetLaunchHeight(extension));
    132   }
    133   if (!override_bounds.IsEmpty())
    134     window_bounds = override_bounds;
    135 
    136   Browser::CreateParams params(type, profile, chrome::GetActiveDesktop());
    137   params.app_name = app_name;
    138   params.initial_bounds = window_bounds;
    139   params.initial_show_state = DetermineWindowShowState(profile,
    140                                                        container,
    141                                                        extension);
    142 
    143   Browser* browser = NULL;
    144 #if defined(OS_WIN)
    145   // On Windows 8's single window Metro mode we don't allow multiple Chrome
    146   // windows to be created. We instead attempt to reuse an existing Browser
    147   // window.
    148   if (win8::IsSingleWindowMetroMode()) {
    149     browser = chrome::FindBrowserWithProfile(
    150         profile, chrome::HOST_DESKTOP_TYPE_NATIVE);
    151   }
    152 #endif
    153   if (!browser)
    154     browser = new Browser(params);
    155 
    156   if (app_browser)
    157     *app_browser = browser;
    158 
    159   WebContents* web_contents = chrome::AddSelectedTabWithURL(
    160       browser, url, content::PAGE_TRANSITION_AUTO_TOPLEVEL);
    161   web_contents->GetMutableRendererPrefs()->can_accept_load_drops = false;
    162   web_contents->GetRenderViewHost()->SyncRendererPrefs();
    163 
    164   browser->window()->Show();
    165 
    166   // TODO(jcampan): http://crbug.com/8123 we should not need to set the initial
    167   //                focus explicitly.
    168   web_contents->GetView()->SetInitialFocus();
    169   return web_contents;
    170 }
    171 
    172 WebContents* OpenApplicationTab(Profile* profile,
    173                                 const Extension* extension,
    174                                 const GURL& override_url,
    175                                 WindowOpenDisposition disposition) {
    176   Browser* browser = chrome::FindTabbedBrowser(profile,
    177                                                false,
    178                                                chrome::GetActiveDesktop());
    179   WebContents* contents = NULL;
    180   if (!browser) {
    181     // No browser for this profile, need to open a new one.
    182     browser = new Browser(Browser::CreateParams(Browser::TYPE_TABBED,
    183                                                 profile,
    184                                                 chrome::GetActiveDesktop()));
    185     browser->window()->Show();
    186     // There's no current tab in this browser window, so add a new one.
    187     disposition = NEW_FOREGROUND_TAB;
    188   } else {
    189     // For existing browser, ensure its window is shown and activated.
    190     browser->window()->Show();
    191     browser->window()->Activate();
    192   }
    193 
    194   // Check the prefs for overridden mode.
    195   ExtensionService* extension_service =
    196       extensions::ExtensionSystem::Get(profile)->extension_service();
    197   DCHECK(extension_service);
    198 
    199   ExtensionPrefs::LaunchType launch_type =
    200       extension_service->extension_prefs()->GetLaunchType(
    201           extension, ExtensionPrefs::LAUNCH_DEFAULT);
    202   UMA_HISTOGRAM_ENUMERATION("Extensions.AppTabLaunchType", launch_type, 100);
    203 
    204   int add_type = TabStripModel::ADD_ACTIVE;
    205   if (launch_type == ExtensionPrefs::LAUNCH_PINNED)
    206     add_type |= TabStripModel::ADD_PINNED;
    207 
    208   GURL extension_url = UrlForExtension(extension, override_url);
    209   // TODO(erikkay): START_PAGE doesn't seem like the right transition in all
    210   // cases.
    211   chrome::NavigateParams params(browser, extension_url,
    212                                 content::PAGE_TRANSITION_AUTO_TOPLEVEL);
    213   params.tabstrip_add_types = add_type;
    214   params.disposition = disposition;
    215 
    216   if (disposition == CURRENT_TAB) {
    217     WebContents* existing_tab =
    218         browser->tab_strip_model()->GetActiveWebContents();
    219     TabStripModel* model = browser->tab_strip_model();
    220     int tab_index = model->GetIndexOfWebContents(existing_tab);
    221 
    222     existing_tab->OpenURL(content::OpenURLParams(
    223           extension_url,
    224           content::Referrer(existing_tab->GetURL(),
    225                             WebKit::WebReferrerPolicyDefault),
    226           disposition, content::PAGE_TRANSITION_LINK, false));
    227     // Reset existing_tab as OpenURL() may have clobbered it.
    228     existing_tab = browser->tab_strip_model()->GetActiveWebContents();
    229     if (params.tabstrip_add_types & TabStripModel::ADD_PINNED) {
    230       model->SetTabPinned(tab_index, true);
    231       // Pinning may have moved the tab.
    232       tab_index = model->GetIndexOfWebContents(existing_tab);
    233     }
    234     if (params.tabstrip_add_types & TabStripModel::ADD_ACTIVE)
    235       model->ActivateTabAt(tab_index, true);
    236 
    237     contents = existing_tab;
    238   } else {
    239     chrome::Navigate(&params);
    240     contents = params.target_contents;
    241   }
    242 
    243   // On Chrome OS the host desktop type for a browser window is always set to
    244   // HOST_DESKTOP_TYPE_ASH. On Windows 8 it is only the case for Chrome ASH
    245   // in metro mode.
    246   if (browser->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH) {
    247     // In ash, LAUNCH_FULLSCREEN launches in the OpenApplicationWindow function
    248     // i.e. it should not reach here.
    249     DCHECK(launch_type != ExtensionPrefs::LAUNCH_FULLSCREEN);
    250   } else {
    251     // TODO(skerner):  If we are already in full screen mode, and the user
    252     // set the app to open as a regular or pinned tab, what should happen?
    253     // Today we open the tab, but stay in full screen mode.  Should we leave
    254     // full screen mode in this case?
    255     if (launch_type == ExtensionPrefs::LAUNCH_FULLSCREEN &&
    256         !browser->window()->IsFullscreen()) {
    257 #if defined(OS_MACOSX)
    258       chrome::ToggleFullscreenWithChromeOrFallback(browser);
    259 #else
    260       chrome::ToggleFullscreenMode(browser);
    261 #endif
    262     }
    263   }
    264   return contents;
    265 }
    266 
    267 }  // namespace
    268 
    269 namespace chrome {
    270 
    271 AppLaunchParams::AppLaunchParams(Profile* profile,
    272                                  const extensions::Extension* extension,
    273                                  extension_misc::LaunchContainer container,
    274                                  WindowOpenDisposition disposition)
    275     : profile(profile),
    276       extension(extension),
    277       container(container),
    278       disposition(disposition),
    279       override_url(),
    280       override_bounds(),
    281       command_line(NULL) {}
    282 
    283 AppLaunchParams::AppLaunchParams(Profile* profile,
    284                                  const extensions::Extension* extension,
    285                                  WindowOpenDisposition disposition)
    286     : profile(profile),
    287       extension(extension),
    288       container(extension_misc::LAUNCH_NONE),
    289       disposition(disposition),
    290       override_url(),
    291       override_bounds(),
    292       command_line(NULL) {
    293   ExtensionService* service =
    294       extensions::ExtensionSystem::Get(profile)->extension_service();
    295   DCHECK(service);
    296 
    297   // Look up the app preference to find out the right launch container. Default
    298   // is to launch as a regular tab.
    299   container = service->extension_prefs()->GetLaunchContainer(
    300       extension, extensions::ExtensionPrefs::LAUNCH_REGULAR);
    301 }
    302 
    303 AppLaunchParams::AppLaunchParams(Profile* profile,
    304                                  const extensions::Extension* extension,
    305                                  int event_flags)
    306     : profile(profile),
    307       extension(extension),
    308       container(extension_misc::LAUNCH_NONE),
    309       disposition(ui::DispositionFromEventFlags(event_flags)),
    310       override_url(),
    311       override_bounds(),
    312       command_line(NULL) {
    313   if (disposition == NEW_FOREGROUND_TAB || disposition == NEW_BACKGROUND_TAB) {
    314     container = extension_misc::LAUNCH_TAB;
    315   } else if (disposition == NEW_WINDOW) {
    316     container = extension_misc::LAUNCH_WINDOW;
    317   } else {
    318     ExtensionService* service =
    319         extensions::ExtensionSystem::Get(profile)->extension_service();
    320     DCHECK(service);
    321 
    322     // Look at preference to find the right launch container.  If no preference
    323     // is set, launch as a regular tab.
    324     container = service->extension_prefs()->GetLaunchContainer(
    325         extension, extensions::ExtensionPrefs::LAUNCH_DEFAULT);
    326     disposition = NEW_FOREGROUND_TAB;
    327   }
    328 }
    329 
    330 WebContents* OpenApplication(const AppLaunchParams& params) {
    331   Profile* profile = params.profile;
    332   const extensions::Extension* extension = params.extension;
    333   extension_misc::LaunchContainer container = params.container;
    334   const GURL& override_url = params.override_url;
    335   const gfx::Rect& override_bounds = params.override_bounds;
    336 
    337   WebContents* tab = NULL;
    338   ExtensionPrefs* prefs = extensions::ExtensionSystem::Get(profile)->
    339       extension_service()->extension_prefs();
    340   prefs->SetActiveBit(extension->id(), true);
    341 
    342   UMA_HISTOGRAM_ENUMERATION("Extensions.AppLaunchContainer", container, 100);
    343 
    344   if (extension->is_platform_app()) {
    345     apps::LaunchPlatformAppWithCommandLine(
    346         profile, extension, params.command_line, params.current_directory);
    347     return NULL;
    348   }
    349 
    350   switch (container) {
    351     case extension_misc::LAUNCH_NONE: {
    352       NOTREACHED();
    353       break;
    354     }
    355     case extension_misc::LAUNCH_PANEL:
    356     case extension_misc::LAUNCH_WINDOW:
    357       tab = OpenApplicationWindow(profile, extension, container,
    358                                   override_url, NULL, override_bounds);
    359       break;
    360     case extension_misc::LAUNCH_TAB: {
    361       tab = OpenApplicationTab(profile, extension, override_url,
    362                                params.disposition);
    363       break;
    364     }
    365     default:
    366       NOTREACHED();
    367       break;
    368   }
    369   return tab;
    370 }
    371 
    372 WebContents* OpenAppShortcutWindow(Profile* profile,
    373                                    const GURL& url,
    374                                    const gfx::Rect& override_bounds) {
    375   Browser* app_browser;
    376   WebContents* tab = OpenApplicationWindow(
    377       profile,
    378       NULL,  // this is a URL app.  No extension.
    379       extension_misc::LAUNCH_WINDOW,
    380       url,
    381       &app_browser,
    382       override_bounds);
    383 
    384   if (!tab)
    385     return NULL;
    386 
    387   // Set UPDATE_SHORTCUT as the pending web app action. This action is picked
    388   // up in LoadingStateChanged to schedule a GetApplicationInfo. And when
    389   // the web app info is available, extensions::TabHelper notifies Browser via
    390   // OnDidGetApplicationInfo, which calls
    391   // web_app::UpdateShortcutForTabContents when it sees UPDATE_SHORTCUT as
    392   // pending web app action.
    393   extensions::TabHelper::FromWebContents(tab)->set_pending_web_app_action(
    394       extensions::TabHelper::UPDATE_SHORTCUT);
    395 
    396   return tab;
    397 }
    398 
    399 }  // namespace chrome
    400