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 "ash/shelf/shelf_model.h" 8 #include "ash/shell.h" 9 #include "ash/wm/window_util.h" 10 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" 11 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h" 12 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" 13 #include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h" 14 #include "chrome/browser/ui/ash/launcher/launcher_context_menu.h" 15 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" 16 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h" 17 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h" 18 #include "chrome/browser/ui/browser.h" 19 #include "chrome/browser/ui/browser_finder.h" 20 #include "chrome/browser/ui/browser_list.h" 21 #include "chrome/browser/ui/browser_window.h" 22 #include "chrome/browser/ui/host_desktop.h" 23 #include "chrome/browser/ui/tabs/tab_strip_model.h" 24 #include "chrome/browser/web_applications/web_app.h" 25 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" 26 #include "content/public/browser/navigation_entry.h" 27 #include "content/public/browser/web_contents.h" 28 #include "extensions/browser/app_window/native_app_window.h" 29 #include "extensions/browser/extension_registry.h" 30 #include "extensions/browser/process_manager.h" 31 #include "ui/aura/window.h" 32 #include "ui/events/event.h" 33 #include "ui/wm/core/window_animations.h" 34 35 using extensions::Extension; 36 using extensions::ExtensionRegistry; 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. 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). 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 79 AppShortcutLauncherItemController::~AppShortcutLauncherItemController() { 80 } 81 82 bool AppShortcutLauncherItemController::IsOpen() const { 83 return !chrome_launcher_controller_-> 84 GetV1ApplicationsFromAppId(app_id()).empty(); 85 } 86 87 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 99 void AppShortcutLauncherItemController::Launch(ash::LaunchSource source, 100 int event_flags) { 101 launcher_controller()->LaunchApp(app_id(), source, event_flags); 102 } 103 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 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 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*> 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 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 205 base::string16 AppShortcutLauncherItemController::GetTitle() { 206 return GetAppTitle(); 207 } 208 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 216 ash::ShelfMenuModel* AppShortcutLauncherItemController::CreateApplicationMenu( 217 int event_flags) { 218 return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags)); 219 } 220 221 bool AppShortcutLauncherItemController::IsDraggable() { 222 return true; 223 } 224 225 bool AppShortcutLauncherItemController::ShouldShowTooltip() { 226 return true; 227 } 228 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 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 = 291 ExtensionRegistry::Get(browser->profile())->GetExtensionById( 292 web_app::GetExtensionIdFromApplicationName(browser->app_name()), 293 ExtensionRegistry::EVERYTHING); 294 return browser_extension == extension; 295 } 296 297 // There are three ways to identify the association of a URL with this 298 // extension: 299 // - The refocus pattern is matched (needed for apps like drive). 300 // - The extension's origin + extent gets matched. 301 // - The launcher controller knows that the tab got created for this app. 302 const GURL tab_url = web_contents->GetURL(); 303 return ((!refocus_pattern.match_all_urls() && 304 refocus_pattern.MatchesURL(tab_url)) || 305 (extension->OverlapsWithOrigin(tab_url) && 306 extension->web_extent().MatchesURL(tab_url)) || 307 launcher_controller()->IsWebContentHandledByApplication(web_contents, 308 app_id())); 309 } 310 311 void AppShortcutLauncherItemController::ActivateContent( 312 content::WebContents* content) { 313 Browser* browser = chrome::FindBrowserWithWebContents(content); 314 TabStripModel* tab_strip = browser->tab_strip_model(); 315 int index = tab_strip->GetIndexOfWebContents(content); 316 DCHECK_NE(TabStripModel::kNoTab, index); 317 318 int old_index = tab_strip->active_index(); 319 if (index != old_index) 320 tab_strip->ActivateTabAt(index, false); 321 launcher_controller()->ActivateWindowOrMinimizeIfActive( 322 browser->window(), 323 index == old_index && GetRunningApplications().size() == 1); 324 } 325 326 bool AppShortcutLauncherItemController::AdvanceToNextApp() { 327 std::vector<content::WebContents*> items = GetRunningApplications(); 328 if (items.size() >= 1) { 329 Browser* browser = chrome::FindBrowserWithWindow( 330 ash::wm::GetActiveWindow()); 331 if (browser) { 332 TabStripModel* tab_strip = browser->tab_strip_model(); 333 content::WebContents* active = tab_strip->GetWebContentsAt( 334 tab_strip->active_index()); 335 std::vector<content::WebContents*>::const_iterator i( 336 std::find(items.begin(), items.end(), active)); 337 if (i != items.end()) { 338 if (items.size() == 1) { 339 // If there is only a single item available, we animate it upon key 340 // action. 341 AnimateWindow(browser->window()->GetNativeWindow(), 342 wm::WINDOW_ANIMATION_TYPE_BOUNCE); 343 } else { 344 int index = (static_cast<int>(i - items.begin()) + 1) % items.size(); 345 ActivateContent(items[index]); 346 } 347 return true; 348 } 349 } 350 } 351 return false; 352 } 353 354 bool AppShortcutLauncherItemController::IsV2App() { 355 const Extension* extension = 356 launcher_controller()->GetExtensionForAppID(app_id()); 357 return extension && extension->is_platform_app(); 358 } 359 360 bool AppShortcutLauncherItemController::AllowNextLaunchAttempt() { 361 if (last_launch_attempt_.is_null() || 362 last_launch_attempt_ + base::TimeDelta::FromMilliseconds( 363 kClickSuppressionInMS) < base::Time::Now()) { 364 last_launch_attempt_ = base::Time::Now(); 365 return true; 366 } 367 return false; 368 } 369