• 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/ash/launcher/app_shortcut_launcher_item_controller.h"
6 
7 #include "apps/ui/native_app_window.h"
8 #include "ash/shelf/shelf_model.h"
9 #include "ash/shell.h"
10 #include "ash/wm/window_util.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
13 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
14 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
15 #include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h"
16 #include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
17 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
18 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
19 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_finder.h"
22 #include "chrome/browser/ui/browser_list.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/host_desktop.h"
25 #include "chrome/browser/ui/tabs/tab_strip_model.h"
26 #include "chrome/browser/web_applications/web_app.h"
27 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
28 #include "content/public/browser/navigation_entry.h"
29 #include "content/public/browser/web_contents.h"
30 #include "extensions/browser/extension_system.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"
35 
36 using extensions::Extension;
37 
38 namespace {
39 
40 // The time delta between clicks in which clicks to launch V2 apps are ignored.
41 const int kClickSuppressionInMS = 1000;
42 
43 // Check if a browser can be used for activation. This addresses a special use
44 // case in the M31 multi profile mode where a user activates a V1 app which only
45 // exists yet on another users desktop, but he expects to get only his own app
46 // items and not the ones from other users through activation.
47 // TODO(skuhne): Remove this function and replace the call with
48 // launcher_controller()->IsBrowserFromActiveUser(browser) once this experiment
49 // goes away.
CanBrowserBeUsedForDirectActivation(Browser * browser,ChromeLauncherController * launcher)50 bool CanBrowserBeUsedForDirectActivation(Browser* browser,
51                                          ChromeLauncherController* launcher) {
52   if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
53           chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_OFF)
54     return true;
55   return multi_user_util::IsProfileFromActiveUser(browser->profile());
56 }
57 
58 }  // namespace
59 
60 // Item controller for an app shortcut. Shortcuts track app and launcher ids,
61 // but do not have any associated windows (opening a shortcut will replace the
62 // item with the appropriate LauncherItemController type).
AppShortcutLauncherItemController(const std::string & app_id,ChromeLauncherController * controller)63 AppShortcutLauncherItemController::AppShortcutLauncherItemController(
64     const std::string& app_id,
65     ChromeLauncherController* controller)
66     : LauncherItemController(TYPE_SHORTCUT, app_id, controller),
67       chrome_launcher_controller_(controller) {
68   // To detect V1 applications we use their domain and match them against the
69   // used URL. This will also work with applications like Google Drive.
70   const Extension* extension =
71       launcher_controller()->GetExtensionForAppID(app_id);
72   // Some unit tests have no real extension.
73   if (extension) {
74     set_refocus_url(GURL(
75         extensions::AppLaunchInfo::GetLaunchWebURL(extension).spec() + "*"));
76   }
77 }
78 
~AppShortcutLauncherItemController()79 AppShortcutLauncherItemController::~AppShortcutLauncherItemController() {
80 }
81 
IsOpen() const82 bool AppShortcutLauncherItemController::IsOpen() const {
83   return !chrome_launcher_controller_->
84       GetV1ApplicationsFromAppId(app_id()).empty();
85 }
86 
IsVisible() const87 bool AppShortcutLauncherItemController::IsVisible() const {
88   // Return true if any browser window associated with the app is visible.
89   std::vector<content::WebContents*> content =
90       chrome_launcher_controller_->GetV1ApplicationsFromAppId(app_id());
91   for (size_t i = 0; i < content.size(); i++) {
92     Browser* browser = chrome::FindBrowserWithWebContents(content[i]);
93     if (browser && browser->window()->GetNativeWindow()->IsVisible())
94       return true;
95   }
96   return false;
97 }
98 
Launch(ash::LaunchSource source,int event_flags)99 void AppShortcutLauncherItemController::Launch(ash::LaunchSource source,
100                                                int event_flags) {
101   launcher_controller()->LaunchApp(app_id(), source, event_flags);
102 }
103 
Activate(ash::LaunchSource source)104 bool AppShortcutLauncherItemController::Activate(ash::LaunchSource source) {
105   content::WebContents* content = GetLRUApplication();
106   if (!content) {
107     if (IsV2App()) {
108       // Ideally we come here only once. After that ShellLauncherItemController
109       // will take over when the shell window gets opened. However there are
110       // apps which take a lot of time for pre-processing (like the files app)
111       // before they open a window. Since there is currently no other way to
112       // detect if an app was started we suppress any further clicks within a
113       // special time out.
114       if (!AllowNextLaunchAttempt())
115         return false;
116     }
117     Launch(source, ui::EF_NONE);
118     return true;
119   }
120   ActivateContent(content);
121   return false;
122 }
123 
Close()124 void AppShortcutLauncherItemController::Close() {
125   // Close all running 'programs' of this type.
126   std::vector<content::WebContents*> content =
127       launcher_controller()->GetV1ApplicationsFromAppId(app_id());
128   for (size_t i = 0; i < content.size(); i++) {
129     Browser* browser = chrome::FindBrowserWithWebContents(content[i]);
130     if (!browser || !launcher_controller()->IsBrowserFromActiveUser(browser))
131       continue;
132     TabStripModel* tab_strip = browser->tab_strip_model();
133     int index = tab_strip->GetIndexOfWebContents(content[i]);
134     DCHECK(index != TabStripModel::kNoTab);
135     tab_strip->CloseWebContentsAt(index, TabStripModel::CLOSE_NONE);
136   }
137 }
138 
139 ChromeLauncherAppMenuItems
GetApplicationList(int event_flags)140 AppShortcutLauncherItemController::GetApplicationList(int event_flags) {
141   ChromeLauncherAppMenuItems items;
142   // Add the application name to the menu.
143   items.push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL, false));
144 
145   std::vector<content::WebContents*> content_list = GetRunningApplications();
146 
147   for (size_t i = 0; i < content_list.size(); i++) {
148     content::WebContents* web_contents = content_list[i];
149     // Get the icon.
150     gfx::Image app_icon = launcher_controller()->GetAppListIcon(web_contents);
151     base::string16 title = launcher_controller()->GetAppListTitle(web_contents);
152     items.push_back(new ChromeLauncherAppMenuItemTab(
153         title, &app_icon, web_contents, i == 0));
154   }
155   return items.Pass();
156 }
157 
158 std::vector<content::WebContents*>
GetRunningApplications()159 AppShortcutLauncherItemController::GetRunningApplications() {
160   std::vector<content::WebContents*> items;
161 
162   URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
163   refocus_pattern.SetMatchAllURLs(true);
164 
165   if (!refocus_url_.is_empty()) {
166     refocus_pattern.SetMatchAllURLs(false);
167     refocus_pattern.Parse(refocus_url_.spec());
168   }
169 
170   const Extension* extension =
171       launcher_controller()->GetExtensionForAppID(app_id());
172 
173   // It is possible to come here While an extension gets loaded.
174   if (!extension)
175     return items;
176 
177   const BrowserList* ash_browser_list =
178       BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
179   for (BrowserList::const_iterator it = ash_browser_list->begin();
180        it != ash_browser_list->end(); ++it) {
181     Browser* browser = *it;
182     if (!launcher_controller()->IsBrowserFromActiveUser(browser))
183       continue;
184     TabStripModel* tab_strip = browser->tab_strip_model();
185     for (int index = 0; index < tab_strip->count(); index++) {
186       content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
187       if (WebContentMatchesApp(
188               extension, refocus_pattern, web_contents, browser))
189         items.push_back(web_contents);
190     }
191   }
192   return items;
193 }
194 
ItemSelected(const ui::Event & event)195 bool AppShortcutLauncherItemController::ItemSelected(const ui::Event& event) {
196   // In case of a keyboard event, we were called by a hotkey. In that case we
197   // activate the next item in line if an item of our list is already active.
198   if (event.type() == ui::ET_KEY_RELEASED) {
199     if (AdvanceToNextApp())
200       return false;
201   }
202   return Activate(ash::LAUNCH_FROM_UNKNOWN);
203 }
204 
GetTitle()205 base::string16 AppShortcutLauncherItemController::GetTitle() {
206   return GetAppTitle();
207 }
208 
CreateContextMenu(aura::Window * root_window)209 ui::MenuModel* AppShortcutLauncherItemController::CreateContextMenu(
210     aura::Window* root_window) {
211   ash::ShelfItem item =
212       *(launcher_controller()->model()->ItemByID(shelf_id()));
213   return new LauncherContextMenu(launcher_controller(), &item, root_window);
214 }
215 
CreateApplicationMenu(int event_flags)216 ash::ShelfMenuModel* AppShortcutLauncherItemController::CreateApplicationMenu(
217     int event_flags) {
218   return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags));
219 }
220 
IsDraggable()221 bool AppShortcutLauncherItemController::IsDraggable() {
222   return true;
223 }
224 
ShouldShowTooltip()225 bool AppShortcutLauncherItemController::ShouldShowTooltip() {
226   return true;
227 }
228 
GetLRUApplication()229 content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() {
230   URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
231   refocus_pattern.SetMatchAllURLs(true);
232 
233   if (!refocus_url_.is_empty()) {
234     refocus_pattern.SetMatchAllURLs(false);
235     refocus_pattern.Parse(refocus_url_.spec());
236   }
237 
238   const Extension* extension =
239       launcher_controller()->GetExtensionForAppID(app_id());
240 
241   // We may get here while the extension is loading (and NULL).
242   if (!extension)
243     return NULL;
244 
245   const BrowserList* ash_browser_list =
246       BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
247   for (BrowserList::const_reverse_iterator
248        it = ash_browser_list->begin_last_active();
249        it != ash_browser_list->end_last_active(); ++it) {
250     Browser* browser = *it;
251     if (!CanBrowserBeUsedForDirectActivation(browser, launcher_controller()))
252       continue;
253     TabStripModel* tab_strip = browser->tab_strip_model();
254     // We start to enumerate from the active index.
255     int active_index = tab_strip->active_index();
256     for (int index = 0; index < tab_strip->count(); index++) {
257       content::WebContents* web_contents = tab_strip->GetWebContentsAt(
258           (index + active_index) % tab_strip->count());
259       if (WebContentMatchesApp(
260               extension, refocus_pattern, web_contents, browser))
261         return web_contents;
262     }
263   }
264   // Coming here our application was not in the LRU list. This could have
265   // happened because it did never get activated yet. So check the browser list
266   // as well.
267   for (BrowserList::const_iterator it = ash_browser_list->begin();
268        it != ash_browser_list->end(); ++it) {
269     Browser* browser = *it;
270     if (!CanBrowserBeUsedForDirectActivation(browser, launcher_controller()))
271       continue;
272     TabStripModel* tab_strip = browser->tab_strip_model();
273     for (int index = 0; index < tab_strip->count(); index++) {
274       content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
275       if (WebContentMatchesApp(
276               extension, refocus_pattern, web_contents, browser))
277         return web_contents;
278     }
279   }
280   return NULL;
281 }
282 
WebContentMatchesApp(const extensions::Extension * extension,const URLPattern & refocus_pattern,content::WebContents * web_contents,Browser * browser)283 bool AppShortcutLauncherItemController::WebContentMatchesApp(
284     const extensions::Extension* extension,
285     const URLPattern& refocus_pattern,
286     content::WebContents* web_contents,
287     Browser* browser) {
288   // If the browser is an app window and the app name matches the extension.
289   if (browser->is_app()) {
290     const extensions::Extension* browser_extension = NULL;
291     const ExtensionService* extension_service =
292         browser->profile()->GetExtensionService();
293     if (extension_service) {
294       browser_extension = extension_service->GetInstalledExtension(
295           web_app::GetExtensionIdFromApplicationName(browser->app_name()));
296     }
297     return browser_extension == extension;
298   }
299 
300   // There are three ways to identify the association of a URL with this
301   // extension:
302   // - The refocus pattern is matched (needed for apps like drive).
303   // - The extension's origin + extent gets matched.
304   // - The launcher controller knows that the tab got created for this app.
305   const GURL tab_url = web_contents->GetURL();
306   return ((!refocus_pattern.match_all_urls() &&
307            refocus_pattern.MatchesURL(tab_url)) ||
308           (extension->OverlapsWithOrigin(tab_url) &&
309            extension->web_extent().MatchesURL(tab_url)) ||
310           launcher_controller()->IsWebContentHandledByApplication(web_contents,
311                                                                   app_id()));
312 }
313 
ActivateContent(content::WebContents * content)314 void AppShortcutLauncherItemController::ActivateContent(
315     content::WebContents* content) {
316   Browser* browser = chrome::FindBrowserWithWebContents(content);
317   TabStripModel* tab_strip = browser->tab_strip_model();
318   int index = tab_strip->GetIndexOfWebContents(content);
319   DCHECK_NE(TabStripModel::kNoTab, index);
320 
321   int old_index = tab_strip->active_index();
322   if (index != old_index)
323     tab_strip->ActivateTabAt(index, false);
324   launcher_controller()->ActivateWindowOrMinimizeIfActive(
325       browser->window(),
326       index == old_index && GetRunningApplications().size() == 1);
327 }
328 
AdvanceToNextApp()329 bool AppShortcutLauncherItemController::AdvanceToNextApp() {
330   std::vector<content::WebContents*> items = GetRunningApplications();
331   if (items.size() >= 1) {
332     Browser* browser = chrome::FindBrowserWithWindow(
333         ash::wm::GetActiveWindow());
334     if (browser) {
335       TabStripModel* tab_strip = browser->tab_strip_model();
336       content::WebContents* active = tab_strip->GetWebContentsAt(
337           tab_strip->active_index());
338       std::vector<content::WebContents*>::const_iterator i(
339           std::find(items.begin(), items.end(), active));
340       if (i != items.end()) {
341         if (items.size() == 1) {
342           // If there is only a single item available, we animate it upon key
343           // action.
344           AnimateWindow(browser->window()->GetNativeWindow(),
345               wm::WINDOW_ANIMATION_TYPE_BOUNCE);
346         } else {
347           int index = (static_cast<int>(i - items.begin()) + 1) % items.size();
348           ActivateContent(items[index]);
349         }
350         return true;
351       }
352     }
353   }
354   return false;
355 }
356 
IsV2App()357 bool AppShortcutLauncherItemController::IsV2App() {
358   const Extension* extension =
359       launcher_controller()->GetExtensionForAppID(app_id());
360   return extension && extension->is_platform_app();
361 }
362 
AllowNextLaunchAttempt()363 bool AppShortcutLauncherItemController::AllowNextLaunchAttempt() {
364   if (last_launch_attempt_.is_null() ||
365       last_launch_attempt_ + base::TimeDelta::FromMilliseconds(
366           kClickSuppressionInMS) < base::Time::Now()) {
367     last_launch_attempt_ = base::Time::Now();
368     return true;
369   }
370   return false;
371 }
372