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