// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/views/frame/browser_frame_win.h" #include #include #include #include "base/command_line.h" #include "base/strings/utf_string_conversions.h" #include "base/win/metro.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/lifetime/application_lifetime.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/util.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/views/frame/browser_frame_common_win.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/frame/browser_window_property_manager_win.h" #include "chrome/browser/ui/views/frame/system_menu_insertion_delegate_win.h" #include "chrome/browser/ui/views/tabs/tab_strip.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/page_navigator.h" #include "content/public/browser/web_contents.h" #include "content/public/common/page_transition_types.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/layout.h" #include "ui/base/models/simple_menu_model.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/theme_provider.h" #include "ui/base/window_open_disposition.h" #include "ui/gfx/win/dpi.h" #include "ui/views/controls/menu/native_menu_win.h" #include "ui/views/views_delegate.h" #include "ui/views/widget/native_widget_win.h" #include "ui/views/widget/widget.h" #include "ui/views/win/hwnd_message_handler.h" #include "ui/views/window/non_client_view.h" #include "url/gurl.h" #include "win8/util/win8_util.h" #pragma comment(lib, "dwmapi.lib") // static static const int kClientEdgeThickness = 3; static const int kTabDragWindowAlpha = 200; // We need to offset the DWMFrame into the toolbar so that the blackness // doesn't show up on our rounded corners. static const int kDWMFrameTopOffset = 3; // If not -1, windows are shown with this state. static int explicit_show_state = -1; using content::OpenURLParams; using content::Referrer; using content::WebContents; #if !defined(USE_AURA) extern "C" { // Windows metro exported functions from metro_driver. typedef void (*SetFrameWindow)(HWND window); typedef void (*CloseFrameWindow)(HWND window); typedef void (*FlipFrameWindows)(); typedef void (*MetroSetFullscreen)(bool fullscreen); } #endif // USE_AURA views::Button* MakeWindowSwitcherButton(views::ButtonListener* listener, bool is_off_the_record) { views::ImageButton* switcher_button = new views::ImageButton(listener); // The button in the incognito window has the hot-cold images inverted // with respect to the regular browser window. switcher_button->SetImage( views::ImageButton::STATE_NORMAL, ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( is_off_the_record ? IDR_INCOGNITO_SWITCH_ON : IDR_INCOGNITO_SWITCH_OFF)); switcher_button->SetImage( views::ImageButton::STATE_HOVERED, ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( is_off_the_record ? IDR_INCOGNITO_SWITCH_OFF : IDR_INCOGNITO_SWITCH_ON)); switcher_button->SetImageAlignment(views::ImageButton::ALIGN_CENTER, views::ImageButton::ALIGN_MIDDLE); return switcher_button; } /////////////////////////////////////////////////////////////////////////////// // BrowserFrameWin, public: BrowserFrameWin::BrowserFrameWin(BrowserFrame* browser_frame, BrowserView* browser_view) : views::NativeWidgetWin(browser_frame), browser_view_(browser_view), browser_frame_(browser_frame) { if (win8::IsSingleWindowMetroMode()) { browser_view->SetWindowSwitcherButton( MakeWindowSwitcherButton(this, browser_view->IsOffTheRecord())); } } BrowserFrameWin::~BrowserFrameWin() { } // static void BrowserFrameWin::SetShowState(int state) { explicit_show_state = state; } void BrowserFrameWin::AdjustFrameForImmersiveMode() { #if defined(USE_AURA) return; #endif // USE_AURA HMODULE metro = base::win::GetMetroModule(); if (!metro) return; // We are in metro mode. browser_frame_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM); SetFrameWindow set_frame_window = reinterpret_cast( ::GetProcAddress(metro, "SetFrameWindow")); set_frame_window(browser_frame_->GetNativeWindow()); } void BrowserFrameWin::CloseImmersiveFrame() { #if defined(USE_AURA) return; #endif // USE_AURA HMODULE metro = base::win::GetMetroModule(); if (!metro) return; CloseFrameWindow close_frame_window = reinterpret_cast( ::GetProcAddress(metro, "CloseFrameWindow")); close_frame_window(browser_frame_->GetNativeWindow()); } views::NativeMenuWin* BrowserFrameWin::GetSystemMenu() { if (!system_menu_.get()) { SystemMenuInsertionDelegateWin insertion_delegate; system_menu_.reset( new views::NativeMenuWin(browser_frame_->GetSystemMenuModel(), GetNativeView())); system_menu_->Rebuild(&insertion_delegate); } return system_menu_.get(); } /////////////////////////////////////////////////////////////////////////////// // BrowserFrameWin, views::NativeWidgetWin overrides: int BrowserFrameWin::GetInitialShowState() const { if (explicit_show_state != -1) return explicit_show_state; STARTUPINFO si = {0}; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; GetStartupInfo(&si); return si.wShowWindow; } bool BrowserFrameWin::GetClientAreaInsets(gfx::Insets* insets) const { // Use the default client insets for an opaque frame or a glass popup/app // frame. if (!GetWidget()->ShouldUseNativeFrame() || !browser_view_->IsBrowserTypeNormal()) { return false; } int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME); // In fullscreen mode, we have no frame. In restored mode, we draw our own // client edge over part of the default frame. if (IsFullscreen()) border_thickness = 0; else if (!IsMaximized()) border_thickness -= kClientEdgeThickness; insets->Set(0, border_thickness, border_thickness, border_thickness); return true; } void BrowserFrameWin::HandleCreate() { NativeWidgetWin::HandleCreate(); browser_window_property_manager_ = BrowserWindowPropertyManager::CreateBrowserWindowPropertyManager( browser_view_); if (browser_window_property_manager_) { browser_window_property_manager_->UpdateWindowProperties( GetMessageHandler()->hwnd()); } } void BrowserFrameWin::HandleFrameChanged() { // Handle window frame layout changes, then set the updated glass region. NativeWidgetWin::HandleFrameChanged(); UpdateDWMFrame(); } bool BrowserFrameWin::PreHandleMSG(UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { static const UINT metro_navigation_search_message = RegisterWindowMessage(chrome::kMetroNavigationAndSearchMessage); static const UINT metro_get_current_tab_info_message = RegisterWindowMessage(chrome::kMetroGetCurrentTabInfoMessage); if (message == metro_navigation_search_message) { HandleMetroNavSearchRequest(w_param, l_param); return false; } else if (message == metro_get_current_tab_info_message) { GetMetroCurrentTabInfo(w_param); return false; } switch (message) { case WM_ACTIVATE: if (LOWORD(w_param) != WA_INACTIVE) minimize_button_metrics_.OnHWNDActivated(); return false; case WM_PRINT: if (win8::IsSingleWindowMetroMode()) { // This message is sent by the AnimateWindow API which is used in metro // mode to flip between active chrome windows. RECT client_rect = {0}; ::GetClientRect(GetNativeView(), &client_rect); HDC dest_dc = reinterpret_cast(w_param); DCHECK(dest_dc); HDC src_dc = ::GetDC(GetNativeView()); ::BitBlt(dest_dc, 0, 0, client_rect.right - client_rect.left, client_rect.bottom - client_rect.top, src_dc, 0, 0, SRCCOPY); ::ReleaseDC(GetNativeView(), src_dc); *result = 0; return true; } return false; case WM_ENDSESSION: chrome::SessionEnding(); return true; case WM_INITMENUPOPUP: GetSystemMenu()->UpdateStates(); return true; } return false; } void BrowserFrameWin::PostHandleMSG(UINT message, WPARAM w_param, LPARAM l_param) { switch (message) { case WM_CREATE: minimize_button_metrics_.Init(GetNativeView()); break; case WM_WINDOWPOSCHANGED: UpdateDWMFrame(); // Windows lies to us about the position of the minimize button before a // window is visible. We use this position to place the OTR avatar in RTL // mode, so when the window is shown, we need to re-layout and schedule a // paint for the non-client frame view so that the icon top has the correct // position when the window becomes visible. This fixes bugs where the icon // appears to overlay the minimize button. // Note that we will call Layout every time SetWindowPos is called with // SWP_SHOWWINDOW, however callers typically are careful about not // specifying this flag unless necessary to avoid flicker. // This may be invoked during creation on XP and before the non_client_view // has been created. WINDOWPOS* window_pos = reinterpret_cast(l_param); if (window_pos->flags & SWP_SHOWWINDOW && GetWidget()->non_client_view()) { GetWidget()->non_client_view()->Layout(); GetWidget()->non_client_view()->SchedulePaint(); } break; } } bool BrowserFrameWin::ShouldUseNativeFrame() const { if (!NativeWidgetWin::ShouldUseNativeFrame()) return false; return chrome::ShouldUseNativeFrame(browser_view_, GetWidget()->GetThemeProvider()); } void BrowserFrameWin::Show() { AdjustFrameForImmersiveMode(); views::NativeWidgetWin::Show(); } void BrowserFrameWin::ShowMaximizedWithBounds( const gfx::Rect& restored_bounds) { AdjustFrameForImmersiveMode(); views::NativeWidgetWin::ShowMaximizedWithBounds(restored_bounds); } void BrowserFrameWin::ShowWithWindowState(ui::WindowShowState show_state) { AdjustFrameForImmersiveMode(); views::NativeWidgetWin::ShowWithWindowState(show_state); } void BrowserFrameWin::Close() { CloseImmersiveFrame(); views::NativeWidgetWin::Close(); } void BrowserFrameWin::FrameTypeChanged() { // In Windows 8 metro mode the frame type is set to FRAME_TYPE_FORCE_CUSTOM // by default. We reset it back to FRAME_TYPE_DEFAULT to ensure that we // don't end up defaulting to BrowserNonClientFrameView in all cases. if (win8::IsSingleWindowMetroMode()) browser_frame_->set_frame_type(views::Widget::FRAME_TYPE_DEFAULT); views::NativeWidgetWin::FrameTypeChanged(); // In Windows 8 metro mode we call Show on the BrowserFrame instance to // ensure that the window can be styled appropriately, i.e. no sysmenu, // etc. if (win8::IsSingleWindowMetroMode()) Show(); } void BrowserFrameWin::SetFullscreen(bool fullscreen) { if (win8::IsSingleWindowMetroMode()) { HMODULE metro = base::win::GetMetroModule(); if (metro) { MetroSetFullscreen set_full_screen = reinterpret_cast( ::GetProcAddress(metro, "SetFullscreen")); DCHECK(set_full_screen); if (set_full_screen) set_full_screen(fullscreen); } else { NOTREACHED() << "Failed to get metro driver module"; } } views::NativeWidgetWin::SetFullscreen(fullscreen); } void BrowserFrameWin::Activate() { // In Windows 8 metro mode we have only one window visible at any given time. // The Activate code path is typically called when a new browser window is // being activated. In metro we need to ensure that the window currently // being displayed is hidden and the new window being activated becomes // visible. This is achieved by calling AdjustFrameForImmersiveMode() // followed by ShowWindow(). if (win8::IsSingleWindowMetroMode()) { AdjustFrameForImmersiveMode(); ::ShowWindow(browser_frame_->GetNativeWindow(), SW_SHOWNORMAL); } else { views::NativeWidgetWin::Activate(); } } //////////////////////////////////////////////////////////////////////////////// // BrowserFrameWin, NativeBrowserFrame implementation: views::NativeWidget* BrowserFrameWin::AsNativeWidget() { return this; } const views::NativeWidget* BrowserFrameWin::AsNativeWidget() const { return this; } bool BrowserFrameWin::UsesNativeSystemMenu() const { return true; } int BrowserFrameWin::GetMinimizeButtonOffset() const { return minimize_button_metrics_.GetMinimizeButtonOffsetX(); } void BrowserFrameWin::ButtonPressed(views::Button* sender, const ui::Event& event) { HMODULE metro = base::win::GetMetroModule(); if (!metro) return; // Toggle the profile and switch to the corresponding browser window in the // profile. The GetOffTheRecordProfile function is documented to create an // incognito profile if one does not exist. That is not a concern as the // windows 8 window switcher button shows up on the caption only when a // normal window and an incognito window are open simultaneously. Profile* profile_to_switch_to = NULL; Profile* current_profile = browser_view()->browser()->profile(); if (current_profile->IsOffTheRecord()) profile_to_switch_to = current_profile->GetOriginalProfile(); else profile_to_switch_to = current_profile->GetOffTheRecordProfile(); DCHECK(profile_to_switch_to); Browser* browser_to_switch_to = chrome::FindTabbedBrowser( profile_to_switch_to, false, chrome::HOST_DESKTOP_TYPE_NATIVE); DCHECK(browser_to_switch_to); BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser( browser_to_switch_to); // Tell the metro_driver to switch to the Browser we found above. This // causes the current browser window to be hidden. SetFrameWindow set_frame_window = reinterpret_cast( ::GetProcAddress(metro, "SetFrameWindow")); set_frame_window(browser_view->frame()->GetNativeWindow()); ::ShowWindow(browser_view->frame()->GetNativeWindow(), SW_SHOWNORMAL); } /////////////////////////////////////////////////////////////////////////////// // BrowserFrameWin, private: void BrowserFrameWin::UpdateDWMFrame() { // For "normal" windows on Aero, we always need to reset the glass area // correctly, even if we're not currently showing the native frame (e.g. // because a theme is showing), so we explicitly check for that case rather // than checking browser_frame_->ShouldUseNativeFrame() here. Using that here // would mean we wouldn't reset the glass area to zero when moving from the // native frame to an opaque frame, leading to graphical glitches behind the // opaque frame. Instead, we use that function below to tell us whether the // frame is currently native or opaque. if (!GetWidget()->client_view() || !browser_view_->IsBrowserTypeNormal() || !NativeWidgetWin::ShouldUseNativeFrame()) return; MARGINS margins = { 0 }; // If the opaque frame is visible, we use the default (zero) margins. // Otherwise, we need to figure out how to extend the glass in. if (browser_frame_->ShouldUseNativeFrame()) { // In fullscreen mode, we don't extend glass into the client area at all, // because the GDI-drawn text in the web content composited over it will // become semi-transparent over any glass area. if (!IsMaximized() && !IsFullscreen()) { margins.cxLeftWidth = kClientEdgeThickness + 1; margins.cxRightWidth = kClientEdgeThickness + 1; margins.cyBottomHeight = kClientEdgeThickness + 1; margins.cyTopHeight = kClientEdgeThickness + 1; } // In maximized mode, we only have a titlebar strip of glass, no side/bottom // borders. if (!IsFullscreen()) { gfx::Rect tabstrip_bounds( browser_frame_->GetBoundsForTabStrip(browser_view_->tabstrip())); tabstrip_bounds = gfx::win::DIPToScreenRect(tabstrip_bounds); margins.cyTopHeight = tabstrip_bounds.bottom() + kDWMFrameTopOffset; } } DwmExtendFrameIntoClientArea(GetNativeView(), &margins); } void BrowserFrameWin::HandleMetroNavSearchRequest(WPARAM w_param, LPARAM l_param) { if (!base::win::IsMetroProcess()) { NOTREACHED() << "Received unexpected metro navigation request"; return; } if (!w_param && !l_param) { NOTREACHED() << "Invalid metro request parameters"; return; } Browser* browser = browser_view()->browser(); DCHECK(browser); GURL request_url; if (w_param) { request_url = GURL(reinterpret_cast(w_param)); } else if (l_param) { request_url = GetDefaultSearchURLForSearchTerms( browser->profile(), reinterpret_cast(l_param)); } if (request_url.is_valid()) { browser->OpenURL(OpenURLParams(request_url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_TYPED, false)); } } void BrowserFrameWin::GetMetroCurrentTabInfo(WPARAM w_param) { if (!base::win::IsMetroProcess()) { NOTREACHED() << "Received unexpected metro request"; return; } if (!w_param) { NOTREACHED() << "Invalid metro request parameter"; return; } base::win::CurrentTabInfo* current_tab_info = reinterpret_cast(w_param); Browser* browser = browser_view()->browser(); DCHECK(browser); // We allocate memory for the title and url via LocalAlloc. The caller has to // free the memory via LocalFree. current_tab_info->title = base::win::LocalAllocAndCopyString( browser->GetWindowTitleForCurrentTab()); WebContents* current_tab = browser->tab_strip_model()->GetActiveWebContents(); DCHECK(current_tab); current_tab_info->url = base::win::LocalAllocAndCopyString( UTF8ToWide(current_tab->GetURL().spec())); } //////////////////////////////////////////////////////////////////////////////// // BrowserFrame, public: bool BrowserFrame::ShouldLeaveOffsetNearTopBorder() { if (win8::IsSingleWindowMetroMode()) { if (ui::GetDisplayLayout() == ui::LAYOUT_DESKTOP) return false; } return !IsMaximized(); }