blob: 0d06d6c2cd06e7bf1de7cab64d7182e3255ffbd4 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webkit/tools/test_shell/webwidget_host.h"
#include "base/logging.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenu.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/win/WebInputEventFactory.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/win/WebScreenInfoFactory.h"
#include "ui/base/win/hwnd_util.h"
#include "ui/gfx/rect.h"
#include "webkit/tools/test_shell/test_shell.h"
using WebKit::WebInputEvent;
using WebKit::WebInputEventFactory;
using WebKit::WebKeyboardEvent;
using WebKit::WebMouseEvent;
using WebKit::WebMouseWheelEvent;
using WebKit::WebPopupMenu;
using WebKit::WebScreenInfo;
using WebKit::WebScreenInfoFactory;
using WebKit::WebSize;
using WebKit::WebWidget;
using WebKit::WebWidgetClient;
static const wchar_t kWindowClassName[] = L"WebWidgetHost";
/*static*/
WebWidgetHost* WebWidgetHost::Create(HWND parent_view,
WebWidgetClient* client) {
WebWidgetHost* host = new WebWidgetHost();
static bool registered_class = false;
if (!registered_class) {
WNDCLASSEX wcex = {0};
wcex.cbSize = sizeof(wcex);
wcex.style = CS_DBLCLKS;
wcex.lpfnWndProc = WebWidgetHost::WndProc;
wcex.hInstance = GetModuleHandle(NULL);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszClassName = kWindowClassName;
RegisterClassEx(&wcex);
registered_class = true;
}
host->view_ = CreateWindowEx(WS_EX_TOOLWINDOW,
kWindowClassName, kWindowClassName, WS_POPUP,
0, 0, 0, 0,
parent_view, NULL, GetModuleHandle(NULL), NULL);
ui::SetWindowUserData(host->view_, host);
host->webwidget_ = WebPopupMenu::create(client);
return host;
}
static WebWidgetHost* FromWindow(HWND view) {
return reinterpret_cast<WebWidgetHost*>(ui::GetWindowUserData(view));
}
/*static*/
LRESULT CALLBACK WebWidgetHost::WndProc(HWND hwnd, UINT message, WPARAM wparam,
LPARAM lparam) {
WebWidgetHost* host = FromWindow(hwnd);
if (host && !host->WndProc(message, wparam, lparam)) {
switch (message) {
case WM_DESTROY:
delete host;
break;
case WM_PAINT: {
RECT rect;
if (GetUpdateRect(hwnd, &rect, FALSE)) {
host->UpdatePaintRect(gfx::Rect(rect));
}
host->Paint();
return 0;
}
case WM_ERASEBKGND:
// Do nothing here to avoid flashing, the background will be erased
// during painting.
return 0;
case WM_SIZE:
host->Resize(lparam);
return 0;
case WM_MOUSEMOVE:
case WM_MOUSELEAVE:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
host->MouseEvent(message, wparam, lparam);
break;
case WM_MOUSEWHEEL:
host->WheelEvent(wparam, lparam);
break;
case WM_CAPTURECHANGED:
case WM_CANCELMODE:
host->CaptureLostEvent();
break;
// TODO(darin): add WM_SYSKEY{DOWN/UP} to capture ALT key actions
case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_CHAR:
case WM_SYSCHAR:
host->KeyEvent(message, wparam, lparam);
break;
case WM_SETFOCUS:
host->SetFocus(true);
break;
case WM_KILLFOCUS:
host->SetFocus(false);
break;
}
}
return DefWindowProc(hwnd, message, wparam, lparam);;
}
void WebWidgetHost::DidInvalidateRect(const gfx::Rect& damaged_rect) {
DLOG_IF(WARNING, painting_) << "unexpected invalidation while painting";
// If this invalidate overlaps with a pending scroll, then we have to
// downgrade to invalidating the scroll rect.
if (damaged_rect.Intersects(scroll_rect_)) {
paint_rect_ = paint_rect_.Union(scroll_rect_);
ResetScrollRect();
}
paint_rect_ = paint_rect_.Union(damaged_rect);
RECT r = damaged_rect.ToRECT();
InvalidateRect(view_, &r, FALSE);
}
void WebWidgetHost::DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect) {
if (dx != 0 && dy != 0) {
// We only support uni-directional scroll
DidScrollRect(0, dy, clip_rect);
dy = 0;
}
// If we already have a pending scroll operation or if this scroll operation
// intersects the existing paint region, then just failover to invalidating.
if (!scroll_rect_.IsEmpty() || paint_rect_.Intersects(clip_rect)) {
paint_rect_ = paint_rect_.Union(scroll_rect_);
ResetScrollRect();
paint_rect_ = paint_rect_.Union(clip_rect);
}
// We will perform scrolling lazily, when requested to actually paint.
scroll_rect_ = clip_rect;
scroll_dx_ = dx;
scroll_dy_ = dy;
RECT r = clip_rect.ToRECT();
InvalidateRect(view_, &r, FALSE);
}
void WebWidgetHost::ScheduleComposite() {
if (!webwidget_)
return;
WebSize size = webwidget_->size();
gfx::Rect rect(0, 0, size.width, size.height);
RECT r = rect.ToRECT();
InvalidateRect(view_, &r, FALSE);
}
void WebWidgetHost::SetCursor(HCURSOR cursor) {
SetClassLong(view_, GCL_HCURSOR,
static_cast<LONG>(reinterpret_cast<LONG_PTR>(cursor)));
::SetCursor(cursor);
}
void WebWidgetHost::DiscardBackingStore() {
canvas_.reset();
}
WebWidgetHost::WebWidgetHost()
: view_(NULL),
webwidget_(NULL),
track_mouse_leave_(false),
scroll_dx_(0),
scroll_dy_(0),
ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {
set_painting(false);
}
WebWidgetHost::~WebWidgetHost() {
ui::SetWindowUserData(view_, 0);
TrackMouseLeave(false);
webwidget_->close();
}
bool WebWidgetHost::WndProc(UINT message, WPARAM wparam, LPARAM lparam) {
switch (message) {
case WM_ACTIVATE:
if (wparam == WA_INACTIVE) {
PostMessage(view_, WM_CLOSE, 0, 0);
return true;
}
break;
}
return false;
}
void WebWidgetHost::UpdatePaintRect(const gfx::Rect& rect) {
paint_rect_ = paint_rect_.Union(rect);
}
void WebWidgetHost::Paint() {
RECT r;
GetClientRect(view_, &r);
gfx::Rect client_rect(r);
// Allocate a canvas if necessary
if (!canvas_.get()) {
ResetScrollRect();
paint_rect_ = client_rect;
canvas_.reset(new skia::PlatformCanvas(
paint_rect_.width(), paint_rect_.height(), true));
}
webwidget_->animate(0.0);
// This may result in more invalidation
webwidget_->layout();
// Scroll the canvas if necessary
scroll_rect_ = client_rect.Intersect(scroll_rect_);
if (!scroll_rect_.IsEmpty()) {
skia::ScopedPlatformPaint scoped_platform_paint(canvas_.get());
HDC hdc = scoped_platform_paint.GetPlatformSurface();
RECT damaged_rect, r = scroll_rect_.ToRECT();
ScrollDC(hdc, scroll_dx_, scroll_dy_, NULL, &r, NULL, &damaged_rect);
PaintRect(gfx::Rect(damaged_rect));
}
ResetScrollRect();
// Paint the canvas if necessary. Allow painting to generate extra rects the
// first time we call it. This is necessary because some WebCore rendering
// objects update their layout only when painted.
for (int i = 0; i < 2; ++i) {
paint_rect_ = client_rect.Intersect(paint_rect_);
if (!paint_rect_.IsEmpty()) {
gfx::Rect rect(paint_rect_);
paint_rect_ = gfx::Rect();
DLOG_IF(WARNING, i == 1) << "painting caused additional invalidations";
PaintRect(rect);
}
}
DCHECK(paint_rect_.IsEmpty());
// Paint to the screen
PAINTSTRUCT ps;
BeginPaint(view_, &ps);
skia::DrawToNativeContext(canvas_.get(), ps.hdc, ps.rcPaint.left,
ps.rcPaint.top, &ps.rcPaint);
EndPaint(view_, &ps);
// Draw children
UpdateWindow(view_);
}
WebScreenInfo WebWidgetHost::GetScreenInfo() {
return WebScreenInfoFactory::screenInfo(view_);
}
void WebWidgetHost::Resize(LPARAM lparam) {
// Force an entire re-paint. TODO(darin): Maybe reuse this memory buffer.
DiscardBackingStore();
webwidget_->resize(WebSize(LOWORD(lparam), HIWORD(lparam)));
}
void WebWidgetHost::MouseEvent(UINT message, WPARAM wparam, LPARAM lparam) {
const WebMouseEvent& event = WebInputEventFactory::mouseEvent(
view_, message, wparam, lparam);
webwidget_->handleInputEvent(event);
switch (event.type) {
case WebInputEvent::MouseMove:
TrackMouseLeave(true);
break;
case WebInputEvent::MouseLeave:
TrackMouseLeave(false);
break;
case WebInputEvent::MouseDown:
SetCapture(view_);
// This mimics a temporary workaround in RenderWidgetHostViewWin for bug
// 765011 to get focus when the mouse is clicked. This happens after the
// mouse down event is sent to the renderer because normally Windows does
// a WM_SETFOCUS after WM_LBUTTONDOWN.
::SetFocus(view_);
break;
case WebInputEvent::MouseUp:
if (GetCapture() == view_)
ReleaseCapture();
break;
}
}
void WebWidgetHost::WheelEvent(WPARAM wparam, LPARAM lparam) {
const WebMouseWheelEvent& event = WebInputEventFactory::mouseWheelEvent(
view_, WM_MOUSEWHEEL, wparam, lparam);
webwidget_->handleInputEvent(event);
}
void WebWidgetHost::KeyEvent(UINT message, WPARAM wparam, LPARAM lparam) {
const WebKeyboardEvent& event = WebInputEventFactory::keyboardEvent(
view_, message, wparam, lparam);
webwidget_->handleInputEvent(event);
}
void WebWidgetHost::CaptureLostEvent() {
webwidget_->mouseCaptureLost();
}
void WebWidgetHost::SetFocus(bool enable) {
// Ignore focus calls in layout test mode so that tests don't mess with each
// other's focus when running in parallel.
if (!TestShell::layout_test_mode())
webwidget_->setFocus(enable);
}
void WebWidgetHost::TrackMouseLeave(bool track) {
if (track == track_mouse_leave_)
return;
track_mouse_leave_ = track;
DCHECK(view_);
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
if (!track_mouse_leave_)
tme.dwFlags |= TME_CANCEL;
tme.hwndTrack = view_;
TrackMouseEvent(&tme);
}
void WebWidgetHost::ResetScrollRect() {
scroll_rect_ = gfx::Rect();
scroll_dx_ = 0;
scroll_dy_ = 0;
}
void WebWidgetHost::PaintRect(const gfx::Rect& rect) {
#ifndef NDEBUG
DCHECK(!painting_);
#endif
DCHECK(canvas_.get());
set_painting(true);
webwidget_->paint(canvas_.get(), rect);
set_painting(false);
}