1 // Copyright 2013 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/browser_status_monitor.h" 6 7 #include "ash/shelf/shelf_util.h" 8 #include "ash/shell.h" 9 #include "ash/wm/window_util.h" 10 #include "base/stl_util.h" 11 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h" 12 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" 13 #include "chrome/browser/ui/browser.h" 14 #include "chrome/browser/ui/browser_finder.h" 15 #include "chrome/browser/ui/browser_list.h" 16 #include "chrome/browser/ui/browser_window.h" 17 #include "chrome/browser/ui/settings_window_manager.h" 18 #include "chrome/browser/ui/settings_window_manager_observer.h" 19 #include "chrome/browser/ui/tabs/tab_strip_model.h" 20 #include "chrome/browser/web_applications/web_app.h" 21 #include "content/public/browser/web_contents.h" 22 #include "content/public/browser/web_contents_observer.h" 23 #include "grit/ash_resources.h" 24 #include "grit/generated_resources.h" 25 #include "ui/aura/window.h" 26 #include "ui/aura/window_event_dispatcher.h" 27 #include "ui/base/l10n/l10n_util.h" 28 #include "ui/gfx/screen.h" 29 #include "ui/wm/public/activation_client.h" 30 31 // This class monitors the WebContent of the all tab and notifies a navigation 32 // to the BrowserStatusMonitor. 33 class BrowserStatusMonitor::LocalWebContentsObserver 34 : public content::WebContentsObserver { 35 public: 36 LocalWebContentsObserver(content::WebContents* contents, 37 BrowserStatusMonitor* monitor) 38 : content::WebContentsObserver(contents), 39 monitor_(monitor) {} 40 41 virtual ~LocalWebContentsObserver() {} 42 43 // content::WebContentsObserver 44 virtual void DidNavigateMainFrame( 45 const content::LoadCommittedDetails& details, 46 const content::FrameNavigateParams& params) OVERRIDE { 47 Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); 48 ChromeLauncherController::AppState state = 49 ChromeLauncherController::APP_STATE_INACTIVE; 50 if (browser->window()->IsActive() && 51 browser->tab_strip_model()->GetActiveWebContents() == web_contents()) 52 state = ChromeLauncherController::APP_STATE_WINDOW_ACTIVE; 53 else if (browser->window()->IsActive()) 54 state = ChromeLauncherController::APP_STATE_ACTIVE; 55 56 monitor_->UpdateAppItemState(web_contents(), state); 57 monitor_->UpdateBrowserItemState(); 58 59 // Navigating may change the ShelfID associated with the WebContents. 60 if (browser->tab_strip_model()->GetActiveWebContents() == web_contents()) 61 monitor_->SetShelfIDForBrowserWindowContents(browser, web_contents()); 62 } 63 64 virtual void WebContentsDestroyed() OVERRIDE { 65 // We can only come here when there was a non standard termination like 66 // an app got un-installed while running, etc. 67 monitor_->WebContentsDestroyed(web_contents()); 68 // |this| is gone now. 69 } 70 71 private: 72 BrowserStatusMonitor* monitor_; 73 74 DISALLOW_COPY_AND_ASSIGN(LocalWebContentsObserver); 75 }; 76 77 // Observes any new settings windows and sets their shelf icon (since they 78 // are excluded from BrowserShortcutLauncherItem). 79 class BrowserStatusMonitor::SettingsWindowObserver 80 : public chrome::SettingsWindowManagerObserver { 81 public: 82 SettingsWindowObserver() {} 83 virtual ~SettingsWindowObserver() {} 84 85 // SettingsWindowManagerObserver 86 virtual void OnNewSettingsWindow(Browser* settings_browser) OVERRIDE { 87 ash::SetShelfItemDetailsForDialogWindow( 88 settings_browser->window()->GetNativeWindow(), 89 IDR_ASH_SHELF_ICON_SETTINGS, 90 l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE)); 91 } 92 93 private: 94 DISALLOW_COPY_AND_ASSIGN(SettingsWindowObserver); 95 }; 96 97 BrowserStatusMonitor::BrowserStatusMonitor( 98 ChromeLauncherController* launcher_controller) 99 : launcher_controller_(launcher_controller), 100 observed_activation_clients_(this), 101 observed_root_windows_(this), 102 settings_window_observer_(new SettingsWindowObserver) { 103 DCHECK(launcher_controller_); 104 BrowserList::AddObserver(this); 105 chrome::SettingsWindowManager::GetInstance()->AddObserver( 106 settings_window_observer_.get()); 107 108 // This check needs for win7_aura. Without this, all tests in 109 // ChromeLauncherController will fail in win7_aura. 110 if (ash::Shell::HasInstance()) { 111 // We can't assume all RootWindows have the same ActivationClient. 112 // Add a RootWindow and its ActivationClient to the observed list. 113 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows(); 114 aura::Window::Windows::const_iterator iter = root_windows.begin(); 115 for (; iter != root_windows.end(); ++iter) { 116 // |observed_activation_clients_| can have the same activation client 117 // multiple times - which would be handled by the used 118 // |ScopedObserverWithDuplicatedSources|. 119 observed_activation_clients_.Add( 120 aura::client::GetActivationClient(*iter)); 121 observed_root_windows_.Add(static_cast<aura::Window*>(*iter)); 122 } 123 ash::Shell::GetInstance()->GetScreen()->AddObserver(this); 124 } 125 } 126 127 BrowserStatusMonitor::~BrowserStatusMonitor() { 128 // This check needs for win7_aura. Without this, all tests in 129 // ChromeLauncherController will fail in win7_aura. 130 if (ash::Shell::HasInstance()) 131 ash::Shell::GetInstance()->GetScreen()->RemoveObserver(this); 132 133 BrowserList::RemoveObserver(this); 134 135 BrowserList* browser_list = 136 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); 137 for (BrowserList::const_iterator i = browser_list->begin(); 138 i != browser_list->end(); ++i) { 139 OnBrowserRemoved(*i); 140 } 141 142 STLDeleteContainerPairSecondPointers(webcontents_to_observer_map_.begin(), 143 webcontents_to_observer_map_.end()); 144 } 145 146 void BrowserStatusMonitor::UpdateAppItemState( 147 content::WebContents* contents, 148 ChromeLauncherController::AppState app_state) { 149 DCHECK(contents); 150 // It is possible to come here from Browser::SwapTabContent where the contents 151 // cannot be associated with a browser. A removal however should be properly 152 // processed. 153 Browser* browser = chrome::FindBrowserWithWebContents(contents); 154 if (app_state == ChromeLauncherController::APP_STATE_REMOVED || 155 (browser && launcher_controller_->IsBrowserFromActiveUser(browser))) 156 launcher_controller_->UpdateAppState(contents, app_state); 157 } 158 159 void BrowserStatusMonitor::UpdateBrowserItemState() { 160 launcher_controller_->GetBrowserShortcutLauncherItemController()-> 161 UpdateBrowserItemState(); 162 } 163 164 void BrowserStatusMonitor::OnWindowActivated(aura::Window* gained_active, 165 aura::Window* lost_active) { 166 Browser* browser = NULL; 167 content::WebContents* contents_from_gained = NULL; 168 content::WebContents* contents_from_lost = NULL; 169 // Update active webcontents's app item state of |lost_active|, if existed. 170 if (lost_active) { 171 browser = chrome::FindBrowserWithWindow(lost_active); 172 if (browser) 173 contents_from_lost = browser->tab_strip_model()->GetActiveWebContents(); 174 if (contents_from_lost) { 175 UpdateAppItemState( 176 contents_from_lost, 177 ChromeLauncherController::APP_STATE_INACTIVE); 178 } 179 } 180 181 // Update active webcontents's app item state of |gained_active|, if existed. 182 if (gained_active) { 183 browser = chrome::FindBrowserWithWindow(gained_active); 184 if (browser) 185 contents_from_gained = browser->tab_strip_model()->GetActiveWebContents(); 186 if (contents_from_gained) { 187 UpdateAppItemState( 188 contents_from_gained, 189 ChromeLauncherController::APP_STATE_WINDOW_ACTIVE); 190 } 191 } 192 193 if (contents_from_lost || contents_from_gained) 194 UpdateBrowserItemState(); 195 } 196 197 void BrowserStatusMonitor::OnWindowDestroyed(aura::Window* window) { 198 // Remove RootWindow and its ActivationClient from observed list. 199 observed_root_windows_.Remove(window); 200 observed_activation_clients_.Remove( 201 aura::client::GetActivationClient(window)); 202 } 203 204 void BrowserStatusMonitor::OnBrowserAdded(Browser* browser) { 205 if (browser->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH) 206 return; 207 208 if (browser->is_type_popup() && browser->is_app()) { 209 // Note: A V1 application will set the tab strip observer when the app gets 210 // added to the shelf. This makes sure that in the multi user case we will 211 // only set the observer while the app item exists in the shelf. 212 AddV1AppToShelf(browser); 213 } else { 214 browser->tab_strip_model()->AddObserver(this); 215 } 216 } 217 218 void BrowserStatusMonitor::OnBrowserRemoved(Browser* browser) { 219 if (browser->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH) 220 return; 221 222 if (browser->is_type_popup() && browser->is_app()) 223 RemoveV1AppFromShelf(browser); 224 else 225 browser->tab_strip_model()->RemoveObserver(this); 226 227 UpdateBrowserItemState(); 228 } 229 230 void BrowserStatusMonitor::OnDisplayAdded(const gfx::Display& new_display) { 231 // Add a new RootWindow and its ActivationClient to observed list. 232 aura::Window* root_window = ash::Shell::GetInstance()-> 233 display_controller()->GetRootWindowForDisplayId(new_display.id()); 234 // When the primary root window's display get removed, the existing root 235 // window is taken over by the new display and the observer is already set. 236 if (!observed_root_windows_.IsObserving(root_window)) { 237 observed_root_windows_.Add(static_cast<aura::Window*>(root_window)); 238 observed_activation_clients_.Add( 239 aura::client::GetActivationClient(root_window)); 240 } 241 } 242 243 void BrowserStatusMonitor::OnDisplayRemoved(const gfx::Display& old_display) { 244 // When this is called, RootWindow of |old_display| is already removed. 245 // Instead, we can remove RootWindow and its ActivationClient in the 246 // OnWindowRemoved(). 247 // Do nothing here. 248 } 249 250 void BrowserStatusMonitor::OnDisplayMetricsChanged(const gfx::Display&, 251 uint32_t) { 252 // Do nothing here. 253 } 254 255 void BrowserStatusMonitor::ActiveTabChanged(content::WebContents* old_contents, 256 content::WebContents* new_contents, 257 int index, 258 int reason) { 259 Browser* browser = NULL; 260 // Use |new_contents|. |old_contents| could be NULL. 261 DCHECK(new_contents); 262 browser = chrome::FindBrowserWithWebContents(new_contents); 263 264 if (browser && browser->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH) 265 return; 266 267 ChromeLauncherController::AppState state = 268 ChromeLauncherController::APP_STATE_INACTIVE; 269 270 // Update immediately on a tab change. 271 if (old_contents && 272 (TabStripModel::kNoTab != 273 browser->tab_strip_model()->GetIndexOfWebContents(old_contents))) 274 UpdateAppItemState(old_contents, state); 275 276 if (new_contents) { 277 state = browser->window()->IsActive() ? 278 ChromeLauncherController::APP_STATE_WINDOW_ACTIVE : 279 ChromeLauncherController::APP_STATE_ACTIVE; 280 UpdateAppItemState(new_contents, state); 281 UpdateBrowserItemState(); 282 SetShelfIDForBrowserWindowContents(browser, new_contents); 283 } 284 } 285 286 void BrowserStatusMonitor::TabReplacedAt(TabStripModel* tab_strip_model, 287 content::WebContents* old_contents, 288 content::WebContents* new_contents, 289 int index) { 290 DCHECK(old_contents && new_contents); 291 Browser* browser = chrome::FindBrowserWithWebContents(new_contents); 292 293 if (browser && browser->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH) 294 return; 295 296 UpdateAppItemState(old_contents, 297 ChromeLauncherController::APP_STATE_REMOVED); 298 RemoveWebContentsObserver(old_contents); 299 300 ChromeLauncherController::AppState state = 301 ChromeLauncherController::APP_STATE_ACTIVE; 302 if (browser->window()->IsActive() && 303 (tab_strip_model->GetActiveWebContents() == new_contents)) 304 state = ChromeLauncherController::APP_STATE_WINDOW_ACTIVE; 305 UpdateAppItemState(new_contents, state); 306 UpdateBrowserItemState(); 307 308 if (tab_strip_model->GetActiveWebContents() == new_contents) 309 SetShelfIDForBrowserWindowContents(browser, new_contents); 310 311 AddWebContentsObserver(new_contents); 312 } 313 314 void BrowserStatusMonitor::TabInsertedAt(content::WebContents* contents, 315 int index, 316 bool foreground) { 317 // An inserted tab is not active - ActiveTabChanged() will be called to 318 // activate. We initialize therefore with |APP_STATE_INACTIVE|. 319 UpdateAppItemState(contents, 320 ChromeLauncherController::APP_STATE_INACTIVE); 321 AddWebContentsObserver(contents); 322 } 323 324 void BrowserStatusMonitor::TabClosingAt(TabStripModel* tab_strip_mode, 325 content::WebContents* contents, 326 int index) { 327 UpdateAppItemState(contents, 328 ChromeLauncherController::APP_STATE_REMOVED); 329 RemoveWebContentsObserver(contents); 330 } 331 332 void BrowserStatusMonitor::WebContentsDestroyed( 333 content::WebContents* contents) { 334 UpdateAppItemState(contents, ChromeLauncherController::APP_STATE_REMOVED); 335 RemoveWebContentsObserver(contents); 336 } 337 338 void BrowserStatusMonitor::AddV1AppToShelf(Browser* browser) { 339 DCHECK(browser->is_type_popup() && browser->is_app()); 340 341 browser->tab_strip_model()->AddObserver(this); 342 343 std::string app_id = 344 web_app::GetExtensionIdFromApplicationName(browser->app_name()); 345 if (!app_id.empty()) { 346 browser_to_app_id_map_[browser] = app_id; 347 launcher_controller_->LockV1AppWithID(app_id); 348 } 349 } 350 351 void BrowserStatusMonitor::RemoveV1AppFromShelf(Browser* browser) { 352 DCHECK(browser->is_type_popup() && browser->is_app()); 353 354 browser->tab_strip_model()->RemoveObserver(this); 355 356 if (browser_to_app_id_map_.find(browser) != browser_to_app_id_map_.end()) { 357 launcher_controller_->UnlockV1AppWithID(browser_to_app_id_map_[browser]); 358 browser_to_app_id_map_.erase(browser); 359 } 360 } 361 362 bool BrowserStatusMonitor::IsV1AppInShelf(Browser* browser) { 363 return browser_to_app_id_map_.find(browser) != browser_to_app_id_map_.end(); 364 } 365 366 void BrowserStatusMonitor::AddWebContentsObserver( 367 content::WebContents* contents) { 368 if (webcontents_to_observer_map_.find(contents) == 369 webcontents_to_observer_map_.end()) { 370 webcontents_to_observer_map_[contents] = 371 new LocalWebContentsObserver(contents, this); 372 } 373 } 374 375 void BrowserStatusMonitor::RemoveWebContentsObserver( 376 content::WebContents* contents) { 377 DCHECK(webcontents_to_observer_map_.find(contents) != 378 webcontents_to_observer_map_.end()); 379 delete webcontents_to_observer_map_[contents]; 380 webcontents_to_observer_map_.erase(contents); 381 } 382 383 ash::ShelfID BrowserStatusMonitor::GetShelfIDForWebContents( 384 content::WebContents* contents) { 385 return launcher_controller_->GetShelfIDForWebContents(contents); 386 } 387 388 void BrowserStatusMonitor::SetShelfIDForBrowserWindowContents( 389 Browser* browser, 390 content::WebContents* web_contents) { 391 launcher_controller_->GetBrowserShortcutLauncherItemController()-> 392 SetShelfIDForBrowserWindowContents(browser, web_contents); 393 } 394