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