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/renderer_host/render_view_host_factory.h"
10#include "content/browser/renderer_host/render_view_host_impl.h"
11#include "content/browser/renderer_host/render_widget_host_view_win.h"
12#include "content/browser/web_contents/interstitial_page_impl.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 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}
250
251void WebContentsViewWin::ShowPopupMenu(const gfx::Rect& bounds,
252                                       int item_height,
253                                       double item_font_size,
254                                       int selected_item,
255                                       const std::vector<MenuItem>& items,
256                                       bool right_aligned,
257                                       bool allow_multiple_selection) {
258  // External popup menus are only used on Mac and Android.
259  NOTIMPLEMENTED();
260}
261
262void WebContentsViewWin::StartDragging(const DropData& drop_data,
263                                       WebKit::WebDragOperationsMask operations,
264                                       const gfx::ImageSkia& image,
265                                       const gfx::Vector2d& image_offset,
266                                       const DragEventSourceInfo& event_info) {
267  drag_handler_ = new WebContentsDragWin(
268      GetNativeView(),
269      web_contents_,
270      drag_dest_,
271      base::Bind(&WebContentsViewWin::EndDragging, base::Unretained(this)));
272  drag_handler_->StartDragging(drop_data, operations, image, image_offset);
273}
274
275void WebContentsViewWin::UpdateDragCursor(WebKit::WebDragOperation operation) {
276  drag_dest_->set_drag_cursor(operation);
277}
278
279void WebContentsViewWin::GotFocus() {
280  if (web_contents_->GetDelegate())
281    web_contents_->GetDelegate()->WebContentsFocused(web_contents_);
282}
283
284void WebContentsViewWin::TakeFocus(bool reverse) {
285  if (web_contents_->GetDelegate() &&
286      !web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) &&
287      delegate_.get()) {
288    delegate_->TakeFocus(reverse);
289  }
290}
291
292void WebContentsViewWin::EndDragging() {
293  drag_handler_ = NULL;
294  web_contents_->SystemDragEnded();
295}
296
297void WebContentsViewWin::CloseTab() {
298  RenderViewHost* rvh = web_contents_->GetRenderViewHost();
299  rvh->GetDelegate()->Close(rvh);
300}
301
302LRESULT WebContentsViewWin::OnCreate(
303    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
304  hwnd_to_wcv_map.insert(std::make_pair(hwnd(), this));
305  AddFilterToParentHwndSubclass(hwnd(), hwnd_message_filter_.get());
306  return 0;
307}
308
309LRESULT WebContentsViewWin::OnDestroy(
310    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
311  if (drag_dest_) {
312    RevokeDragDrop(GetNativeView());
313    drag_dest_ = NULL;
314  }
315  if (drag_handler_) {
316    drag_handler_->CancelDrag();
317    drag_handler_ = NULL;
318  }
319  return 0;
320}
321
322LRESULT WebContentsViewWin::OnWindowPosChanged(
323    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
324
325  // Our parent might have changed. So we re-install our hwnd message filter.
326  AddFilterToParentHwndSubclass(hwnd(), hwnd_message_filter_.get());
327
328  WINDOWPOS* window_pos = reinterpret_cast<WINDOWPOS*>(lparam);
329  if (window_pos->flags & SWP_HIDEWINDOW) {
330    web_contents_->WasHidden();
331    return 0;
332  }
333
334  // The WebContents was shown by a means other than the user selecting a
335  // Tab, e.g. the window was minimized then restored.
336  if (window_pos->flags & SWP_SHOWWINDOW)
337    web_contents_->WasShown();
338
339  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
340  if (rwhv) {
341    RenderWidgetHostViewWin* view = static_cast<RenderWidgetHostViewWin*>(rwhv);
342    view->UpdateScreenInfo(view->GetNativeView());
343  }
344
345  // Unless we were specifically told not to size, cause the renderer to be
346  // sized to the new bounds, which forces a repaint. Not required for the
347  // simple minimize-restore case described above, for example, since the
348  // size hasn't changed.
349  if (window_pos->flags & SWP_NOSIZE)
350    return 0;
351
352  gfx::Size size(window_pos->cx, window_pos->cy);
353  if (web_contents_->GetInterstitialPage())
354    web_contents_->GetInterstitialPage()->SetSize(size);
355  if (rwhv)
356    rwhv->SetSize(size);
357
358  if (delegate_)
359    delegate_->SizeChanged(size);
360
361  return 0;
362}
363
364LRESULT WebContentsViewWin::OnMouseDown(
365    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
366  // Make sure this WebContents is activated when it is clicked on.
367  if (web_contents_->GetDelegate())
368    web_contents_->GetDelegate()->ActivateContents(web_contents_);
369  return 0;
370}
371
372LRESULT WebContentsViewWin::OnMouseMove(
373    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
374  // Let our delegate know that the mouse moved (useful for resetting status
375  // bubble state).
376  if (web_contents_->GetDelegate()) {
377    web_contents_->GetDelegate()->ContentsMouseEvent(
378        web_contents_,
379        gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(),
380        true);
381  }
382  return 0;
383}
384
385LRESULT WebContentsViewWin::OnNCCalcSize(
386    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
387  // Hack for ThinkPad mouse wheel driver. We have set the fake scroll bars
388  // to receive scroll messages from ThinkPad touch-pad driver. Suppress
389  // painting of scrollbars by returning 0 size for them.
390  return 0;
391}
392
393LRESULT WebContentsViewWin::OnNCHitTest(
394    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
395  return HTTRANSPARENT;
396}
397
398LRESULT WebContentsViewWin::OnScroll(
399    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
400  int scroll_type = LOWORD(wparam);
401  short position = HIWORD(wparam);
402  HWND scrollbar = reinterpret_cast<HWND>(lparam);
403  // This window can receive scroll events as a result of the ThinkPad's
404  // touch-pad scroll wheel emulation.
405  // If ctrl is held, zoom the UI.  There are three issues with this:
406  // 1) Should the event be eaten or forwarded to content?  We eat the event,
407  //    which is like Firefox and unlike IE.
408  // 2) Should wheel up zoom in or out?  We zoom in (increase font size), which
409  //    is like IE and Google maps, but unlike Firefox.
410  // 3) Should the mouse have to be over the content area?  We zoom as long as
411  //    content has focus, although FF and IE require that the mouse is over
412  //    content.  This is because all events get forwarded when content has
413  //    focus.
414  if (GetAsyncKeyState(VK_CONTROL) & 0x8000) {
415    int distance = 0;
416    switch (scroll_type) {
417      case SB_LINEUP:
418        distance = WHEEL_DELTA;
419        break;
420      case SB_LINEDOWN:
421        distance = -WHEEL_DELTA;
422        break;
423        // TODO(joshia): Handle SB_PAGEUP, SB_PAGEDOWN, SB_THUMBPOSITION,
424        // and SB_THUMBTRACK for completeness
425      default:
426        break;
427    }
428
429    web_contents_->GetDelegate()->ContentsZoomChange(distance > 0);
430    return 0;
431  }
432
433  // Reflect scroll message to the view() to give it a chance
434  // to process scrolling.
435  SendMessage(GetContentNativeView(), message, wparam, lparam);
436  return 0;
437}
438
439LRESULT WebContentsViewWin::OnSize(
440    UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) {
441  // NOTE: Because we handle OnWindowPosChanged without calling DefWindowProc,
442  // OnSize is NOT called on window resize. This handler is called only once
443  // when the window is created.
444  // Don't call base class OnSize to avoid useless layout for 0x0 size.
445  // We will get OnWindowPosChanged later and layout root view in WasSized.
446
447  // Hack for ThinkPad touch-pad driver.
448  // Set fake scrollbars so that we can get scroll messages,
449  SCROLLINFO si = {0};
450  si.cbSize = sizeof(si);
451  si.fMask = SIF_ALL;
452
453  si.nMin = 1;
454  si.nMax = 100;
455  si.nPage = 10;
456  si.nPos = 50;
457
458  ::SetScrollInfo(hwnd(), SB_HORZ, &si, FALSE);
459  ::SetScrollInfo(hwnd(), SB_VERT, &si, FALSE);
460
461  return 1;
462}
463
464}  // namespace content
465