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 21 namespace 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. 26 const int kIdScreenReaderHoneyPot = 1; 27 28 LegacyRenderWidgetHostHWND::~LegacyRenderWidgetHostHWND() { 29 ::DestroyWindow(hwnd()); 30 } 31 32 // static 33 scoped_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 54 void 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 66 HWND LegacyRenderWidgetHostHWND::GetParent() { 67 return ::GetParent(hwnd()); 68 } 69 70 void LegacyRenderWidgetHostHWND::OnManagerDeleted() { 71 manager_ = NULL; 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(), 0); 86 } 87 88 void LegacyRenderWidgetHostHWND::OnFinalMessage(HWND hwnd) { 89 if (manager_) 90 manager_->OnAccessibleHwndDeleted(); 91 } 92 93 LegacyRenderWidgetHostHWND::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 102 bool 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 123 ui::WindowEventTarget* LegacyRenderWidgetHostHWND::GetWindowEventTarget( 124 HWND parent) { 125 return reinterpret_cast<ui::WindowEventTarget*>(ui::ViewProp::GetValue( 126 parent, ui::WindowEventTarget::kWin32InputEventTarget)); 127 } 128 129 LRESULT LegacyRenderWidgetHostHWND::OnEraseBkGnd(UINT message, 130 WPARAM w_param, 131 LPARAM l_param) { 132 return 1; 133 } 134 135 LRESULT 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. 166 LRESULT 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 177 LRESULT 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 210 LRESULT 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 227 LRESULT 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 254 LRESULT 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 264 LRESULT 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 274 LRESULT 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 289 LRESULT LegacyRenderWidgetHostHWND::OnNCPaint(UINT message, 290 WPARAM w_param, 291 LPARAM l_param) { 292 return 0; 293 } 294 295 LRESULT 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 304 LRESULT LegacyRenderWidgetHostHWND::OnSetCursor(UINT message, 305 WPARAM w_param, 306 LPARAM l_param) { 307 return 0; 308 } 309 310 LRESULT LegacyRenderWidgetHostHWND::OnNCCalcSize(UINT message, 311 WPARAM w_param, 312 LPARAM l_param) { 313 // Prevent scrollbars, etc from drawing. 314 return 0; 315 } 316 317 LRESULT 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