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
47using content::WebContents;
48using extensions::Extension;
49using extensions::ExtensionPrefs;
50
51namespace {
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.
56GURL 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
80ui::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
109WebContents* 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
172WebContents* 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
269namespace chrome {
270
271AppLaunchParams::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
283AppLaunchParams::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
303AppLaunchParams::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
330WebContents* 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
372WebContents* 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