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
23namespace 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.
28const int kIdScreenReaderHoneyPot = 1;
29
30// static
31LegacyRenderWidgetHostHWND* 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
53void LegacyRenderWidgetHostHWND::Destroy() {
54  if (::IsWindow(hwnd()))
55    ::DestroyWindow(hwnd());
56}
57
58void 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
70HWND LegacyRenderWidgetHostHWND::GetParent() {
71  return ::GetParent(hwnd());
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(),
86                 SWP_NOREDRAW);
87}
88
89void LegacyRenderWidgetHostHWND::OnFinalMessage(HWND hwnd) {
90  if (host_) {
91    host_->OnLegacyWindowDestroyed();
92    host_ = NULL;
93  }
94  delete this;
95}
96
97LegacyRenderWidgetHostHWND::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
106LegacyRenderWidgetHostHWND::~LegacyRenderWidgetHostHWND() {
107  DCHECK(!::IsWindow(hwnd()));
108}
109
110bool 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
131ui::WindowEventTarget* LegacyRenderWidgetHostHWND::GetWindowEventTarget(
132    HWND parent) {
133  return reinterpret_cast<ui::WindowEventTarget*>(ui::ViewProp::GetValue(
134      parent, ui::WindowEventTarget::kWin32InputEventTarget));
135}
136
137LRESULT LegacyRenderWidgetHostHWND::OnEraseBkGnd(UINT message,
138                                                 WPARAM w_param,
139                                                 LPARAM l_param) {
140  return 1;
141}
142
143LRESULT 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.
185LRESULT 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
199LRESULT 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
246LRESULT 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
266LRESULT 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
293LRESULT 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
306LRESULT 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
319LRESULT 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
336LRESULT LegacyRenderWidgetHostHWND::OnNCPaint(UINT message,
337                                              WPARAM w_param,
338                                              LPARAM l_param) {
339  return 0;
340}
341
342LRESULT 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
351LRESULT LegacyRenderWidgetHostHWND::OnSetCursor(UINT message,
352                                                WPARAM w_param,
353                                                LPARAM l_param) {
354  return 0;
355}
356
357LRESULT LegacyRenderWidgetHostHWND::OnNCCalcSize(UINT message,
358                                                 WPARAM w_param,
359                                                 LPARAM l_param) {
360  // Prevent scrollbars, etc from drawing.
361  return 0;
362}
363
364LRESULT 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