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/frame/browser_frame_win.h" 6 7 #include <dwmapi.h> 8 #include <shellapi.h> 9 #include <set> 10 11 #include "base/command_line.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "base/win/metro.h" 14 #include "chrome/app/chrome_command_ids.h" 15 #include "chrome/browser/lifetime/application_lifetime.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/search_engines/util.h" 18 #include "chrome/browser/ui/browser_commands.h" 19 #include "chrome/browser/ui/browser_finder.h" 20 #include "chrome/browser/ui/tabs/tab_strip_model.h" 21 #include "chrome/browser/ui/views/frame/browser_frame_common_win.h" 22 #include "chrome/browser/ui/views/frame/browser_view.h" 23 #include "chrome/browser/ui/views/frame/system_menu_insertion_delegate_win.h" 24 #include "chrome/browser/ui/views/tabs/tab_strip.h" 25 #include "chrome/common/chrome_constants.h" 26 #include "chrome/common/chrome_switches.h" 27 #include "content/public/browser/browser_accessibility_state.h" 28 #include "content/public/browser/page_navigator.h" 29 #include "content/public/browser/web_contents.h" 30 #include "content/public/common/page_transition_types.h" 31 #include "grit/generated_resources.h" 32 #include "grit/theme_resources.h" 33 #include "ui/base/l10n/l10n_util.h" 34 #include "ui/base/layout.h" 35 #include "ui/base/models/simple_menu_model.h" 36 #include "ui/base/resource/resource_bundle.h" 37 #include "ui/base/theme_provider.h" 38 #include "ui/base/win/dpi.h" 39 #include "ui/base/window_open_disposition.h" 40 #include "ui/gfx/font.h" 41 #include "ui/views/controls/menu/native_menu_win.h" 42 #include "ui/views/views_delegate.h" 43 #include "ui/views/widget/native_widget_win.h" 44 #include "ui/views/widget/widget.h" 45 #include "ui/views/window/non_client_view.h" 46 #include "url/gurl.h" 47 #include "win8/util/win8_util.h" 48 49 #pragma comment(lib, "dwmapi.lib") 50 51 // static 52 static const int kClientEdgeThickness = 3; 53 static const int kTabDragWindowAlpha = 200; 54 // We need to offset the DWMFrame into the toolbar so that the blackness 55 // doesn't show up on our rounded corners. 56 static const int kDWMFrameTopOffset = 3; 57 // If not -1, windows are shown with this state. 58 static int explicit_show_state = -1; 59 60 using content::OpenURLParams; 61 using content::Referrer; 62 using content::WebContents; 63 64 #if !defined(USE_AURA) 65 extern "C" { 66 // Windows metro exported functions from metro_driver. 67 typedef void (*SetFrameWindow)(HWND window); 68 typedef void (*CloseFrameWindow)(HWND window); 69 typedef void (*FlipFrameWindows)(); 70 typedef void (*MetroSetFullscreen)(bool fullscreen); 71 } 72 #endif // USE_AURA 73 74 views::Button* MakeWindowSwitcherButton(views::ButtonListener* listener, 75 bool is_off_the_record) { 76 views::ImageButton* switcher_button = new views::ImageButton(listener); 77 // The button in the incognito window has the hot-cold images inverted 78 // with respect to the regular browser window. 79 switcher_button->SetImage( 80 views::ImageButton::STATE_NORMAL, 81 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 82 is_off_the_record ? IDR_INCOGNITO_SWITCH_ON : 83 IDR_INCOGNITO_SWITCH_OFF)); 84 switcher_button->SetImage( 85 views::ImageButton::STATE_HOVERED, 86 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 87 is_off_the_record ? IDR_INCOGNITO_SWITCH_OFF : 88 IDR_INCOGNITO_SWITCH_ON)); 89 switcher_button->SetImageAlignment(views::ImageButton::ALIGN_CENTER, 90 views::ImageButton::ALIGN_MIDDLE); 91 return switcher_button; 92 } 93 94 /////////////////////////////////////////////////////////////////////////////// 95 // BrowserFrameWin, public: 96 97 BrowserFrameWin::BrowserFrameWin(BrowserFrame* browser_frame, 98 BrowserView* browser_view) 99 : views::NativeWidgetWin(browser_frame), 100 browser_view_(browser_view), 101 browser_frame_(browser_frame) { 102 if (win8::IsSingleWindowMetroMode()) { 103 browser_view->SetWindowSwitcherButton( 104 MakeWindowSwitcherButton(this, browser_view->IsOffTheRecord())); 105 } 106 } 107 108 BrowserFrameWin::~BrowserFrameWin() { 109 } 110 111 // static 112 void BrowserFrameWin::SetShowState(int state) { 113 explicit_show_state = state; 114 } 115 116 void BrowserFrameWin::AdjustFrameForImmersiveMode() { 117 #if defined(USE_AURA) 118 return; 119 #endif // USE_AURA 120 HMODULE metro = base::win::GetMetroModule(); 121 if (!metro) 122 return; 123 // We are in metro mode. 124 browser_frame_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM); 125 SetFrameWindow set_frame_window = reinterpret_cast<SetFrameWindow>( 126 ::GetProcAddress(metro, "SetFrameWindow")); 127 set_frame_window(browser_frame_->GetNativeWindow()); 128 } 129 130 void BrowserFrameWin::CloseImmersiveFrame() { 131 #if defined(USE_AURA) 132 return; 133 #endif // USE_AURA 134 HMODULE metro = base::win::GetMetroModule(); 135 if (!metro) 136 return; 137 CloseFrameWindow close_frame_window = reinterpret_cast<CloseFrameWindow>( 138 ::GetProcAddress(metro, "CloseFrameWindow")); 139 close_frame_window(browser_frame_->GetNativeWindow()); 140 } 141 142 143 views::NativeMenuWin* BrowserFrameWin::GetSystemMenu() { 144 if (!system_menu_.get()) { 145 SystemMenuInsertionDelegateWin insertion_delegate; 146 system_menu_.reset( 147 new views::NativeMenuWin(browser_frame_->GetSystemMenuModel(), 148 GetNativeView())); 149 system_menu_->Rebuild(&insertion_delegate); 150 } 151 return system_menu_.get(); 152 } 153 154 /////////////////////////////////////////////////////////////////////////////// 155 // BrowserFrameWin, views::NativeWidgetWin overrides: 156 157 int BrowserFrameWin::GetInitialShowState() const { 158 if (explicit_show_state != -1) 159 return explicit_show_state; 160 161 STARTUPINFO si = {0}; 162 si.cb = sizeof(si); 163 si.dwFlags = STARTF_USESHOWWINDOW; 164 GetStartupInfo(&si); 165 return si.wShowWindow; 166 } 167 168 bool BrowserFrameWin::GetClientAreaInsets(gfx::Insets* insets) const { 169 // Use the default client insets for an opaque frame or a glass popup/app 170 // frame. 171 if (!GetWidget()->ShouldUseNativeFrame() || 172 !browser_view_->IsBrowserTypeNormal()) { 173 return false; 174 } 175 176 int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME); 177 // In fullscreen mode, we have no frame. In restored mode, we draw our own 178 // client edge over part of the default frame. 179 if (IsFullscreen()) 180 border_thickness = 0; 181 else if (!IsMaximized()) 182 border_thickness -= kClientEdgeThickness; 183 insets->Set(0, border_thickness, border_thickness, border_thickness); 184 return true; 185 } 186 187 void BrowserFrameWin::HandleFrameChanged() { 188 // Handle window frame layout changes, then set the updated glass region. 189 NativeWidgetWin::HandleFrameChanged(); 190 UpdateDWMFrame(); 191 } 192 193 bool BrowserFrameWin::PreHandleMSG(UINT message, 194 WPARAM w_param, 195 LPARAM l_param, 196 LRESULT* result) { 197 static const UINT metro_navigation_search_message = 198 RegisterWindowMessage(chrome::kMetroNavigationAndSearchMessage); 199 200 static const UINT metro_get_current_tab_info_message = 201 RegisterWindowMessage(chrome::kMetroGetCurrentTabInfoMessage); 202 203 if (message == metro_navigation_search_message) { 204 HandleMetroNavSearchRequest(w_param, l_param); 205 return false; 206 } else if (message == metro_get_current_tab_info_message) { 207 GetMetroCurrentTabInfo(w_param); 208 return false; 209 } 210 211 switch (message) { 212 case WM_ACTIVATE: 213 if (LOWORD(w_param) != WA_INACTIVE) 214 minimize_button_metrics_.OnHWNDActivated(); 215 return false; 216 case WM_PRINT: 217 if (win8::IsSingleWindowMetroMode()) { 218 // This message is sent by the AnimateWindow API which is used in metro 219 // mode to flip between active chrome windows. 220 RECT client_rect = {0}; 221 ::GetClientRect(GetNativeView(), &client_rect); 222 HDC dest_dc = reinterpret_cast<HDC>(w_param); 223 DCHECK(dest_dc); 224 HDC src_dc = ::GetDC(GetNativeView()); 225 ::BitBlt(dest_dc, 0, 0, client_rect.right - client_rect.left, 226 client_rect.bottom - client_rect.top, src_dc, 0, 0, 227 SRCCOPY); 228 ::ReleaseDC(GetNativeView(), src_dc); 229 *result = 0; 230 return true; 231 } 232 return false; 233 case WM_ENDSESSION: 234 chrome::SessionEnding(); 235 return true; 236 case WM_INITMENUPOPUP: 237 GetSystemMenu()->UpdateStates(); 238 return true; 239 } 240 return false; 241 } 242 243 void BrowserFrameWin::PostHandleMSG(UINT message, 244 WPARAM w_param, 245 LPARAM l_param) { 246 switch (message) { 247 case WM_CREATE: 248 minimize_button_metrics_.Init(GetNativeView()); 249 break; 250 case WM_WINDOWPOSCHANGED: 251 UpdateDWMFrame(); 252 253 // Windows lies to us about the position of the minimize button before a 254 // window is visible. We use this position to place the OTR avatar in RTL 255 // mode, so when the window is shown, we need to re-layout and schedule a 256 // paint for the non-client frame view so that the icon top has the correct 257 // position when the window becomes visible. This fixes bugs where the icon 258 // appears to overlay the minimize button. 259 // Note that we will call Layout every time SetWindowPos is called with 260 // SWP_SHOWWINDOW, however callers typically are careful about not 261 // specifying this flag unless necessary to avoid flicker. 262 // This may be invoked during creation on XP and before the non_client_view 263 // has been created. 264 WINDOWPOS* window_pos = reinterpret_cast<WINDOWPOS*>(l_param); 265 if (window_pos->flags & SWP_SHOWWINDOW && GetWidget()->non_client_view()) { 266 GetWidget()->non_client_view()->Layout(); 267 GetWidget()->non_client_view()->SchedulePaint(); 268 } 269 break; 270 } 271 } 272 273 bool BrowserFrameWin::ShouldUseNativeFrame() const { 274 if (!NativeWidgetWin::ShouldUseNativeFrame()) 275 return false; 276 return chrome::ShouldUseNativeFrame(browser_view_, 277 GetWidget()->GetThemeProvider()); 278 } 279 280 void BrowserFrameWin::Show() { 281 AdjustFrameForImmersiveMode(); 282 views::NativeWidgetWin::Show(); 283 } 284 285 void BrowserFrameWin::ShowMaximizedWithBounds( 286 const gfx::Rect& restored_bounds) { 287 AdjustFrameForImmersiveMode(); 288 views::NativeWidgetWin::ShowMaximizedWithBounds(restored_bounds); 289 } 290 291 void BrowserFrameWin::ShowWithWindowState(ui::WindowShowState show_state) { 292 AdjustFrameForImmersiveMode(); 293 views::NativeWidgetWin::ShowWithWindowState(show_state); 294 } 295 296 void BrowserFrameWin::Close() { 297 CloseImmersiveFrame(); 298 views::NativeWidgetWin::Close(); 299 } 300 301 void BrowserFrameWin::FrameTypeChanged() { 302 // In Windows 8 metro mode the frame type is set to FRAME_TYPE_FORCE_CUSTOM 303 // by default. We reset it back to FRAME_TYPE_DEFAULT to ensure that we 304 // don't end up defaulting to BrowserNonClientFrameView in all cases. 305 if (win8::IsSingleWindowMetroMode()) 306 browser_frame_->set_frame_type(views::Widget::FRAME_TYPE_DEFAULT); 307 308 views::NativeWidgetWin::FrameTypeChanged(); 309 310 // In Windows 8 metro mode we call Show on the BrowserFrame instance to 311 // ensure that the window can be styled appropriately, i.e. no sysmenu, 312 // etc. 313 if (win8::IsSingleWindowMetroMode()) 314 Show(); 315 } 316 317 void BrowserFrameWin::SetFullscreen(bool fullscreen) { 318 if (win8::IsSingleWindowMetroMode()) { 319 HMODULE metro = base::win::GetMetroModule(); 320 if (metro) { 321 MetroSetFullscreen set_full_screen = reinterpret_cast<MetroSetFullscreen>( 322 ::GetProcAddress(metro, "SetFullscreen")); 323 DCHECK(set_full_screen); 324 if (set_full_screen) 325 set_full_screen(fullscreen); 326 } else { 327 NOTREACHED() << "Failed to get metro driver module"; 328 } 329 } 330 views::NativeWidgetWin::SetFullscreen(fullscreen); 331 } 332 333 void BrowserFrameWin::Activate() { 334 // In Windows 8 metro mode we have only one window visible at any given time. 335 // The Activate code path is typically called when a new browser window is 336 // being activated. In metro we need to ensure that the window currently 337 // being displayed is hidden and the new window being activated becomes 338 // visible. This is achieved by calling AdjustFrameForImmersiveMode() 339 // followed by ShowWindow(). 340 if (win8::IsSingleWindowMetroMode()) { 341 AdjustFrameForImmersiveMode(); 342 ::ShowWindow(browser_frame_->GetNativeWindow(), SW_SHOWNORMAL); 343 } else { 344 views::NativeWidgetWin::Activate(); 345 } 346 } 347 348 349 //////////////////////////////////////////////////////////////////////////////// 350 // BrowserFrameWin, NativeBrowserFrame implementation: 351 352 views::NativeWidget* BrowserFrameWin::AsNativeWidget() { 353 return this; 354 } 355 356 const views::NativeWidget* BrowserFrameWin::AsNativeWidget() const { 357 return this; 358 } 359 360 bool BrowserFrameWin::UsesNativeSystemMenu() const { 361 return true; 362 } 363 364 int BrowserFrameWin::GetMinimizeButtonOffset() const { 365 return minimize_button_metrics_.GetMinimizeButtonOffsetX(); 366 } 367 368 void BrowserFrameWin::TabStripDisplayModeChanged() { 369 UpdateDWMFrame(); 370 } 371 372 void BrowserFrameWin::ButtonPressed(views::Button* sender, 373 const ui::Event& event) { 374 HMODULE metro = base::win::GetMetroModule(); 375 if (!metro) 376 return; 377 378 // Toggle the profile and switch to the corresponding browser window in the 379 // profile. The GetOffTheRecordProfile function is documented to create an 380 // incognito profile if one does not exist. That is not a concern as the 381 // windows 8 window switcher button shows up on the caption only when a 382 // normal window and an incognito window are open simultaneously. 383 Profile* profile_to_switch_to = NULL; 384 Profile* current_profile = browser_view()->browser()->profile(); 385 if (current_profile->IsOffTheRecord()) 386 profile_to_switch_to = current_profile->GetOriginalProfile(); 387 else 388 profile_to_switch_to = current_profile->GetOffTheRecordProfile(); 389 390 DCHECK(profile_to_switch_to); 391 392 Browser* browser_to_switch_to = chrome::FindTabbedBrowser( 393 profile_to_switch_to, false, chrome::HOST_DESKTOP_TYPE_NATIVE); 394 395 DCHECK(browser_to_switch_to); 396 397 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser( 398 browser_to_switch_to); 399 400 // Tell the metro_driver to switch to the Browser we found above. This 401 // causes the current browser window to be hidden. 402 SetFrameWindow set_frame_window = reinterpret_cast<SetFrameWindow>( 403 ::GetProcAddress(metro, "SetFrameWindow")); 404 set_frame_window(browser_view->frame()->GetNativeWindow()); 405 ::ShowWindow(browser_view->frame()->GetNativeWindow(), SW_SHOWNORMAL); 406 } 407 408 /////////////////////////////////////////////////////////////////////////////// 409 // BrowserFrameWin, private: 410 411 void BrowserFrameWin::UpdateDWMFrame() { 412 // For "normal" windows on Aero, we always need to reset the glass area 413 // correctly, even if we're not currently showing the native frame (e.g. 414 // because a theme is showing), so we explicitly check for that case rather 415 // than checking browser_frame_->ShouldUseNativeFrame() here. Using that here 416 // would mean we wouldn't reset the glass area to zero when moving from the 417 // native frame to an opaque frame, leading to graphical glitches behind the 418 // opaque frame. Instead, we use that function below to tell us whether the 419 // frame is currently native or opaque. 420 if (!GetWidget()->client_view() || !browser_view_->IsBrowserTypeNormal() || 421 !NativeWidgetWin::ShouldUseNativeFrame()) 422 return; 423 424 MARGINS margins = { 0 }; 425 426 // If the opaque frame is visible, we use the default (zero) margins. 427 // Otherwise, we need to figure out how to extend the glass in. 428 if (browser_frame_->ShouldUseNativeFrame()) { 429 // In fullscreen mode, we don't extend glass into the client area at all, 430 // because the GDI-drawn text in the web content composited over it will 431 // become semi-transparent over any glass area. 432 if (!IsMaximized() && !IsFullscreen()) { 433 margins.cxLeftWidth = kClientEdgeThickness + 1; 434 margins.cxRightWidth = kClientEdgeThickness + 1; 435 margins.cyBottomHeight = kClientEdgeThickness + 1; 436 margins.cyTopHeight = kClientEdgeThickness + 1; 437 } 438 // In maximized mode, we only have a titlebar strip of glass, no side/bottom 439 // borders. 440 if (!IsFullscreen()) { 441 gfx::Rect tabstrip_bounds( 442 browser_frame_->GetBoundsForTabStrip(browser_view_->tabstrip())); 443 tabstrip_bounds = ui::win::DIPToScreenRect(tabstrip_bounds); 444 margins.cyTopHeight = tabstrip_bounds.bottom() + kDWMFrameTopOffset; 445 } 446 } 447 448 DwmExtendFrameIntoClientArea(GetNativeView(), &margins); 449 } 450 451 void BrowserFrameWin::HandleMetroNavSearchRequest(WPARAM w_param, 452 LPARAM l_param) { 453 if (!base::win::IsMetroProcess()) { 454 NOTREACHED() << "Received unexpected metro navigation request"; 455 return; 456 } 457 458 if (!w_param && !l_param) { 459 NOTREACHED() << "Invalid metro request parameters"; 460 return; 461 } 462 463 Browser* browser = browser_view()->browser(); 464 DCHECK(browser); 465 466 GURL request_url; 467 if (w_param) { 468 request_url = GURL(reinterpret_cast<const wchar_t*>(w_param)); 469 } else if (l_param) { 470 request_url = GetDefaultSearchURLForSearchTerms( 471 browser->profile(), reinterpret_cast<const wchar_t*>(l_param)); 472 } 473 if (request_url.is_valid()) { 474 browser->OpenURL(OpenURLParams(request_url, Referrer(), NEW_FOREGROUND_TAB, 475 content::PAGE_TRANSITION_TYPED, false)); 476 } 477 } 478 479 void BrowserFrameWin::GetMetroCurrentTabInfo(WPARAM w_param) { 480 if (!base::win::IsMetroProcess()) { 481 NOTREACHED() << "Received unexpected metro request"; 482 return; 483 } 484 485 if (!w_param) { 486 NOTREACHED() << "Invalid metro request parameter"; 487 return; 488 } 489 490 base::win::CurrentTabInfo* current_tab_info = 491 reinterpret_cast<base::win::CurrentTabInfo*>(w_param); 492 493 Browser* browser = browser_view()->browser(); 494 DCHECK(browser); 495 496 // We allocate memory for the title and url via LocalAlloc. The caller has to 497 // free the memory via LocalFree. 498 current_tab_info->title = base::win::LocalAllocAndCopyString( 499 browser->GetWindowTitleForCurrentTab()); 500 501 WebContents* current_tab = browser->tab_strip_model()->GetActiveWebContents(); 502 DCHECK(current_tab); 503 504 current_tab_info->url = base::win::LocalAllocAndCopyString( 505 UTF8ToWide(current_tab->GetURL().spec())); 506 } 507 508 //////////////////////////////////////////////////////////////////////////////// 509 // BrowserFrame, public: 510 511 // static 512 const gfx::Font& BrowserFrame::GetTitleFont() { 513 static gfx::Font* title_font = 514 new gfx::Font(views::NativeWidgetWin::GetWindowTitleFont()); 515 return *title_font; 516 } 517 518 bool BrowserFrame::ShouldLeaveOffsetNearTopBorder() { 519 if (win8::IsSingleWindowMetroMode()) { 520 if (ui::GetDisplayLayout() == ui::LAYOUT_DESKTOP) 521 return false; 522 } 523 return !IsMaximized(); 524 } 525 526 //////////////////////////////////////////////////////////////////////////////// 527 // NativeBrowserFrame, public: 528 529 // static 530 NativeBrowserFrame* NativeBrowserFrame::CreateNativeBrowserFrame( 531 BrowserFrame* browser_frame, 532 BrowserView* browser_view) { 533 return new BrowserFrameWin(browser_frame, browser_view); 534 } 535