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/views/chrome_views_delegate.h"
6
7#include "base/memory/scoped_ptr.h"
8#include "base/prefs/pref_service.h"
9#include "base/prefs/scoped_user_pref_update.h"
10#include "base/strings/string_util.h"
11#include "base/strings/utf_string_conversions.h"
12#include "base/time/time.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/profiles/profile_manager.h"
15#include "chrome/browser/ui/browser_window_state.h"
16#include "chrome/browser/ui/views/accessibility/accessibility_event_router_views.h"
17#include "chrome/common/pref_names.h"
18#include "grit/chrome_unscaled_resources.h"
19#include "ui/base/resource/resource_bundle.h"
20#include "ui/base/ui_base_switches.h"
21#include "ui/gfx/rect.h"
22#include "ui/gfx/screen.h"
23#include "ui/views/widget/native_widget.h"
24#include "ui/views/widget/widget.h"
25
26#if defined(OS_WIN)
27#include <dwmapi.h>
28#include <shellapi.h>
29#include "base/task_runner_util.h"
30#include "base/win/windows_version.h"
31#include "chrome/browser/app_icon_win.h"
32#include "content/public/browser/browser_thread.h"
33#include "ui/base/win/shell.h"
34#endif
35
36#if defined(USE_AURA)
37#include "content/public/browser/context_factory.h"
38#include "ui/aura/window.h"
39#include "ui/aura/window_event_dispatcher.h"
40#endif
41
42#if defined(USE_AURA) && !defined(OS_CHROMEOS)
43#include "chrome/browser/ui/host_desktop.h"
44#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
45#include "ui/views/widget/native_widget_aura.h"
46#endif
47
48#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
49#include "ui/views/linux_ui/linux_ui.h"
50#endif
51
52#if defined(USE_ASH)
53#include "ash/shell.h"
54#include "ash/wm/window_state.h"
55#include "chrome/browser/ui/ash/accessibility/automation_manager_ash.h"
56#include "chrome/browser/ui/ash/ash_init.h"
57#include "chrome/browser/ui/ash/ash_util.h"
58#endif
59
60
61// Helpers --------------------------------------------------------------------
62
63namespace {
64
65Profile* GetProfileForWindow(const views::Widget* window) {
66  if (!window)
67    return NULL;
68  return reinterpret_cast<Profile*>(
69      window->GetNativeWindowProperty(Profile::kProfileKey));
70}
71
72// If the given window has a profile associated with it, use that profile's
73// preference service. Otherwise, store and retrieve the data from Local State.
74// This function may return NULL if the necessary pref service has not yet
75// been initialized.
76// TODO(mirandac): This function will also separate windows by profile in a
77// multi-profile environment.
78PrefService* GetPrefsForWindow(const views::Widget* window) {
79  Profile* profile = GetProfileForWindow(window);
80  if (!profile) {
81    // Use local state for windows that have no explicit profile.
82    return g_browser_process->local_state();
83  }
84  return profile->GetPrefs();
85}
86
87#if defined(OS_WIN)
88bool MonitorHasTopmostAutohideTaskbarForEdge(UINT edge, HMONITOR monitor) {
89  APPBARDATA taskbar_data = { sizeof(APPBARDATA), NULL, 0, edge };
90  // MSDN documents an ABM_GETAUTOHIDEBAREX, which supposedly takes a monitor
91  // rect and returns autohide bars on that monitor.  This sounds like a good
92  // idea for multi-monitor systems.  Unfortunately, it appears to not work at
93  // least some of the time (erroneously returning NULL) and there's almost no
94  // online documentation or other sample code using it that suggests ways to
95  // address this problem.  So we just use ABM_GETAUTOHIDEBAR and hope the user
96  // only cares about autohide bars on the monitor with the primary taskbar.
97  //
98  // NOTE: This call spins a nested message loop.
99  HWND taskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAR,
100                                                        &taskbar_data));
101  return ::IsWindow(taskbar) &&
102      (MonitorFromWindow(taskbar, MONITOR_DEFAULTTONULL) == monitor) &&
103      (GetWindowLong(taskbar, GWL_EXSTYLE) & WS_EX_TOPMOST);
104}
105
106int GetAppbarAutohideEdgesOnWorkerThread(HMONITOR monitor) {
107  DCHECK(monitor);
108
109  int edges = 0;
110  if (MonitorHasTopmostAutohideTaskbarForEdge(ABE_LEFT, monitor))
111    edges |= views::ViewsDelegate::EDGE_LEFT;
112  if (MonitorHasTopmostAutohideTaskbarForEdge(ABE_TOP, monitor))
113    edges |= views::ViewsDelegate::EDGE_TOP;
114  if (MonitorHasTopmostAutohideTaskbarForEdge(ABE_RIGHT, monitor))
115    edges |= views::ViewsDelegate::EDGE_RIGHT;
116  if (MonitorHasTopmostAutohideTaskbarForEdge(ABE_BOTTOM, monitor))
117    edges |= views::ViewsDelegate::EDGE_BOTTOM;
118  return edges;
119}
120#endif
121
122}  // namespace
123
124
125// ChromeViewsDelegate --------------------------------------------------------
126
127#if defined(OS_WIN)
128ChromeViewsDelegate::ChromeViewsDelegate()
129    : in_autohide_edges_callback_(false),
130      weak_factory_(this) {
131#else
132ChromeViewsDelegate::ChromeViewsDelegate() {
133#endif
134}
135
136ChromeViewsDelegate::~ChromeViewsDelegate() {
137}
138
139void ChromeViewsDelegate::SaveWindowPlacement(const views::Widget* window,
140                                              const std::string& window_name,
141                                              const gfx::Rect& bounds,
142                                              ui::WindowShowState show_state) {
143  PrefService* prefs = GetPrefsForWindow(window);
144  if (!prefs)
145    return;
146
147  scoped_ptr<DictionaryPrefUpdate> pref_update =
148      chrome::GetWindowPlacementDictionaryReadWrite(window_name, prefs);
149  base::DictionaryValue* window_preferences = pref_update->Get();
150  window_preferences->SetInteger("left", bounds.x());
151  window_preferences->SetInteger("top", bounds.y());
152  window_preferences->SetInteger("right", bounds.right());
153  window_preferences->SetInteger("bottom", bounds.bottom());
154  window_preferences->SetBoolean("maximized",
155                                 show_state == ui::SHOW_STATE_MAXIMIZED);
156  gfx::Rect work_area(gfx::Screen::GetScreenFor(window->GetNativeView())->
157      GetDisplayNearestWindow(window->GetNativeView()).work_area());
158  window_preferences->SetInteger("work_area_left", work_area.x());
159  window_preferences->SetInteger("work_area_top", work_area.y());
160  window_preferences->SetInteger("work_area_right", work_area.right());
161  window_preferences->SetInteger("work_area_bottom", work_area.bottom());
162}
163
164bool ChromeViewsDelegate::GetSavedWindowPlacement(
165    const views::Widget* widget,
166    const std::string& window_name,
167    gfx::Rect* bounds,
168    ui::WindowShowState* show_state) const {
169  PrefService* prefs = g_browser_process->local_state();
170  if (!prefs)
171    return false;
172
173  DCHECK(prefs->FindPreference(window_name.c_str()));
174  const base::DictionaryValue* dictionary =
175      prefs->GetDictionary(window_name.c_str());
176  int left = 0;
177  int top = 0;
178  int right = 0;
179  int bottom = 0;
180  if (!dictionary || !dictionary->GetInteger("left", &left) ||
181      !dictionary->GetInteger("top", &top) ||
182      !dictionary->GetInteger("right", &right) ||
183      !dictionary->GetInteger("bottom", &bottom))
184    return false;
185
186  bounds->SetRect(left, top, right - left, bottom - top);
187
188  bool maximized = false;
189  if (dictionary)
190    dictionary->GetBoolean("maximized", &maximized);
191  *show_state = maximized ? ui::SHOW_STATE_MAXIMIZED : ui::SHOW_STATE_NORMAL;
192
193#if defined(USE_ASH)
194  // On Ash environment, a window won't span across displays.  Adjust
195  // the bounds to fit the work area.
196  gfx::NativeView window = widget->GetNativeView();
197  if (chrome::GetHostDesktopTypeForNativeView(window) ==
198      chrome::HOST_DESKTOP_TYPE_ASH) {
199    gfx::Display display = gfx::Screen::GetScreenFor(window)->
200        GetDisplayMatching(*bounds);
201    bounds->AdjustToFit(display.work_area());
202    ash::wm::GetWindowState(window)->set_minimum_visibility(true);
203  }
204#endif
205  return true;
206}
207
208void ChromeViewsDelegate::NotifyAccessibilityEvent(
209    views::View* view, ui::AXEvent event_type) {
210  AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
211      view, event_type);
212
213#if defined(USE_ASH)
214  AutomationManagerAsh::GetInstance()->HandleEvent(
215      GetProfileForWindow(view->GetWidget()), view, event_type);
216#endif
217}
218
219void ChromeViewsDelegate::NotifyMenuItemFocused(
220    const base::string16& menu_name,
221    const base::string16& menu_item_name,
222    int item_index,
223    int item_count,
224    bool has_submenu) {
225  AccessibilityEventRouterViews::GetInstance()->HandleMenuItemFocused(
226      menu_name, menu_item_name, item_index, item_count, has_submenu);
227}
228
229#if defined(OS_WIN)
230HICON ChromeViewsDelegate::GetDefaultWindowIcon() const {
231  return GetAppIcon();
232}
233
234bool ChromeViewsDelegate::IsWindowInMetro(gfx::NativeWindow window) const {
235  return chrome::IsNativeViewInAsh(window);
236}
237
238#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
239gfx::ImageSkia* ChromeViewsDelegate::GetDefaultWindowIcon() const {
240  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
241  return rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_64);
242}
243#endif
244
245#if defined(USE_ASH)
246views::NonClientFrameView* ChromeViewsDelegate::CreateDefaultNonClientFrameView(
247    views::Widget* widget) {
248  return chrome::IsNativeViewInAsh(widget->GetNativeView()) ?
249      ash::Shell::GetInstance()->CreateDefaultNonClientFrameView(widget) : NULL;
250}
251#endif
252
253void ChromeViewsDelegate::AddRef() {
254  g_browser_process->AddRefModule();
255}
256
257void ChromeViewsDelegate::ReleaseRef() {
258  g_browser_process->ReleaseModule();
259}
260
261void ChromeViewsDelegate::OnBeforeWidgetInit(
262    views::Widget::InitParams* params,
263    views::internal::NativeWidgetDelegate* delegate) {
264  // We need to determine opacity if it's not already specified.
265  if (params->opacity == views::Widget::InitParams::INFER_OPACITY)
266    params->opacity = GetOpacityForInitParams(*params);
267
268  // If we already have a native_widget, we don't have to try to come
269  // up with one.
270  if (params->native_widget)
271    return;
272
273#if defined(USE_AURA) && !defined(OS_CHROMEOS)
274  bool use_non_toplevel_window =
275      params->parent &&
276      params->type != views::Widget::InitParams::TYPE_MENU &&
277      params->type != views::Widget::InitParams::TYPE_TOOLTIP;
278
279#if defined(OS_WIN)
280  // On desktop Linux Chrome must run in an environment that supports a variety
281  // of window managers, some of which do not play nicely with parts of our UI
282  // that have specific expectations about window sizing and placement. For this
283  // reason windows opened as top level (!params.child) are always constrained
284  // by the browser frame, so we can position them correctly. This has some
285  // negative side effects, like dialogs being clipped by the browser frame, but
286  // the side effects are not as bad as the poor window manager interactions. On
287  // Windows however these WM interactions are not an issue, so we open windows
288  // requested as top_level as actual top level windows on the desktop.
289  use_non_toplevel_window = use_non_toplevel_window &&
290      (params->child ||
291       chrome::GetHostDesktopTypeForNativeView(params->parent) !=
292          chrome::HOST_DESKTOP_TYPE_NATIVE);
293
294  if (!ui::win::IsAeroGlassEnabled()) {
295    // If we don't have composition (either because Glass is not enabled or
296    // because it was disabled at the command line), anything that requires
297    // transparency will be broken with a toplevel window, so force the use of
298    // a non toplevel window.
299    if (params->opacity == views::Widget::InitParams::TRANSLUCENT_WINDOW &&
300        params->type != views::Widget::InitParams::TYPE_MENU)
301      use_non_toplevel_window = true;
302  } else {
303    // If we're on Vista+ with composition enabled, then we can use toplevel
304    // windows for most things (they get blended via WS_EX_COMPOSITED, which
305    // allows for animation effects, but also exceeding the bounds of the parent
306    // window).
307    if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_ASH &&
308        params->parent &&
309        params->type != views::Widget::InitParams::TYPE_CONTROL &&
310        params->type != views::Widget::InitParams::TYPE_WINDOW) {
311      // When we set this to false, we get a DesktopNativeWidgetAura from the
312      // default case (not handled in this function).
313      use_non_toplevel_window = false;
314    }
315  }
316#endif  // OS_WIN
317#endif  // USE_AURA
318
319#if defined(OS_CHROMEOS)
320  // When we are doing straight chromeos builds, we still need to handle the
321  // toplevel window case.
322  // There may be a few remaining widgets in Chrome OS that are not top level,
323  // but have neither a context nor a parent. Provide a fallback context so
324  // users don't crash. Developers will hit the DCHECK and should provide a
325  // context.
326  if (params->context)
327    params->context = params->context->GetRootWindow();
328  DCHECK(params->parent || params->context || !params->child)
329      << "Please provide a parent or context for this widget.";
330  if (!params->parent && !params->context)
331    params->context = ash::Shell::GetPrimaryRootWindow();
332#elif defined(USE_AURA)
333  // While the majority of the time, context wasn't plumbed through due to the
334  // existence of a global WindowTreeClient, if this window is toplevel, it's
335  // possible that there is no contextual state that we can use.
336  if (params->parent == NULL && params->context == NULL && !params->child) {
337    // We need to make a decision about where to place this window based on the
338    // desktop type.
339    switch (chrome::GetActiveDesktop()) {
340      case chrome::HOST_DESKTOP_TYPE_NATIVE:
341        // If we're native, we should give this window its own toplevel desktop
342        // widget.
343        params->native_widget = new views::DesktopNativeWidgetAura(delegate);
344        break;
345#if defined(USE_ASH)
346      case chrome::HOST_DESKTOP_TYPE_ASH:
347        // If we're in ash, give this window the context of the main monitor.
348        params->context = ash::Shell::GetPrimaryRootWindow();
349        break;
350#endif
351      default:
352        NOTREACHED();
353    }
354  } else if (use_non_toplevel_window) {
355    views::NativeWidgetAura* native_widget =
356        new views::NativeWidgetAura(delegate);
357    if (params->parent) {
358      Profile* parent_profile = reinterpret_cast<Profile*>(
359          params->parent->GetNativeWindowProperty(Profile::kProfileKey));
360      native_widget->SetNativeWindowProperty(Profile::kProfileKey,
361                                             parent_profile);
362    }
363    params->native_widget = native_widget;
364  } else {
365    // TODO(erg): Once we've threaded context to everywhere that needs it, we
366    // should remove this check here.
367    gfx::NativeView to_check =
368        params->context ? params->context : params->parent;
369    if (chrome::GetHostDesktopTypeForNativeView(to_check) ==
370        chrome::HOST_DESKTOP_TYPE_NATIVE) {
371      params->native_widget = new views::DesktopNativeWidgetAura(delegate);
372    }
373  }
374#endif
375}
376
377#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
378bool ChromeViewsDelegate::WindowManagerProvidesTitleBar(bool maximized) {
379  // On Ubuntu Unity, the system always provides a title bar for maximized
380  // windows.
381  views::LinuxUI* ui = views::LinuxUI::instance();
382  return maximized && ui && ui->UnityIsRunning();
383}
384#endif
385
386#if defined(USE_AURA)
387ui::ContextFactory* ChromeViewsDelegate::GetContextFactory() {
388  return content::GetContextFactory();
389}
390#endif
391
392#if defined(OS_WIN)
393int ChromeViewsDelegate::GetAppbarAutohideEdges(HMONITOR monitor,
394                                                const base::Closure& callback) {
395  // Initialize the map with EDGE_BOTTOM. This is important, as if we return an
396  // initial value of 0 (no auto-hide edges) then we'll go fullscreen and
397  // windows will automatically remove WS_EX_TOPMOST from the appbar resulting
398  // in us thinking there is no auto-hide edges. By returning at least one edge
399  // we don't initially go fullscreen until we figure out the real auto-hide
400  // edges.
401  if (!appbar_autohide_edge_map_.count(monitor))
402    appbar_autohide_edge_map_[monitor] = EDGE_BOTTOM;
403  if (monitor && !in_autohide_edges_callback_) {
404    base::PostTaskAndReplyWithResult(
405        content::BrowserThread::GetBlockingPool(),
406        FROM_HERE,
407        base::Bind(&GetAppbarAutohideEdgesOnWorkerThread,
408                   monitor),
409        base::Bind(&ChromeViewsDelegate::OnGotAppbarAutohideEdges,
410                   weak_factory_.GetWeakPtr(),
411                   callback,
412                   monitor,
413                   appbar_autohide_edge_map_[monitor]));
414  }
415  return appbar_autohide_edge_map_[monitor];
416}
417
418void ChromeViewsDelegate::OnGotAppbarAutohideEdges(
419    const base::Closure& callback,
420    HMONITOR monitor,
421    int returned_edges,
422    int edges) {
423  appbar_autohide_edge_map_[monitor] = edges;
424  if (returned_edges == edges)
425    return;
426
427  base::AutoReset<bool> in_callback_setter(&in_autohide_edges_callback_, true);
428  callback.Run();
429}
430#endif
431
432#if !defined(USE_AURA) && !defined(USE_CHROMEOS)
433views::Widget::InitParams::WindowOpacity
434ChromeViewsDelegate::GetOpacityForInitParams(
435    const views::Widget::InitParams& params) {
436  return views::Widget::InitParams::OPAQUE_WINDOW;
437}
438#endif
439