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