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