1// Copyright (c) 2014 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 "content/browser/renderer_host/legacy_render_widget_host_win.h" 6 7#include "base/command_line.h" 8#include "base/memory/scoped_ptr.h" 9#include "base/win/windows_version.h" 10#include "content/browser/accessibility/browser_accessibility_manager_win.h" 11#include "content/browser/accessibility/browser_accessibility_win.h" 12#include "content/public/browser/browser_accessibility_state.h" 13#include "content/public/common/content_switches.h" 14#include "ui/base/touch/touch_enabled.h" 15#include "ui/base/view_prop.h" 16#include "ui/base/win/internal_constants.h" 17#include "ui/base/win/window_event_target.h" 18#include "ui/gfx/geometry/rect.h" 19#include "ui/gfx/win/dpi.h" 20 21namespace content { 22 23// A custom MSAA object id used to determine if a screen reader or some 24// other client is listening on MSAA events - if so, we enable full web 25// accessibility support. 26const int kIdScreenReaderHoneyPot = 1; 27 28LegacyRenderWidgetHostHWND::~LegacyRenderWidgetHostHWND() { 29 ::DestroyWindow(hwnd()); 30} 31 32// static 33scoped_ptr<LegacyRenderWidgetHostHWND> LegacyRenderWidgetHostHWND::Create( 34 HWND parent) { 35 // content_unittests passes in the desktop window as the parent. We allow 36 // the LegacyRenderWidgetHostHWND instance to be created in this case for 37 // these tests to pass. 38 if (CommandLine::ForCurrentProcess()->HasSwitch( 39 switches::kDisableLegacyIntermediateWindow) || 40 (!GetWindowEventTarget(parent) && parent != ::GetDesktopWindow())) 41 return scoped_ptr<LegacyRenderWidgetHostHWND>(); 42 43 scoped_ptr<LegacyRenderWidgetHostHWND> legacy_window_instance; 44 legacy_window_instance.reset(new LegacyRenderWidgetHostHWND(parent)); 45 // If we failed to create the child, or if the switch to disable the legacy 46 // window is passed in, then return NULL. 47 if (!::IsWindow(legacy_window_instance->hwnd())) 48 return scoped_ptr<LegacyRenderWidgetHostHWND>(); 49 50 legacy_window_instance->Init(); 51 return legacy_window_instance.Pass(); 52} 53 54void LegacyRenderWidgetHostHWND::UpdateParent(HWND parent) { 55 ::SetParent(hwnd(), parent); 56 // If the new parent is the desktop Window, then we disable the child window 57 // to ensure that it does not receive any input events. It should not because 58 // of WS_EX_TRANSPARENT. This is only for safety. 59 if (parent == ::GetDesktopWindow()) { 60 ::EnableWindow(hwnd(), FALSE); 61 } else { 62 ::EnableWindow(hwnd(), TRUE); 63 } 64} 65 66HWND LegacyRenderWidgetHostHWND::GetParent() { 67 return ::GetParent(hwnd()); 68} 69 70void LegacyRenderWidgetHostHWND::OnManagerDeleted() { 71 manager_ = NULL; 72} 73 74void LegacyRenderWidgetHostHWND::Show() { 75 ::ShowWindow(hwnd(), SW_SHOW); 76} 77 78void LegacyRenderWidgetHostHWND::Hide() { 79 ::ShowWindow(hwnd(), SW_HIDE); 80} 81 82void LegacyRenderWidgetHostHWND::SetBounds(const gfx::Rect& bounds) { 83 gfx::Rect bounds_in_pixel = gfx::win::DIPToScreenRect(bounds); 84 ::SetWindowPos(hwnd(), NULL, bounds_in_pixel.x(), bounds_in_pixel.y(), 85 bounds_in_pixel.width(), bounds_in_pixel.height(), 0); 86} 87 88void LegacyRenderWidgetHostHWND::OnFinalMessage(HWND hwnd) { 89 if (manager_) 90 manager_->OnAccessibleHwndDeleted(); 91} 92 93LegacyRenderWidgetHostHWND::LegacyRenderWidgetHostHWND(HWND parent) 94 : manager_(NULL), 95 mouse_tracking_enabled_(false) { 96 RECT rect = {0}; 97 Base::Create(parent, rect, L"Chrome Legacy Window", 98 WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 99 WS_EX_TRANSPARENT); 100} 101 102bool LegacyRenderWidgetHostHWND::Init() { 103 if (base::win::GetVersion() >= base::win::VERSION_WIN7 && 104 ui::AreTouchEventsEnabled()) 105 RegisterTouchWindow(hwnd(), TWF_WANTPALM); 106 107 HRESULT hr = ::CreateStdAccessibleObject( 108 hwnd(), OBJID_WINDOW, IID_IAccessible, 109 reinterpret_cast<void **>(window_accessible_.Receive())); 110 DCHECK(SUCCEEDED(hr)); 111 112 if (!BrowserAccessibilityState::GetInstance()->IsAccessibleBrowser()) { 113 // Attempt to detect screen readers or other clients who want full 114 // accessibility support, by seeing if they respond to this event. 115 NotifyWinEvent(EVENT_SYSTEM_ALERT, hwnd(), kIdScreenReaderHoneyPot, 116 CHILDID_SELF); 117 } 118 119 return !!SUCCEEDED(hr); 120} 121 122// static 123ui::WindowEventTarget* LegacyRenderWidgetHostHWND::GetWindowEventTarget( 124 HWND parent) { 125 return reinterpret_cast<ui::WindowEventTarget*>(ui::ViewProp::GetValue( 126 parent, ui::WindowEventTarget::kWin32InputEventTarget)); 127} 128 129LRESULT LegacyRenderWidgetHostHWND::OnEraseBkGnd(UINT message, 130 WPARAM w_param, 131 LPARAM l_param) { 132 return 1; 133} 134 135LRESULT LegacyRenderWidgetHostHWND::OnGetObject(UINT message, 136 WPARAM w_param, 137 LPARAM l_param) { 138 // Only the lower 32 bits of l_param are valid when checking the object id 139 // because it sometimes gets sign-extended incorrectly (but not always). 140 DWORD obj_id = static_cast<DWORD>(static_cast<DWORD_PTR>(l_param)); 141 142 if (kIdScreenReaderHoneyPot == obj_id) { 143 // When an MSAA client has responded to our fake event on this id, 144 // enable screen reader support. 145 BrowserAccessibilityState::GetInstance()->OnScreenReaderDetected(); 146 return static_cast<LRESULT>(0L); 147 } 148 149 if (OBJID_CLIENT != obj_id || !manager_) 150 return static_cast<LRESULT>(0L); 151 152 base::win::ScopedComPtr<IAccessible> root( 153 manager_->GetRoot()->ToBrowserAccessibilityWin()); 154 return LresultFromObject(IID_IAccessible, w_param, 155 static_cast<IAccessible*>(root.Detach())); 156} 157 158// We send keyboard/mouse/touch messages to the parent window via SendMessage. 159// While this works, this has the side effect of converting input messages into 160// sent messages which changes their priority and could technically result 161// in these messages starving other messages in the queue. Additionally 162// keyboard/mouse hooks would not see these messages. The alternative approach 163// is to set and release capture as needed on the parent to ensure that it 164// receives all mouse events. However that was shelved due to possible issues 165// with capture changes. 166LRESULT LegacyRenderWidgetHostHWND::OnKeyboardRange(UINT message, 167 WPARAM w_param, 168 LPARAM l_param, 169 BOOL& handled) { 170 if (GetWindowEventTarget(GetParent())) { 171 return GetWindowEventTarget(GetParent())->HandleKeyboardMessage( 172 message, w_param, l_param); 173 } 174 return 0; 175} 176 177LRESULT LegacyRenderWidgetHostHWND::OnMouseRange(UINT message, 178 WPARAM w_param, 179 LPARAM l_param, 180 BOOL& handled) { 181 if (message == WM_MOUSEMOVE) { 182 if (!mouse_tracking_enabled_) { 183 mouse_tracking_enabled_ = true; 184 TRACKMOUSEEVENT tme; 185 tme.cbSize = sizeof(tme); 186 tme.dwFlags = TME_LEAVE; 187 tme.hwndTrack = hwnd(); 188 tme.dwHoverTime = 0; 189 TrackMouseEvent(&tme); 190 } 191 } 192 // The offsets for WM_NCXXX and WM_MOUSEWHEEL and WM_MOUSEHWHEEL messages are 193 // in screen coordinates. We should not be converting them to parent 194 // coordinates. 195 if ((message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) && 196 (message != WM_MOUSEWHEEL && message != WM_MOUSEHWHEEL)) { 197 POINT mouse_coords; 198 mouse_coords.x = GET_X_LPARAM(l_param); 199 mouse_coords.y = GET_Y_LPARAM(l_param); 200 ::MapWindowPoints(hwnd(), GetParent(), &mouse_coords, 1); 201 l_param = MAKELPARAM(mouse_coords.x, mouse_coords.y); 202 } 203 if (GetWindowEventTarget(GetParent())) { 204 return GetWindowEventTarget(GetParent())->HandleMouseMessage( 205 message, w_param, l_param); 206 } 207 return 0; 208} 209 210LRESULT LegacyRenderWidgetHostHWND::OnMouseLeave(UINT message, 211 WPARAM w_param, 212 LPARAM l_param) { 213 mouse_tracking_enabled_ = false; 214 if ((::GetCapture() != GetParent()) && GetWindowEventTarget(GetParent())) { 215 // We should send a WM_MOUSELEAVE to the parent window only if the mouse 216 // has moved outside the bounds of the parent. 217 POINT cursor_pos; 218 ::GetCursorPos(&cursor_pos); 219 if (::WindowFromPoint(cursor_pos) != GetParent()) { 220 return GetWindowEventTarget(GetParent())->HandleMouseMessage( 221 message, w_param, l_param); 222 } 223 } 224 return 0; 225} 226 227LRESULT LegacyRenderWidgetHostHWND::OnMouseActivate(UINT message, 228 WPARAM w_param, 229 LPARAM l_param) { 230 // Don't pass this to DefWindowProc. That results in the WM_MOUSEACTIVATE 231 // message going all the way to the parent which then messes up state 232 // related to focused views, etc. This is because it treats this as if 233 // it lost activation. 234 // Our dummy window should not interfere with focus and activation in 235 // the parent. Return MA_ACTIVATE here ensures that focus state in the parent 236 // is preserved. The only exception is if the parent was created with the 237 // WS_EX_NOACTIVATE style. 238 if (::GetWindowLong(GetParent(), GWL_EXSTYLE) & WS_EX_NOACTIVATE) 239 return MA_NOACTIVATE; 240 // On Windows, if we select the menu item by touch and if the window at the 241 // location is another window on the same thread, that window gets a 242 // WM_MOUSEACTIVATE message and ends up activating itself, which is not 243 // correct. We workaround this by setting a property on the window at the 244 // current cursor location. We check for this property in our 245 // WM_MOUSEACTIVATE handler and don't activate the window if the property is 246 // set. 247 if (::GetProp(hwnd(), ui::kIgnoreTouchMouseActivateForWindow)) { 248 ::RemoveProp(hwnd(), ui::kIgnoreTouchMouseActivateForWindow); 249 return MA_NOACTIVATE; 250 } 251 return MA_ACTIVATE; 252} 253 254LRESULT LegacyRenderWidgetHostHWND::OnTouch(UINT message, 255 WPARAM w_param, 256 LPARAM l_param) { 257 if (GetWindowEventTarget(GetParent())) { 258 return GetWindowEventTarget(GetParent())->HandleTouchMessage( 259 message, w_param, l_param); 260 } 261 return 0; 262} 263 264LRESULT LegacyRenderWidgetHostHWND::OnScroll(UINT message, 265 WPARAM w_param, 266 LPARAM l_param) { 267 if (GetWindowEventTarget(GetParent())) { 268 return GetWindowEventTarget(GetParent())->HandleScrollMessage( 269 message, w_param, l_param); 270 } 271 return 0; 272} 273 274LRESULT LegacyRenderWidgetHostHWND::OnNCHitTest(UINT message, 275 WPARAM w_param, 276 LPARAM l_param) { 277 if (GetWindowEventTarget(GetParent())) { 278 LRESULT hit_test = GetWindowEventTarget( 279 GetParent())->HandleNcHitTestMessage(message, w_param, l_param); 280 // If the parent returns HTNOWHERE which can happen for popup windows, etc 281 // we return HTCLIENT. 282 if (hit_test == HTNOWHERE) 283 hit_test = HTCLIENT; 284 return hit_test; 285 } 286 return HTNOWHERE; 287} 288 289LRESULT LegacyRenderWidgetHostHWND::OnNCPaint(UINT message, 290 WPARAM w_param, 291 LPARAM l_param) { 292 return 0; 293} 294 295LRESULT LegacyRenderWidgetHostHWND::OnPaint(UINT message, 296 WPARAM w_param, 297 LPARAM l_param) { 298 PAINTSTRUCT ps = {0}; 299 ::BeginPaint(hwnd(), &ps); 300 ::EndPaint(hwnd(), &ps); 301 return 0; 302} 303 304LRESULT LegacyRenderWidgetHostHWND::OnSetCursor(UINT message, 305 WPARAM w_param, 306 LPARAM l_param) { 307 return 0; 308} 309 310LRESULT LegacyRenderWidgetHostHWND::OnNCCalcSize(UINT message, 311 WPARAM w_param, 312 LPARAM l_param) { 313 // Prevent scrollbars, etc from drawing. 314 return 0; 315} 316 317LRESULT LegacyRenderWidgetHostHWND::OnSize(UINT message, 318 WPARAM w_param, 319 LPARAM l_param) { 320 // Certain trackpad drivers on Windows have bugs where in they don't generate 321 // WM_MOUSEWHEEL messages for the trackpoint and trackpad scrolling gestures 322 // unless there is an entry for Chrome with the class name of the Window. 323 // Additionally others check if the window WS_VSCROLL/WS_HSCROLL styles and 324 // generate the legacy WM_VSCROLL/WM_HSCROLL messages. 325 // We add these styles to ensure that trackpad/trackpoint scrolling 326 // work. 327 long current_style = ::GetWindowLong(hwnd(), GWL_STYLE); 328 ::SetWindowLong(hwnd(), GWL_STYLE, 329 current_style | WS_VSCROLL | WS_HSCROLL); 330 return 0; 331} 332 333} // namespace content 334