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.
33class 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).
79class 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
97BrowserStatusMonitor::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
127BrowserStatusMonitor::~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
146void 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
159void BrowserStatusMonitor::UpdateBrowserItemState() {
160  launcher_controller_->GetBrowserShortcutLauncherItemController()->
161      UpdateBrowserItemState();
162}
163
164void 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
197void 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
204void 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
218void 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
230void 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
243void 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
250void BrowserStatusMonitor::OnDisplayMetricsChanged(const gfx::Display&,
251                                                   uint32_t) {
252  // Do nothing here.
253}
254
255void 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
286void 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
314void 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
324void 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
332void BrowserStatusMonitor::WebContentsDestroyed(
333    content::WebContents* contents) {
334  UpdateAppItemState(contents, ChromeLauncherController::APP_STATE_REMOVED);
335  RemoveWebContentsObserver(contents);
336}
337
338void 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
351void 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
362bool BrowserStatusMonitor::IsV1AppInShelf(Browser* browser) {
363  return browser_to_app_id_map_.find(browser) != browser_to_app_id_map_.end();
364}
365
366void 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
375void 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
383ash::ShelfID BrowserStatusMonitor::GetShelfIDForWebContents(
384    content::WebContents* contents) {
385  return launcher_controller_->GetShelfIDForWebContents(contents);
386}
387
388void BrowserStatusMonitor::SetShelfIDForBrowserWindowContents(
389    Browser* browser,
390    content::WebContents* web_contents) {
391  launcher_controller_->GetBrowserShortcutLauncherItemController()->
392      SetShelfIDForBrowserWindowContents(browser, web_contents);
393}
394