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(¶ms); 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