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