1// Copyright (c) 2012 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/web_contents/web_contents_view_win.h"
6
7#include "base/bind.h"
8#include "base/memory/scoped_vector.h"
9#include "content/browser/frame_host/interstitial_page_impl.h"
10#include "content/browser/renderer_host/render_view_host_factory.h"
11#include "content/browser/renderer_host/render_view_host_impl.h"
12#include "content/browser/renderer_host/render_widget_host_view_win.h"
13#include "content/browser/web_contents/web_contents_drag_win.h"
14#include "content/browser/web_contents/web_contents_impl.h"
15#include "content/browser/web_contents/web_drag_dest_win.h"
16#include "content/public/browser/web_contents_delegate.h"
17#include "content/public/browser/web_contents_view_delegate.h"
18#include "ui/base/win/hidden_window.h"
19#include "ui/base/win/hwnd_subclass.h"
20#include "ui/gfx/screen.h"
21
22namespace content {
23WebContentsViewPort* CreateWebContentsView(
24    WebContentsImpl* web_contents,
25    WebContentsViewDelegate* delegate,
26    RenderViewHostDelegateView** render_view_host_delegate_view) {
27  WebContentsViewWin* rv = new WebContentsViewWin(web_contents, delegate);
28  *render_view_host_delegate_view = rv;
29  return rv;
30}
31
32namespace {
33
34typedef std::map<HWND, WebContentsViewWin*> HwndToWcvMap;
35HwndToWcvMap hwnd_to_wcv_map;
36
37void RemoveHwndToWcvMapEntry(WebContentsViewWin* wcv) {
38  HwndToWcvMap::iterator it;
39  for (it = hwnd_to_wcv_map.begin(); it != hwnd_to_wcv_map.end();) {
40    if (it->second == wcv)
41      hwnd_to_wcv_map.erase(it++);
42    else
43      ++it;
44  }
45}
46
47BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam) {
48  HwndToWcvMap::iterator it = hwnd_to_wcv_map.find(hwnd);
49  if (it == hwnd_to_wcv_map.end())
50    return TRUE;  // must return TRUE to continue enumeration.
51  WebContentsViewWin* wcv = it->second;
52  RenderWidgetHostViewWin* rwhv = static_cast<RenderWidgetHostViewWin*>(
53      wcv->web_contents()->GetRenderWidgetHostView());
54  if (rwhv)
55    rwhv->UpdateScreenInfo(rwhv->GetNativeView());
56
57  return TRUE;  // must return TRUE to continue enumeration.
58}
59
60class PositionChangedMessageFilter : public ui::HWNDMessageFilter {
61 public:
62  PositionChangedMessageFilter() {}
63
64 private:
65  // Overridden from ui::HWNDMessageFilter:
66  virtual bool FilterMessage(HWND hwnd,
67                             UINT message,
68                             WPARAM w_param,
69                             LPARAM l_param,
70                             LRESULT* l_result) OVERRIDE {
71    if (message == WM_WINDOWPOSCHANGED || message == WM_SETTINGCHANGE)
72      EnumChildWindows(hwnd, EnumChildProc, 0);
73
74    return false;
75  }
76
77  DISALLOW_COPY_AND_ASSIGN(PositionChangedMessageFilter);
78};
79
80void AddFilterToParentHwndSubclass(HWND hwnd, ui::HWNDMessageFilter* filter) {
81  HWND parent = ::GetAncestor(hwnd, GA_ROOT);
82  if (parent) {
83    ui::HWNDSubclass::RemoveFilterFromAllTargets(filter);
84    ui::HWNDSubclass::AddFilterToTarget(parent, filter);
85  }
86}
87
88}  // namespace namespace
89
90WebContentsViewWin::WebContentsViewWin(WebContentsImpl* web_contents,
91                                       WebContentsViewDelegate* delegate)
92    : web_contents_(web_contents),
93      delegate_(delegate),
94      hwnd_message_filter_(new PositionChangedMessageFilter) {
95}
96
97WebContentsViewWin::~WebContentsViewWin() {
98  RemoveHwndToWcvMapEntry(this);
99
100  if (IsWindow(hwnd()))
101    DestroyWindow(hwnd());
102}
103
104gfx::NativeView WebContentsViewWin::GetNativeView() const {
105  return hwnd();
106}
107
108gfx::NativeView WebContentsViewWin::GetContentNativeView() const {
109  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
110  return rwhv ? rwhv->GetNativeView() : NULL;
111}
112
113gfx::NativeWindow WebContentsViewWin::GetTopLevelNativeWindow() const {
114  return ::GetAncestor(GetNativeView(), GA_ROOT);
115}
116
117void WebContentsViewWin::GetContainerBounds(gfx::Rect *out) const {
118  // Copied from NativeWidgetWin::GetClientAreaScreenBounds().
119  RECT r;
120  GetClientRect(hwnd(), &r);
121  POINT point = { r.left, r.top };
122  ClientToScreen(hwnd(), &point);
123  *out = gfx::Rect(point.x, point.y, r.right - r.left, r.bottom - r.top);
124}
125
126void WebContentsViewWin::OnTabCrashed(base::TerminationStatus status,
127                                      int error_code) {
128}
129
130void WebContentsViewWin::SizeContents(const gfx::Size& size) {
131  gfx::Rect bounds;
132  GetContainerBounds(&bounds);
133  if (bounds.size() != size) {
134    SetWindowPos(hwnd(), NULL, 0, 0, size.width(), size.height(),
135                 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
136  } else {
137    // Our size matches what we want but the renderers size may not match.
138    // Pretend we were resized so that the renderers size is updated too.
139    if (web_contents_->GetInterstitialPage())
140      web_contents_->GetInterstitialPage()->SetSize(size);
141    RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
142    if (rwhv)
143      rwhv->SetSize(size);
144  }
145}
146
147void WebContentsViewWin::CreateView(
148    const gfx::Size& initial_size, gfx::NativeView context) {
149  initial_size_ = initial_size;
150
151  set_window_style(WS_VISIBLE | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
152
153  Init(ui::GetHiddenWindow(), gfx::Rect(initial_size_));
154
155  // Remove the root view drop target so we can register our own.
156  RevokeDragDrop(GetNativeView());
157  drag_dest_ = new WebDragDest(hwnd(), web_contents_);
158  if (delegate_) {
159    WebDragDestDelegate* delegate = delegate_->GetDragDestDelegate();
160    if (delegate)
161      drag_dest_->set_delegate(delegate);
162  }
163}
164
165void WebContentsViewWin::Focus() {
166  if (web_contents_->GetInterstitialPage()) {
167    web_contents_->GetInterstitialPage()->Focus();
168    return;
169  }
170
171  if (delegate_.get() && delegate_->Focus())
172    return;
173
174  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
175  if (rwhv)
176    rwhv->Focus();
177}
178
179void WebContentsViewWin::SetInitialFocus() {
180  if (web_contents_->FocusLocationBarByDefault())
181    web_contents_->SetFocusToLocationBar(false);
182  else
183    Focus();
184}
185
186void WebContentsViewWin::StoreFocus() {
187  if (delegate_)
188    delegate_->StoreFocus();
189}
190
191void WebContentsViewWin::RestoreFocus() {
192  if (delegate_)
193    delegate_->RestoreFocus();
194}
195
196DropData* WebContentsViewWin::GetDropData() const {
197  return drag_dest_->current_drop_data();
198}
199
200gfx::Rect WebContentsViewWin::GetViewBounds() const {
201  RECT r;
202  GetWindowRect(hwnd(), &r);
203  return gfx::Rect(r);
204}
205
206RenderWidgetHostView* WebContentsViewWin::CreateViewForWidget(
207    RenderWidgetHost* render_widget_host)  {
208  if (render_widget_host->GetView()) {
209    // During testing, the view will already be set up in most cases to the
210    // test view, so we don't want to clobber it with a real one. To verify that
211    // this actually is happening (and somebody isn't accidentally creating the
212    // view twice), we check for the RVH Factory, which will be set when we're
213    // making special ones (which go along with the special views).
214    DCHECK(RenderViewHostFactory::has_factory());
215    return render_widget_host->GetView();
216  }
217
218  RenderWidgetHostViewWin* view = static_cast<RenderWidgetHostViewWin*>(
219      RenderWidgetHostView::CreateViewForWidget(render_widget_host));
220  view->CreateWnd(GetNativeView());
221  view->ShowWindow(SW_SHOW);
222  view->SetSize(initial_size_);
223  return view;
224}
225
226RenderWidgetHostView* WebContentsViewWin::CreateViewForPopupWidget(
227    RenderWidgetHost* render_widget_host) {
228  return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
229}
230
231void WebContentsViewWin::SetPageTitle(const base::string16& title) {
232  // It's possible to get this after the hwnd has been destroyed.
233  if (GetNativeView())
234    ::SetWindowText(GetNativeView(), title.c_str());
235}
236
237void WebContentsViewWin::RenderViewCreated(RenderViewHost* host) {
238}
239
240void WebContentsViewWin::RenderViewSwappedIn(RenderViewHost* host) {
241}
242
243void WebContentsViewWin::SetOverscrollControllerEnabled(bool enabled) {
244}
245
246void WebContentsViewWin::ShowContextMenu(const ContextMenuParams& params) {
247  if (delegate_)
248    delegate_->ShowContextMenu(params);
249  // WARNING: this may have been deleted.
250}
251
252void WebContentsViewWin::ShowPopupMenu(const gfx::Rect& bounds,
253                                       int item_height,
254                                       double item_font_size,
255                                       int selected_item,
256                                       const std::vector<MenuItem>& items,
257                                       bool right_aligned,
258                                       bool allow_multiple_selection) {
259  // External popup menus are only used on Mac and Android.
260  NOTIMPLEMENTED();
261}
262
263void WebContentsViewWin::StartDragging(const DropData& drop_data,
264                                       blink::WebDragOperationsMask operations,
265                                       const gfx::ImageSkia& image,
266                                       const gfx::Vector2d& image_offset,
267                                       const DragEventSourceInfo& event_info) {
268  drag_handler_ = new WebContentsDragWin(
269      GetNativeView(),
270      web_contents_,
271      drag_dest_,
272      base::Bind(&WebContentsViewWin::EndDragging, base::Unretained(this)));
273  drag_handler_->StartDragging(drop_data, operations, image, image_offset);
274}
275
276void WebContentsViewWin::UpdateDragCursor(blink::WebDragOperation operation) {
277  drag_dest_->set_drag_cursor(operation);
278}
279
280void WebContentsViewWin::GotFocus() {
281  if (web_contents_->GetDelegate())
282    web_contents_->GetDelegate()->WebContentsFocused(web_contents_);
283}
284
285void WebContentsViewWin::TakeFocus(bool reverse) {
286  if (web_contents_->GetDelegate() &&
287      !web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) &&
288      delegate_.get()) {
289    delegate_->TakeFocus(reverse);
290  }
291}
292
293void WebContentsViewWin::EndDragging() {
294  drag_handler_ = NULL;
295  web_contents_->SystemDragEnded();
296}
297
298void WebContentsViewWin::CloseTab() {
299  RenderViewHost* rvh = web_contents_->GetRenderViewHost();
300  rvh->GetDelegate()->Close(rvh);
301}
302
303LRESULT WebContentsViewWin::OnCreate(
304    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
305  hwnd_to_wcv_map.insert(std::make_pair(hwnd(), this));
306  AddFilterToParentHwndSubclass(hwnd(), hwnd_message_filter_.get());
307  return 0;
308}
309
310LRESULT WebContentsViewWin::OnDestroy(
311    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
312  if (drag_dest_) {
313    RevokeDragDrop(GetNativeView());
314    drag_dest_ = NULL;
315  }
316  if (drag_handler_) {
317    drag_handler_->CancelDrag();
318    drag_handler_ = NULL;
319  }
320  return 0;
321}
322
323LRESULT WebContentsViewWin::OnWindowPosChanged(
324    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
325
326  // Our parent might have changed. So we re-install our hwnd message filter.
327  AddFilterToParentHwndSubclass(hwnd(), hwnd_message_filter_.get());
328
329  WINDOWPOS* window_pos = reinterpret_cast<WINDOWPOS*>(lparam);
330  if (window_pos->flags & SWP_HIDEWINDOW) {
331    web_contents_->WasHidden();
332    return 0;
333  }
334
335  // The WebContents was shown by a means other than the user selecting a
336  // Tab, e.g. the window was minimized then restored.
337  if (window_pos->flags & SWP_SHOWWINDOW)
338    web_contents_->WasShown();
339
340  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
341  if (rwhv) {
342    RenderWidgetHostViewWin* view = static_cast<RenderWidgetHostViewWin*>(rwhv);
343    view->UpdateScreenInfo(view->GetNativeView());
344  }
345
346  // Unless we were specifically told not to size, cause the renderer to be
347  // sized to the new bounds, which forces a repaint. Not required for the
348  // simple minimize-restore case described above, for example, since the
349  // size hasn't changed.
350  if (window_pos->flags & SWP_NOSIZE)
351    return 0;
352
353  gfx::Size size(window_pos->cx, window_pos->cy);
354  if (web_contents_->GetInterstitialPage())
355    web_contents_->GetInterstitialPage()->SetSize(size);
356  if (rwhv)
357    rwhv->SetSize(size);
358
359  if (delegate_)
360    delegate_->SizeChanged(size);
361
362  return 0;
363}
364
365LRESULT WebContentsViewWin::OnMouseDown(
366    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
367  // Make sure this WebContents is activated when it is clicked on.
368  if (web_contents_->GetDelegate())
369    web_contents_->GetDelegate()->ActivateContents(web_contents_);
370  return 0;
371}
372
373LRESULT WebContentsViewWin::OnMouseMove(
374    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
375  // Let our delegate know that the mouse moved (useful for resetting status
376  // bubble state).
377  if (web_contents_->GetDelegate()) {
378    web_contents_->GetDelegate()->ContentsMouseEvent(
379        web_contents_,
380        gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(),
381        true);
382  }
383  return 0;
384}
385
386LRESULT WebContentsViewWin::OnNCCalcSize(
387    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
388  // Hack for ThinkPad mouse wheel driver. We have set the fake scroll bars
389  // to receive scroll messages from ThinkPad touch-pad driver. Suppress
390  // painting of scrollbars by returning 0 size for them.
391  return 0;
392}
393
394LRESULT WebContentsViewWin::OnNCHitTest(
395    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
396  return HTTRANSPARENT;
397}
398
399LRESULT WebContentsViewWin::OnScroll(
400    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
401  int scroll_type = LOWORD(wparam);
402  short position = HIWORD(wparam);
403  HWND scrollbar = reinterpret_cast<HWND>(lparam);
404  // This window can receive scroll events as a result of the ThinkPad's
405  // touch-pad scroll wheel emulation.
406  // If ctrl is held, zoom the UI.  There are three issues with this:
407  // 1) Should the event be eaten or forwarded to content?  We eat the event,
408  //    which is like Firefox and unlike IE.
409  // 2) Should wheel up zoom in or out?  We zoom in (increase font size), which
410  //    is like IE and Google maps, but unlike Firefox.
411  // 3) Should the mouse have to be over the content area?  We zoom as long as
412  //    content has focus, although FF and IE require that the mouse is over
413  //    content.  This is because all events get forwarded when content has
414  //    focus.
415  if (GetAsyncKeyState(VK_CONTROL) & 0x8000) {
416    int distance = 0;
417    switch (scroll_type) {
418      case SB_LINEUP:
419        distance = WHEEL_DELTA;
420        break;
421      case SB_LINEDOWN:
422        distance = -WHEEL_DELTA;
423        break;
424        // TODO(joshia): Handle SB_PAGEUP, SB_PAGEDOWN, SB_THUMBPOSITION,
425        // and SB_THUMBTRACK for completeness
426      default:
427        break;
428    }
429
430    web_contents_->GetDelegate()->ContentsZoomChange(distance > 0);
431    return 0;
432  }
433
434  // Reflect scroll message to the view() to give it a chance
435  // to process scrolling.
436  SendMessage(GetContentNativeView(), message, wparam, lparam);
437  return 0;
438}
439
440LRESULT WebContentsViewWin::OnSize(
441    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
442  // NOTE: Because we handle OnWindowPosChanged without calling DefWindowProc,
443  // OnSize is NOT called on window resize. This handler is called only once
444  // when the window is created.
445  // Don't call base class OnSize to avoid useless layout for 0x0 size.
446  // We will get OnWindowPosChanged later and layout root view in WasSized.
447
448  // Hack for ThinkPad touch-pad driver.
449  // Set fake scrollbars so that we can get scroll messages,
450  SCROLLINFO si = {0};
451  si.cbSize = sizeof(si);
452  si.fMask = SIF_ALL;
453
454  si.nMin = 1;
455  si.nMax = 100;
456  si.nPage = 10;
457  si.nPos = 50;
458
459  ::SetScrollInfo(hwnd(), SB_HORZ, &si, FALSE);
460  ::SetScrollInfo(hwnd(), SB_VERT, &si, FALSE);
461
462  return 1;
463}
464
465}  // namespace content
466