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.
5 #include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h"
7 #include "ash/shelf/shelf_model.h"
9 #include "ash/wm/window_util.h"
10 #include "chrome/browser/extensions/launch_util.h"
11 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
12 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
13 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
14 #include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h"
15 #include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
16 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
17 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
18 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_finder.h"
21 #include "chrome/browser/ui/browser_list.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/extensions/manifest_handlers/app_launch_info.h"
27 #include "content/public/browser/navigation_entry.h"
28 #include "content/public/browser/web_contents.h"
29 #include "extensions/browser/app_window/native_app_window.h"
30 #include "extensions/browser/extension_registry.h"
31 #include "extensions/browser/process_manager.h"
32 #include "ui/aura/window.h"
33 #include "ui/events/event.h"
34 #include "ui/wm/core/window_animations.h"
36 using extensions::Extension
;
37 using extensions::ExtensionRegistry
;
41 // The time delta between clicks in which clicks to launch V2 apps are ignored.
42 const int kClickSuppressionInMS
= 1000;
44 // Check if a browser can be used for activation. This addresses a special use
45 // case in the M31 multi profile mode where a user activates a V1 app which only
46 // exists yet on another users desktop, but he expects to get only his own app
47 // items and not the ones from other users through activation.
48 // TODO(skuhne): Remove this function and replace the call with
49 // launcher_controller()->IsBrowserFromActiveUser(browser) once this experiment
51 bool CanBrowserBeUsedForDirectActivation(Browser
* browser
,
52 ChromeLauncherController
* launcher
) {
53 if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
54 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_OFF
)
56 return multi_user_util::IsProfileFromActiveUser(browser
->profile());
61 // Item controller for an app shortcut. Shortcuts track app and launcher ids,
62 // but do not have any associated windows (opening a shortcut will replace the
63 // item with the appropriate LauncherItemController type).
64 AppShortcutLauncherItemController::AppShortcutLauncherItemController(
65 const std::string
& app_id
,
66 ChromeLauncherController
* controller
)
67 : LauncherItemController(TYPE_SHORTCUT
, app_id
, controller
),
68 chrome_launcher_controller_(controller
) {
69 // To detect V1 applications we use their domain and match them against the
70 // used URL. This will also work with applications like Google Drive.
71 const Extension
* extension
=
72 launcher_controller()->GetExtensionForAppID(app_id
);
73 // Some unit tests have no real extension.
76 extensions::AppLaunchInfo::GetLaunchWebURL(extension
).spec() + "*"));
80 AppShortcutLauncherItemController::~AppShortcutLauncherItemController() {
83 bool AppShortcutLauncherItemController::IsOpen() const {
84 return !chrome_launcher_controller_
->
85 GetV1ApplicationsFromAppId(app_id()).empty();
88 bool AppShortcutLauncherItemController::IsVisible() const {
89 // Return true if any browser window associated with the app is visible.
90 std::vector
<content::WebContents
*> content
=
91 chrome_launcher_controller_
->GetV1ApplicationsFromAppId(app_id());
92 for (size_t i
= 0; i
< content
.size(); i
++) {
93 Browser
* browser
= chrome::FindBrowserWithWebContents(content
[i
]);
94 if (browser
&& browser
->window()->GetNativeWindow()->IsVisible())
100 void AppShortcutLauncherItemController::Launch(ash::LaunchSource source
,
102 launcher_controller()->LaunchApp(app_id(), source
, event_flags
);
105 ash::ShelfItemDelegate::PerformedAction
106 AppShortcutLauncherItemController::Activate(ash::LaunchSource source
) {
107 content::WebContents
* content
= GetLRUApplication();
110 // Ideally we come here only once. After that ShellLauncherItemController
111 // will take over when the shell window gets opened. However there are
112 // apps which take a lot of time for pre-processing (like the files app)
113 // before they open a window. Since there is currently no other way to
114 // detect if an app was started we suppress any further clicks within a
116 if (!AllowNextLaunchAttempt())
119 Launch(source
, ui::EF_NONE
);
120 return kNewWindowCreated
;
122 return ActivateContent(content
);
125 void AppShortcutLauncherItemController::Close() {
126 // Close all running 'programs' of this type.
127 std::vector
<content::WebContents
*> content
=
128 launcher_controller()->GetV1ApplicationsFromAppId(app_id());
129 for (size_t i
= 0; i
< content
.size(); i
++) {
130 Browser
* browser
= chrome::FindBrowserWithWebContents(content
[i
]);
131 if (!browser
|| !launcher_controller()->IsBrowserFromActiveUser(browser
))
133 TabStripModel
* tab_strip
= browser
->tab_strip_model();
134 int index
= tab_strip
->GetIndexOfWebContents(content
[i
]);
135 DCHECK(index
!= TabStripModel::kNoTab
);
136 tab_strip
->CloseWebContentsAt(index
, TabStripModel::CLOSE_NONE
);
140 ChromeLauncherAppMenuItems
141 AppShortcutLauncherItemController::GetApplicationList(int event_flags
) {
142 ChromeLauncherAppMenuItems items
;
143 // Add the application name to the menu.
144 items
.push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL
, false));
146 std::vector
<content::WebContents
*> content_list
= GetRunningApplications();
148 for (size_t i
= 0; i
< content_list
.size(); i
++) {
149 content::WebContents
* web_contents
= content_list
[i
];
151 gfx::Image app_icon
= launcher_controller()->GetAppListIcon(web_contents
);
152 base::string16 title
= launcher_controller()->GetAppListTitle(web_contents
);
153 items
.push_back(new ChromeLauncherAppMenuItemTab(
154 title
, &app_icon
, web_contents
, i
== 0));
159 std::vector
<content::WebContents
*>
160 AppShortcutLauncherItemController::GetRunningApplications() {
161 std::vector
<content::WebContents
*> items
;
163 URLPattern
refocus_pattern(URLPattern::SCHEME_ALL
);
164 refocus_pattern
.SetMatchAllURLs(true);
166 if (!refocus_url_
.is_empty()) {
167 refocus_pattern
.SetMatchAllURLs(false);
168 refocus_pattern
.Parse(refocus_url_
.spec());
171 const Extension
* extension
=
172 launcher_controller()->GetExtensionForAppID(app_id());
174 // It is possible to come here While an extension gets loaded.
178 const BrowserList
* ash_browser_list
=
179 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH
);
180 for (BrowserList::const_iterator it
= ash_browser_list
->begin();
181 it
!= ash_browser_list
->end(); ++it
) {
182 Browser
* browser
= *it
;
183 if (!launcher_controller()->IsBrowserFromActiveUser(browser
))
185 TabStripModel
* tab_strip
= browser
->tab_strip_model();
186 for (int index
= 0; index
< tab_strip
->count(); index
++) {
187 content::WebContents
* web_contents
= tab_strip
->GetWebContentsAt(index
);
188 if (WebContentMatchesApp(
189 extension
, refocus_pattern
, web_contents
, browser
))
190 items
.push_back(web_contents
);
196 ash::ShelfItemDelegate::PerformedAction
197 AppShortcutLauncherItemController::ItemSelected(const ui::Event
& event
) {
198 // In case of a keyboard event, we were called by a hotkey. In that case we
199 // activate the next item in line if an item of our list is already active.
200 if (event
.type() == ui::ET_KEY_RELEASED
) {
201 if (AdvanceToNextApp())
202 return kExistingWindowActivated
;
204 return Activate(ash::LAUNCH_FROM_UNKNOWN
);
207 base::string16
AppShortcutLauncherItemController::GetTitle() {
208 return GetAppTitle();
211 ui::MenuModel
* AppShortcutLauncherItemController::CreateContextMenu(
212 aura::Window
* root_window
) {
213 ash::ShelfItem item
=
214 *(launcher_controller()->model()->ItemByID(shelf_id()));
215 return new LauncherContextMenu(launcher_controller(), &item
, root_window
);
218 ash::ShelfMenuModel
* AppShortcutLauncherItemController::CreateApplicationMenu(
220 return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags
));
223 bool AppShortcutLauncherItemController::IsDraggable() {
227 bool AppShortcutLauncherItemController::ShouldShowTooltip() {
231 content::WebContents
* AppShortcutLauncherItemController::GetLRUApplication() {
232 URLPattern
refocus_pattern(URLPattern::SCHEME_ALL
);
233 refocus_pattern
.SetMatchAllURLs(true);
235 if (!refocus_url_
.is_empty()) {
236 refocus_pattern
.SetMatchAllURLs(false);
237 refocus_pattern
.Parse(refocus_url_
.spec());
240 const Extension
* extension
=
241 launcher_controller()->GetExtensionForAppID(app_id());
243 // We may get here while the extension is loading (and NULL).
247 const BrowserList
* ash_browser_list
=
248 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH
);
249 for (BrowserList::const_reverse_iterator
250 it
= ash_browser_list
->begin_last_active();
251 it
!= ash_browser_list
->end_last_active(); ++it
) {
252 Browser
* browser
= *it
;
253 if (!CanBrowserBeUsedForDirectActivation(browser
, launcher_controller()))
255 TabStripModel
* tab_strip
= browser
->tab_strip_model();
256 // We start to enumerate from the active index.
257 int active_index
= tab_strip
->active_index();
258 for (int index
= 0; index
< tab_strip
->count(); index
++) {
259 content::WebContents
* web_contents
= tab_strip
->GetWebContentsAt(
260 (index
+ active_index
) % tab_strip
->count());
261 if (WebContentMatchesApp(
262 extension
, refocus_pattern
, web_contents
, browser
))
266 // Coming here our application was not in the LRU list. This could have
267 // happened because it did never get activated yet. So check the browser list
269 for (BrowserList::const_iterator it
= ash_browser_list
->begin();
270 it
!= ash_browser_list
->end(); ++it
) {
271 Browser
* browser
= *it
;
272 if (!CanBrowserBeUsedForDirectActivation(browser
, launcher_controller()))
274 TabStripModel
* tab_strip
= browser
->tab_strip_model();
275 for (int index
= 0; index
< tab_strip
->count(); index
++) {
276 content::WebContents
* web_contents
= tab_strip
->GetWebContentsAt(index
);
277 if (WebContentMatchesApp(
278 extension
, refocus_pattern
, web_contents
, browser
))
285 bool AppShortcutLauncherItemController::WebContentMatchesApp(
286 const extensions::Extension
* extension
,
287 const URLPattern
& refocus_pattern
,
288 content::WebContents
* web_contents
,
290 // If the browser is an app window, and the app name matches the extension,
291 // then the contents match the app.
292 if (browser
->is_app()) {
293 const extensions::Extension
* browser_extension
=
294 ExtensionRegistry::Get(browser
->profile())->GetExtensionById(
295 web_app::GetExtensionIdFromApplicationName(browser
->app_name()),
296 ExtensionRegistry::EVERYTHING
);
297 return browser_extension
== extension
;
300 // Apps set to launch in app windows should not match contents running in
302 if (extensions::LaunchesInWindow(browser
->profile(), extension
))
305 // There are three ways to identify the association of a URL with this
307 // - The refocus pattern is matched (needed for apps like drive).
308 // - The extension's origin + extent gets matched.
309 // - The launcher controller knows that the tab got created for this app.
310 const GURL tab_url
= web_contents
->GetURL();
311 return ((!refocus_pattern
.match_all_urls() &&
312 refocus_pattern
.MatchesURL(tab_url
)) ||
313 (extension
->OverlapsWithOrigin(tab_url
) &&
314 extension
->web_extent().MatchesURL(tab_url
)) ||
315 launcher_controller()->IsWebContentHandledByApplication(web_contents
,
319 ash::ShelfItemDelegate::PerformedAction
320 AppShortcutLauncherItemController::ActivateContent(
321 content::WebContents
* content
) {
322 Browser
* browser
= chrome::FindBrowserWithWebContents(content
);
323 TabStripModel
* tab_strip
= browser
->tab_strip_model();
324 int index
= tab_strip
->GetIndexOfWebContents(content
);
325 DCHECK_NE(TabStripModel::kNoTab
, index
);
327 int old_index
= tab_strip
->active_index();
328 if (index
!= old_index
)
329 tab_strip
->ActivateTabAt(index
, false);
330 return launcher_controller()->ActivateWindowOrMinimizeIfActive(
332 index
== old_index
&& GetRunningApplications().size() == 1);
335 bool AppShortcutLauncherItemController::AdvanceToNextApp() {
336 std::vector
<content::WebContents
*> items
= GetRunningApplications();
337 if (items
.size() >= 1) {
338 Browser
* browser
= chrome::FindBrowserWithWindow(
339 ash::wm::GetActiveWindow());
341 TabStripModel
* tab_strip
= browser
->tab_strip_model();
342 content::WebContents
* active
= tab_strip
->GetWebContentsAt(
343 tab_strip
->active_index());
344 std::vector
<content::WebContents
*>::const_iterator
i(
345 std::find(items
.begin(), items
.end(), active
));
346 if (i
!= items
.end()) {
347 if (items
.size() == 1) {
348 // If there is only a single item available, we animate it upon key
350 AnimateWindow(browser
->window()->GetNativeWindow(),
351 wm::WINDOW_ANIMATION_TYPE_BOUNCE
);
353 int index
= (static_cast<int>(i
- items
.begin()) + 1) % items
.size();
354 ActivateContent(items
[index
]);
363 bool AppShortcutLauncherItemController::IsV2App() {
364 const Extension
* extension
=
365 launcher_controller()->GetExtensionForAppID(app_id());
366 return extension
&& extension
->is_platform_app();
369 bool AppShortcutLauncherItemController::AllowNextLaunchAttempt() {
370 if (last_launch_attempt_
.is_null() ||
371 last_launch_attempt_
+ base::TimeDelta::FromMilliseconds(
372 kClickSuppressionInMS
) < base::Time::Now()) {
373 last_launch_attempt_
= base::Time::Now();