1// Copyright (c) 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_shortcut_launcher_item_controller.h"
6
7#include <vector>
8
9#include "ash/shelf/shelf.h"
10#include "ash/shelf/shelf_model.h"
11#include "ash/shelf/shelf_util.h"
12#include "ash/shell.h"
13#include "ash/wm/window_util.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
16#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h"
17#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
18#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
19#include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h"
20#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
21#include "chrome/browser/ui/browser.h"
22#include "chrome/browser/ui/browser_finder.h"
23#include "chrome/browser/ui/browser_list.h"
24#include "chrome/browser/ui/browser_window.h"
25#include "chrome/browser/ui/chrome_pages.h"
26#include "chrome/browser/ui/settings_window_manager.h"
27#include "chrome/browser/ui/tabs/tab_strip_model.h"
28#include "chrome/browser/web_applications/web_app.h"
29#include "chrome/common/extensions/extension_constants.h"
30#include "content/public/browser/web_contents.h"
31#include "content/public/common/url_constants.h"
32#include "grit/ash_resources.h"
33#include "grit/chromium_strings.h"
34#include "grit/generated_resources.h"
35#include "ui/aura/window.h"
36#include "ui/base/l10n/l10n_util.h"
37#include "ui/base/resource/resource_bundle.h"
38#include "ui/events/event.h"
39#include "ui/gfx/image/image.h"
40#include "ui/wm/core/window_animations.h"
41
42BrowserShortcutLauncherItemController::BrowserShortcutLauncherItemController(
43    ChromeLauncherController* launcher_controller)
44    : LauncherItemController(TYPE_SHORTCUT,
45                             extension_misc::kChromeAppId,
46                             launcher_controller) {
47}
48
49BrowserShortcutLauncherItemController::
50    ~BrowserShortcutLauncherItemController() {
51}
52
53void BrowserShortcutLauncherItemController::UpdateBrowserItemState() {
54  // The shell will not be available for win7_aura unittests like
55  // ChromeLauncherControllerTest.BrowserMenuGeneration.
56  if (!ash::Shell::HasInstance())
57    return;
58
59  ash::ShelfModel* model = launcher_controller()->model();
60
61  // Determine the new browser's active state and change if necessary.
62  int browser_index = model->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT);
63  DCHECK_GE(browser_index, 0);
64  ash::ShelfItem browser_item = model->items()[browser_index];
65  ash::ShelfItemStatus browser_status = ash::STATUS_CLOSED;
66
67  aura::Window* window = ash::wm::GetActiveWindow();
68  if (window) {
69    // Check if the active browser / tab is a browser which is not an app,
70    // a windowed app, a popup or any other item which is not a browser of
71    // interest.
72    Browser* browser = chrome::FindBrowserWithWindow(window);
73    if (IsBrowserRepresentedInBrowserList(browser)) {
74      browser_status = ash::STATUS_ACTIVE;
75      // If an app that has item is running in active WebContents, browser item
76      // status cannot be active.
77      content::WebContents* contents =
78          browser->tab_strip_model()->GetActiveWebContents();
79      if (contents &&
80          (launcher_controller()->GetShelfIDForWebContents(contents) !=
81              browser_item.id))
82        browser_status = ash::STATUS_RUNNING;
83    }
84  }
85
86  if (browser_status == ash::STATUS_CLOSED) {
87    const BrowserList* ash_browser_list =
88        BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
89    for (BrowserList::const_reverse_iterator it =
90             ash_browser_list->begin_last_active();
91         it != ash_browser_list->end_last_active() &&
92         browser_status == ash::STATUS_CLOSED; ++it) {
93      if (IsBrowserRepresentedInBrowserList(*it))
94        browser_status = ash::STATUS_RUNNING;
95    }
96  }
97
98  if (browser_status != browser_item.status) {
99    browser_item.status = browser_status;
100    model->Set(browser_index, browser_item);
101  }
102}
103
104void BrowserShortcutLauncherItemController::SetShelfIDForBrowserWindowContents(
105    Browser* browser,
106    content::WebContents* web_contents) {
107  // We need to call SetShelfIDForWindow for V1 applications since they are
108  // content which might change and as such change the application type.
109  if (!browser ||
110      !launcher_controller()->IsBrowserFromActiveUser(browser) ||
111      browser->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH ||
112      chrome::IsTrustedPopupWindowWithScheme(browser, content::kChromeUIScheme))
113    return;
114
115  ash::SetShelfIDForWindow(
116      launcher_controller()->GetShelfIDForWebContents(web_contents),
117      browser->window()->GetNativeWindow());
118}
119
120bool BrowserShortcutLauncherItemController::IsOpen() const {
121  const BrowserList* ash_browser_list =
122      BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
123  for (BrowserList::const_iterator it = ash_browser_list->begin();
124       it != ash_browser_list->end(); ++it) {
125    if (launcher_controller()->IsBrowserFromActiveUser(*it))
126      return true;
127  }
128  return false;
129}
130
131bool BrowserShortcutLauncherItemController::IsVisible() const {
132  Browser* last_browser = chrome::FindTabbedBrowser(
133      launcher_controller()->profile(),
134      true,
135      chrome::HOST_DESKTOP_TYPE_ASH);
136
137  if (!last_browser) {
138    return false;
139  }
140
141  aura::Window* window = last_browser->window()->GetNativeWindow();
142  return ash::wm::IsActiveWindow(window);
143}
144
145void BrowserShortcutLauncherItemController::Launch(ash::LaunchSource source,
146                                                   int event_flags) {
147}
148
149bool BrowserShortcutLauncherItemController::Activate(ash::LaunchSource source) {
150  Browser* last_browser = chrome::FindTabbedBrowser(
151      launcher_controller()->profile(),
152      true,
153      chrome::HOST_DESKTOP_TYPE_ASH);
154
155  if (!last_browser) {
156    launcher_controller()->CreateNewWindow();
157    return true;
158  }
159
160  launcher_controller()->ActivateWindowOrMinimizeIfActive(
161      last_browser->window(), GetApplicationList(0).size() == 2);
162  return false;
163}
164
165void BrowserShortcutLauncherItemController::Close() {
166}
167
168ChromeLauncherAppMenuItems
169BrowserShortcutLauncherItemController::GetApplicationList(int event_flags) {
170  ChromeLauncherAppMenuItems items;
171  bool found_tabbed_browser = false;
172  // Add the application name to the menu.
173  items.push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL, false));
174  const BrowserList* ash_browser_list =
175      BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
176  for (BrowserList::const_iterator it = ash_browser_list->begin();
177       it != ash_browser_list->end(); ++it) {
178    Browser* browser = *it;
179    // Make sure that the browser was already shown, is from the current user
180    // and has a proper window.
181    if (!launcher_controller()->IsBrowserFromActiveUser(browser) ||
182        std::find(ash_browser_list->begin_last_active(),
183                  ash_browser_list->end_last_active(),
184                  browser) == ash_browser_list->end_last_active() ||
185        !browser->window())
186      continue;
187    if (browser->is_type_tabbed())
188      found_tabbed_browser = true;
189    else if (!IsBrowserRepresentedInBrowserList(browser))
190      continue;
191    TabStripModel* tab_strip = browser->tab_strip_model();
192    if (tab_strip->active_index() == -1)
193      continue;
194    if (!(event_flags & ui::EF_SHIFT_DOWN)) {
195      content::WebContents* web_contents =
196          tab_strip->GetWebContentsAt(tab_strip->active_index());
197      gfx::Image app_icon = GetBrowserListIcon(web_contents);
198      base::string16 title = GetBrowserListTitle(web_contents);
199      items.push_back(new ChromeLauncherAppMenuItemBrowser(
200          title, &app_icon, browser, items.size() == 1));
201    } else {
202      for (int index = 0; index  < tab_strip->count(); ++index) {
203        content::WebContents* web_contents =
204            tab_strip->GetWebContentsAt(index);
205        gfx::Image app_icon =
206            launcher_controller()->GetAppListIcon(web_contents);
207        base::string16 title =
208            launcher_controller()->GetAppListTitle(web_contents);
209        // Check if we need to insert a separator in front.
210        bool leading_separator = !index;
211        items.push_back(new ChromeLauncherAppMenuItemTab(
212            title, &app_icon, web_contents, leading_separator));
213      }
214    }
215  }
216  // If only windowed applications are open, we return an empty list to
217  // enforce the creation of a new browser.
218  if (!found_tabbed_browser)
219    items.clear();
220  return items.Pass();
221}
222
223bool BrowserShortcutLauncherItemController::ItemSelected(
224    const ui::Event& event) {
225  if (event.flags() & ui::EF_CONTROL_DOWN) {
226    launcher_controller()->CreateNewWindow();
227    return true;
228  }
229
230  // In case of a keyboard event, we were called by a hotkey. In that case we
231  // activate the next item in line if an item of our list is already active.
232  if (event.type() & ui::ET_KEY_RELEASED) {
233    ActivateOrAdvanceToNextBrowser();
234    return false;
235  }
236
237  return Activate(ash::LAUNCH_FROM_UNKNOWN);
238}
239
240base::string16 BrowserShortcutLauncherItemController::GetTitle() {
241  return l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
242}
243
244ui::MenuModel* BrowserShortcutLauncherItemController::CreateContextMenu(
245    aura::Window* root_window) {
246  ash::ShelfItem item =
247      *(launcher_controller()->model()->ItemByID(shelf_id()));
248  return new LauncherContextMenu(launcher_controller(), &item, root_window);
249}
250
251ash::ShelfMenuModel*
252BrowserShortcutLauncherItemController::CreateApplicationMenu(int event_flags) {
253  return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags));
254}
255
256bool BrowserShortcutLauncherItemController::IsDraggable() {
257  return launcher_controller()->CanPin() ? true : false;
258}
259
260bool BrowserShortcutLauncherItemController::ShouldShowTooltip() {
261  return true;
262}
263
264gfx::Image BrowserShortcutLauncherItemController::GetBrowserListIcon(
265    content::WebContents* web_contents) const {
266  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
267  return rb.GetImageNamed(IsIncognito(web_contents) ?
268      IDR_ASH_SHELF_LIST_INCOGNITO_BROWSER :
269      IDR_ASH_SHELF_LIST_BROWSER);
270}
271
272base::string16 BrowserShortcutLauncherItemController::GetBrowserListTitle(
273    content::WebContents* web_contents) const {
274  base::string16 title = web_contents->GetTitle();
275  if (!title.empty())
276    return title;
277  return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
278}
279
280bool BrowserShortcutLauncherItemController::IsIncognito(
281    content::WebContents* web_contents) const {
282  const Profile* profile =
283      Profile::FromBrowserContext(web_contents->GetBrowserContext());
284  return profile->IsOffTheRecord() && !profile->IsGuestSession();
285}
286
287void BrowserShortcutLauncherItemController::ActivateOrAdvanceToNextBrowser() {
288  // Create a list of all suitable running browsers.
289  std::vector<Browser*> items;
290  // We use the list in the order of how the browsers got created - not the LRU
291  // order.
292  const BrowserList* ash_browser_list =
293      BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
294  for (BrowserList::const_iterator it =
295           ash_browser_list->begin();
296       it != ash_browser_list->end(); ++it) {
297    if (IsBrowserRepresentedInBrowserList(*it))
298      items.push_back(*it);
299  }
300  // If there are no suitable browsers we create a new one.
301  if (items.empty()) {
302    launcher_controller()->CreateNewWindow();
303    return;
304  }
305  Browser* browser = chrome::FindBrowserWithWindow(ash::wm::GetActiveWindow());
306  if (items.size() == 1) {
307    // If there is only one suitable browser, we can either activate it, or
308    // bounce it (if it is already active).
309    if (browser == items[0]) {
310      AnimateWindow(browser->window()->GetNativeWindow(),
311                    wm::WINDOW_ANIMATION_TYPE_BOUNCE);
312      return;
313    }
314    browser = items[0];
315  } else {
316    // If there is more then one suitable browser, we advance to the next if
317    // |browser| is already active - or - check the last used browser if it can
318    // be used.
319    std::vector<Browser*>::iterator i =
320        std::find(items.begin(), items.end(), browser);
321    if (i != items.end()) {
322      browser = (++i == items.end()) ? items[0] : *i;
323    } else {
324      browser = chrome::FindTabbedBrowser(launcher_controller()->profile(),
325                                          true,
326                                          chrome::HOST_DESKTOP_TYPE_ASH);
327      if (!browser ||
328          !IsBrowserRepresentedInBrowserList(browser))
329        browser = items[0];
330    }
331  }
332  DCHECK(browser);
333  browser->window()->Show();
334  browser->window()->Activate();
335}
336
337bool BrowserShortcutLauncherItemController::IsBrowserRepresentedInBrowserList(
338    Browser* browser) {
339  // Only Ash desktop browser windows for the active user are represented.
340  if (!browser ||
341      !launcher_controller()->IsBrowserFromActiveUser(browser) ||
342      browser->host_desktop_type() != chrome::HOST_DESKTOP_TYPE_ASH)
343    return false;
344
345  // v1 App popup windows with a valid app id have their own icon.
346  if (browser->is_app() &&
347      browser->is_type_popup() &&
348      launcher_controller()->GetShelfIDForAppID(
349          web_app::GetExtensionIdFromApplicationName(browser->app_name())) > 0)
350    return false;
351
352  // Stand-alone chrome:// windows (e.g. settings) have their own icon.
353  if (chrome::IsTrustedPopupWindowWithScheme(browser, content::kChromeUIScheme))
354    return false;
355
356  // Tabbed browser and other popup windows are all represented.
357  return true;
358}
359