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