blob: 47a9d0714da722b352ed1f3d304e3673c025a3cb [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.
#import <Cocoa/Cocoa.h>
#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/mac/WebInputEventFactory.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebScreenInfoFactory.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 "ui/gfx/rect.h"
#include "ui/gfx/size.h"
#include "webkit/glue/webkit_glue.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::WebWidgetClient;
/*static*/
WebWidgetHost* WebWidgetHost::Create(NSView* parent_view,
WebWidgetClient* client) {
WebWidgetHost* host = new WebWidgetHost();
NSRect content_rect = [parent_view frame];
content_rect.origin.y += 64;
content_rect.size.height -= 64;
host->view_ = [[NSView alloc] initWithFrame:content_rect];
[parent_view addSubview:host->view_];
// ui::SetWindowUserData(host->hwnd_, host);
host->webwidget_ = WebPopupMenu::create(client);
host->webwidget_->resize(WebSize(content_rect.size.width,
content_rect.size.height));
return host;
}
/*static*/
void WebWidgetHost::HandleEvent(NSView* view, NSEvent* event) {
/* TODO(port): rig up a way to get to the host */
WebWidgetHost* host = NULL;
if (host) {
switch ([event type]) {
case NSLeftMouseDown:
case NSLeftMouseUp:
case NSRightMouseDown:
case NSRightMouseUp:
case NSOtherMouseDown:
case NSOtherMouseUp:
case NSMouseEntered:
case NSMouseExited:
host->MouseEvent(event);
break;
case NSScrollWheel:
host->WheelEvent(event);
break;
case NSKeyDown:
case NSKeyUp:
host->KeyEvent(event);
break;
case NSAppKitDefined:
switch ([event subtype]) {
case NSApplicationActivatedEventType:
host->SetFocus(true);
break;
case NSApplicationDeactivatedEventType:
host->SetFocus(false);
break;
}
break;
}
}
}
void WebWidgetHost::DidInvalidateRect(const gfx::Rect& damaged_rect) {
#ifndef NDEBUG
DLOG_IF(WARNING, painting_) << "unexpected invalidation while painting";
#endif
// 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);
NSRect r = NSRectFromCGRect(damaged_rect.ToCGRect());
// flip to cocoa coordinates
r.origin.y = [view_ frame].size.height - r.size.height - r.origin.y;
[view_ setNeedsDisplayInRect:r];
}
void WebWidgetHost::DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect) {
DCHECK(dx || dy);
// 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;
NSRect r = NSRectFromCGRect(clip_rect.ToCGRect());
// flip to cocoa coordinates
r.origin.y = [view_ frame].size.height - r.size.height - r.origin.y;
[view_ setNeedsDisplayInRect:r];
}
void WebWidgetHost::ScheduleComposite() {
if (!webwidget_)
return;
WebSize size = webwidget_->size();
NSRect r = NSMakeRect(0, 0, size.width, size.height);
[view_ setNeedsDisplayInRect:r];
}
// void WebWidgetHost::SetCursor(HCURSOR cursor) {
// }
void WebWidgetHost::DiscardBackingStore() {
canvas_.reset();
}
WebWidgetHost::WebWidgetHost()
: view_(NULL),
webwidget_(NULL),
scroll_dx_(0),
scroll_dy_(0),
ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {
set_painting(false);
}
WebWidgetHost::~WebWidgetHost() {
// ui::SetWindowUserData(hwnd_, 0);
webwidget_->close();
}
void WebWidgetHost::UpdatePaintRect(const gfx::Rect& rect) {
paint_rect_ = paint_rect_.Union(rect);
}
void WebWidgetHost::Paint() {
gfx::Rect client_rect(NSRectToCGRect([view_ frame]));
NSGraphicsContext* view_context = [NSGraphicsContext currentContext];
CGContextRef context = static_cast<CGContextRef>([view_context graphicsPort]);
// 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));
}
// make sure webkit draws into our bitmap, not the window
CGContextRef bitmap_context =
skia::GetBitmapContext(skia::GetTopDevice(*canvas_));
[NSGraphicsContext setCurrentContext:
[NSGraphicsContext graphicsContextWithGraphicsPort:bitmap_context
flipped:YES]];
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()) {
// add to invalidate rect, since there's no equivalent of ScrollDC.
paint_rect_ = paint_rect_.Union(scroll_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());
// set the context back to our window
[NSGraphicsContext setCurrentContext: view_context];
// Paint to the screen
if ([view_ lockFocusIfCanDraw]) {
int bitmap_height = CGBitmapContextGetHeight(bitmap_context);
int bitmap_width = CGBitmapContextGetWidth(bitmap_context);
CGRect bitmap_rect = { { 0, 0 },
{ bitmap_width, bitmap_height } };
skia::DrawToNativeContext(canvas_.get(), context, 0,
client_rect.height() - bitmap_height, &bitmap_rect);
[view_ unlockFocus];
}
}
WebScreenInfo WebWidgetHost::GetScreenInfo() {
return WebScreenInfoFactory::screenInfo(view_);
}
void WebWidgetHost::Resize(const gfx::Rect& rect) {
// Force an entire re-paint. TODO(darin): Maybe reuse this memory buffer.
DiscardBackingStore();
webwidget_->resize(WebSize(rect.width(), rect.height()));
}
void WebWidgetHost::MouseEvent(NSEvent *event) {
const WebMouseEvent& web_event = WebInputEventFactory::mouseEvent(
event, view_);
webwidget_->handleInputEvent(web_event);
}
void WebWidgetHost::WheelEvent(NSEvent *event) {
webwidget_->handleInputEvent(
WebInputEventFactory::mouseWheelEvent(event, view_));
}
void WebWidgetHost::KeyEvent(NSEvent *event) {
WebKeyboardEvent keyboard_event(WebInputEventFactory::keyboardEvent(event));
webwidget_->handleInputEvent(keyboard_event);
if ([event type] == NSKeyDown) {
// Send a Char event here to emulate the keyboard events.
// TODO(hbono): Bug 20852 <http://crbug.com/20852> implement the
// NSTextInput protocol and remove this code.
keyboard_event.type = WebInputEvent::Char;
webwidget_->handleInputEvent(keyboard_event);
}
}
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::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(webkit_glue::ToWebCanvas(canvas_.get()), rect);
set_painting(false);
}