Home | History | Annotate | Download | only in frame
      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