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 63 namespace { 64 65 Profile* 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. 78 PrefService* 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) 88 bool 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 106 int 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) 128 ChromeViewsDelegate::ChromeViewsDelegate() 129 : in_autohide_edges_callback_(false), 130 weak_factory_(this) { 131 #else 132 ChromeViewsDelegate::ChromeViewsDelegate() { 133 #endif 134 } 135 136 ChromeViewsDelegate::~ChromeViewsDelegate() { 137 } 138 139 void 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 164 bool 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 208 void 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 219 void 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) 230 HICON ChromeViewsDelegate::GetDefaultWindowIcon() const { 231 return GetAppIcon(); 232 } 233 234 bool ChromeViewsDelegate::IsWindowInMetro(gfx::NativeWindow window) const { 235 return chrome::IsNativeViewInAsh(window); 236 } 237 238 #elif defined(OS_LINUX) && !defined(OS_CHROMEOS) 239 gfx::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) 246 views::NonClientFrameView* ChromeViewsDelegate::CreateDefaultNonClientFrameView( 247 views::Widget* widget) { 248 return chrome::IsNativeViewInAsh(widget->GetNativeView()) ? 249 ash::Shell::GetInstance()->CreateDefaultNonClientFrameView(widget) : NULL; 250 } 251 #endif 252 253 void ChromeViewsDelegate::AddRef() { 254 g_browser_process->AddRefModule(); 255 } 256 257 void ChromeViewsDelegate::ReleaseRef() { 258 g_browser_process->ReleaseModule(); 259 } 260 261 void 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) 378 bool 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) 387 ui::ContextFactory* ChromeViewsDelegate::GetContextFactory() { 388 return content::GetContextFactory(); 389 } 390 #endif 391 392 #if defined(OS_WIN) 393 int 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 418 void 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) 433 views::Widget::InitParams::WindowOpacity 434 ChromeViewsDelegate::GetOpacityForInitParams( 435 const views::Widget::InitParams& params) { 436 return views::Widget::InitParams::OPAQUE_WINDOW; 437 } 438 #endif 439