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