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