summaryrefslogtreecommitdiffstats
path: root/content/browser
diff options
context:
space:
mode:
authoravi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-28 15:11:05 +0000
committeravi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-28 15:11:05 +0000
commitae3fec8dc8d5c0559e495994c7e096354578bcab (patch)
treeb304b634b657ec17fa7331856493a8dda8d5d7b7 /content/browser
parent66db7bc0efc03109bddc3b31f7944d7fe492f643 (diff)
downloadchromium_src-ae3fec8dc8d5c0559e495994c7e096354578bcab.zip
chromium_src-ae3fec8dc8d5c0559e495994c7e096354578bcab.tar.gz
chromium_src-ae3fec8dc8d5c0559e495994c7e096354578bcab.tar.bz2
Move RenderWidgetHostViewMac to content.
BUG=95573 TEST=no visible change Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=103113 Reverted: http://src.chromium.org/viewvc/chrome?view=rev&revision=103117 Review URL: http://codereview.chromium.org/8052026 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@103118 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser')
-rw-r--r--content/browser/renderer_host/accelerated_plugin_view_mac.h58
-rw-r--r--content/browser/renderer_host/accelerated_plugin_view_mac.mm247
-rw-r--r--content/browser/renderer_host/accelerated_plugin_view_mac_unittest.mm188
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac.h410
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac.mm2740
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h70
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.mm235
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm169
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac_unittest.mm194
9 files changed, 4311 insertions, 0 deletions
diff --git a/content/browser/renderer_host/accelerated_plugin_view_mac.h b/content/browser/renderer_host/accelerated_plugin_view_mac.h
new file mode 100644
index 0000000..f404c52
--- /dev/null
+++ b/content/browser/renderer_host/accelerated_plugin_view_mac.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_PLUGIN_VIEW_MAC_H
+#define CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_PLUGIN_VIEW_MAC_H
+
+#import <Cocoa/Cocoa.h>
+#include <QuartzCore/QuartzCore.h>
+
+#include "base/memory/scoped_nsobject.h"
+#include "ui/gfx/native_widget_types.h"
+
+class RenderWidgetHostViewMac;
+
+// Informal protocol implemented by windows that need to be informed explicitly
+// about underlay surfaces.
+@interface NSObject (UnderlayableSurface)
+- (void)underlaySurfaceAdded;
+- (void)underlaySurfaceRemoved;
+@end
+
+// This subclass of NSView hosts the output of accelerated plugins on
+// the page.
+@interface AcceleratedPluginView : NSView {
+ scoped_nsobject<NSOpenGLPixelFormat> glPixelFormat_;
+ CGLPixelFormatObj cglPixelFormat_; // weak, backed by |glPixelFormat_|.
+ scoped_nsobject<NSOpenGLContext> glContext_;
+ CGLContextObj cglContext_; // weak, backed by |glContext_|.
+
+ RenderWidgetHostViewMac* renderWidgetHostView_; // weak
+ gfx::PluginWindowHandle pluginHandle_; // weak
+
+ // Rects that should show web content rather than plugin content.
+ scoped_nsobject<NSArray> cutoutRects_;
+}
+
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r
+ pluginHandle:(gfx::PluginWindowHandle)pluginHandle;
+
+// Sets the list of rectangles that should show the web page, rather than the
+// accelerated plugin. This is used to simulate the iframe-based trick that web
+// pages have long used to show web content above windowed plugins on Windows
+// and Linux.
+- (void)setCutoutRects:(NSArray*)cutout_rects;
+
+// NSViews autorelease subviews when they die. The RWHVMac gets destroyed when
+// RHWVCocoa gets dealloc'd, which means the AcceleratedPluginView child views
+// can be around a little longer than the RWHVMac. This is called when the
+// RWHVMac is about to be deleted (but it's still valid while this method runs).
+- (void)onRenderWidgetHostViewGone;
+
+// Draw the view from the UI thread.
+- (void)drawView;
+
+@end
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_PLUGIN_VIEW_MAC_H
diff --git a/content/browser/renderer_host/accelerated_plugin_view_mac.mm b/content/browser/renderer_host/accelerated_plugin_view_mac.mm
new file mode 100644
index 0000000..9bcc433
--- /dev/null
+++ b/content/browser/renderer_host/accelerated_plugin_view_mac.mm
@@ -0,0 +1,247 @@
+// 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 "content/browser/renderer_host/accelerated_plugin_view_mac.h"
+
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "content/browser/browser_thread.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+#include "ui/gfx/gl/gl_switches.h"
+#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
+
+@implementation AcceleratedPluginView
+
+- (void)drawView {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ TRACE_EVENT0("browser", "AcceleratedPluginViewMac::drawView");
+
+ if (renderWidgetHostView_) {
+ // TODO(thakis): Pixel or view coordinates for size?
+ renderWidgetHostView_->DrawAcceleratedSurfaceInstance(
+ cglContext_, pluginHandle_, [super frame].size);
+ }
+
+ CGLFlushDrawable(cglContext_);
+ CGLSetCurrentContext(0);
+}
+
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r
+ pluginHandle:(gfx::PluginWindowHandle)pluginHandle {
+ if ((self = [super initWithFrame:NSZeroRect])) {
+ renderWidgetHostView_ = r;
+ pluginHandle_ = pluginHandle;
+
+ [self setAutoresizingMask:NSViewMaxXMargin|NSViewMinYMargin];
+
+ NSOpenGLPixelFormatAttribute attributes[] =
+ { NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, 0};
+
+ // TODO(zmo): remove the diagnostic error messages once we figure out the
+ // cause of the failure and a fix.
+
+ glPixelFormat_.reset([[NSOpenGLPixelFormat alloc]
+ initWithAttributes:attributes]);
+ if (!glPixelFormat_)
+ LOG(ERROR) << "NSOpenGLPixelFormat initWithAttributes failed";
+
+ glContext_.reset([[NSOpenGLContext alloc] initWithFormat:glPixelFormat_
+ shareContext:nil]);
+ if (!glContext_)
+ LOG(ERROR) << "NSOpenGLContext initWithFormat failed";
+
+ // We "punch a hole" in the window, and have the WindowServer render the
+ // OpenGL surface underneath so we can draw over it.
+ GLint belowWindow = -1;
+ [glContext_ setValues:&belowWindow forParameter:NSOpenGLCPSurfaceOrder];
+
+ cglContext_ = (CGLContextObj)[glContext_ CGLContextObj];
+ if (!cglContext_)
+ LOG(ERROR) << "CGLContextObj failed";
+
+ cglPixelFormat_ = (CGLPixelFormatObj)[glPixelFormat_ CGLPixelFormatObj];
+ if (!cglPixelFormat_)
+ LOG(ERROR) << "CGLPixelFormatObj failed";
+
+ // Draw at beam vsync.
+ GLint swapInterval;
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync))
+ swapInterval = 0;
+ else
+ swapInterval = 1;
+ [glContext_ setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(globalFrameDidChange:)
+ name:NSViewGlobalFrameDidChangeNotification
+ object:self];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (renderWidgetHostView_)
+ renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_);
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)setCutoutRects:(NSArray*)cutout_rects {
+ cutoutRects_.reset([cutout_rects copy]);
+}
+
+- (void)onRenderWidgetHostViewGone {
+ if (!renderWidgetHostView_)
+ return;
+
+ // Deallocate the plugin handle while we still can.
+ renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_);
+ renderWidgetHostView_ = NULL;
+}
+
+- (void)drawRect:(NSRect)rect {
+ TRACE_EVENT0("browser", "AcceleratedPluginView::drawRect");
+ const NSRect* dirtyRects;
+ int dirtyRectCount;
+ [self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount];
+
+ {
+ gfx::ScopedNSGraphicsContextSaveGState scopedGState;
+
+ // Mask out any cutout rects--somewhat counterintuitively cutout rects are
+ // places where clearColor is *not* drawn. The trick is that drawing nothing
+ // lets the parent view (i.e., the web page) show through, whereas drawing
+ // clearColor punches a hole in the window (letting OpenGL show through).
+ if ([cutoutRects_.get() count] > 0) {
+ NSBezierPath* path = [NSBezierPath bezierPath];
+ // Trace the bounds clockwise to give a base clip rect of the whole view.
+ NSRect bounds = [self bounds];
+ [path moveToPoint:bounds.origin];
+ [path lineToPoint:NSMakePoint(NSMinX(bounds), NSMaxY(bounds))];
+ [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))];
+ [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMinY(bounds))];
+ [path closePath];
+
+ // Then trace each cutout rect counterclockwise to remove that region from
+ // the clip region.
+ for (NSValue* rectWrapper in cutoutRects_.get()) {
+ [path appendBezierPathWithRect:[rectWrapper rectValue]];
+ }
+
+ [path addClip];
+ }
+
+ // Punch a hole so that the OpenGL view shows through.
+ [[NSColor clearColor] set];
+ NSRectFillList(dirtyRects, dirtyRectCount);
+ }
+
+ [self drawView];
+}
+
+- (void)rightMouseDown:(NSEvent*)event {
+ // The NSResponder documentation: "Note: The NSView implementation of this
+ // method does not pass the message up the responder chain, it handles it
+ // directly."
+ // That's bad, we want the next responder (RWHVMac) to handle this event to
+ // dispatch it to the renderer.
+ [[self nextResponder] rightMouseDown:event];
+}
+
+- (void)globalFrameDidChange:(NSNotification*)notification {
+ // This call to -update can call -globalFrameDidChange: again, see
+ // http://crbug.com/55754 comments 22 and 24.
+ [glContext_ update];
+
+ // You would think that -update updates the viewport. You would be wrong.
+ CGLSetCurrentContext(cglContext_);
+ NSSize size = [self frame].size;
+ glViewport(0, 0, size.width, size.height);
+ CGLSetCurrentContext(0);
+}
+
+- (void)prepareForGLRendering {
+ TRACE_EVENT0("browser", "AcceleratedPluginView::prepareForGLRendering");
+
+ // If we're using OpenGL, make sure it is connected.
+ if ([glContext_ view] != self) {
+ [glContext_ setView:self];
+ }
+}
+
+- (void)renewGState {
+ TRACE_EVENT0("browser", "AcceleratedPluginView::renewGState");
+ // Synchronize with window server to avoid flashes or corrupt drawing.
+ [[self window] disableScreenUpdatesUntilFlush];
+ [self globalFrameDidChange:nil];
+ [super renewGState];
+}
+
+- (void)setUpGState {
+ TRACE_EVENT0("browser", "AcceleratedPluginView::setUpGState");
+ [self prepareForGLRendering];
+}
+
+// TODO(jbates): is lockFocus needed anymore? it seems redundant with
+// setUpGState in traces.
+- (void)lockFocus {
+ TRACE_EVENT0("browser", "AcceleratedPluginView::lockFocus");
+ [super lockFocus];
+ [self prepareForGLRendering];
+ [glContext_ makeCurrentContext];
+}
+
+- (void)viewWillMoveToWindow:(NSWindow*)newWindow {
+ TRACE_EVENT1("browser", "AcceleratedPluginView::viewWillMoveToWindow",
+ "newWindow", newWindow);
+ // Inform the window hosting this accelerated view that it needs to be
+ // transparent.
+ if (![self isHiddenOrHasHiddenAncestor]) {
+ if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)])
+ [static_cast<id>([self window]) underlaySurfaceRemoved];
+ if ([newWindow respondsToSelector:@selector(underlaySurfaceAdded)])
+ [static_cast<id>(newWindow) underlaySurfaceAdded];
+ }
+}
+
+- (void)viewDidHide {
+ TRACE_EVENT0("browser", "AcceleratedPluginView::viewDidHide");
+ [super viewDidHide];
+
+ if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)]) {
+ [static_cast<id>([self window]) underlaySurfaceRemoved];
+ }
+}
+
+- (void)viewDidUnhide {
+ TRACE_EVENT0("browser", "AcceleratedPluginView::viewDidUnhide");
+ [super viewDidUnhide];
+
+ if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)]) {
+ [static_cast<id>([self window]) underlaySurfaceAdded];
+ }
+}
+
+- (BOOL)acceptsFirstResponder {
+ // Accept first responder if the first responder isn't the RWHVMac, and if the
+ // RWHVMac accepts first responder. If the RWHVMac does not accept first
+ // responder, do not accept on its behalf.
+ return ([[self window] firstResponder] != [self superview] &&
+ [[self superview] acceptsFirstResponder]);
+}
+
+- (BOOL)becomeFirstResponder {
+ // Delegate first responder to the RWHVMac.
+ [[self window] makeFirstResponder:[self superview]];
+ return YES;
+}
+
+- (void)viewDidMoveToSuperview {
+ TRACE_EVENT0("browser", "AcceleratedPluginView::viewDidMoveToSuperview");
+ if (![self superview])
+ [self onRenderWidgetHostViewGone];
+}
+@end
+
diff --git a/content/browser/renderer_host/accelerated_plugin_view_mac_unittest.mm b/content/browser/renderer_host/accelerated_plugin_view_mac_unittest.mm
new file mode 100644
index 0000000..4d902d2
--- /dev/null
+++ b/content/browser/renderer_host/accelerated_plugin_view_mac_unittest.mm
@@ -0,0 +1,188 @@
+// 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 "content/browser/renderer_host/accelerated_plugin_view_mac.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+@interface UnderlayCountingWindow : NSWindow {
+ @private
+ int underlayCount_;
+}
+@property (readonly, nonatomic) int underlayCount;
+- (void)underlaySurfaceAdded;
+- (void)underlaySurfaceRemoved;
+@end
+
+@implementation UnderlayCountingWindow
+@synthesize underlayCount = underlayCount_;
+
+- (void)underlaySurfaceAdded {
+ DCHECK_GE(underlayCount_, 0);
+ ++underlayCount_;
+}
+
+- (void)underlaySurfaceRemoved {
+ --underlayCount_;
+ DCHECK_GE(underlayCount_, 0);
+}
+@end
+
+class AcceleratedPluginViewTest : public PlatformTest {
+ protected:
+ AcceleratedPluginViewTest() {}
+
+ UnderlayCountingWindow* StubUnderlayWindow() {
+ UnderlayCountingWindow* window = [[[UnderlayCountingWindow alloc]
+ initWithContentRect:NSMakeRect(20, 20, 300, 200)
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:NO] autorelease];
+ [window orderFront:nil];
+ return window;
+ }
+
+ AcceleratedPluginView* StubAcceleratedPluginView() {
+ // It truns out the rwhv and the plugin handle are not necessary for
+ // this test, and AcceleratedPluginView doesn't crash without them.
+ AcceleratedPluginView* view = [[[AcceleratedPluginView alloc]
+ initWithRenderWidgetHostViewMac:NULL
+ pluginHandle:0] autorelease];
+ return view;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AcceleratedPluginViewTest);
+};
+
+TEST_F(AcceleratedPluginViewTest, Basic) {
+}
+
+TEST_F(AcceleratedPluginViewTest, BasicAdding) {
+ AcceleratedPluginView* view = StubAcceleratedPluginView();
+
+ UnderlayCountingWindow* window = StubUnderlayWindow();
+ EXPECT_EQ(0, [window underlayCount]);
+
+ [[window contentView] addSubview:view];
+ EXPECT_EQ(1, [window underlayCount]);
+
+ [view removeFromSuperview];
+ EXPECT_EQ(0, [window underlayCount]);
+}
+
+TEST_F(AcceleratedPluginViewTest, MoveBetweenWindows) {
+ AcceleratedPluginView* view = StubAcceleratedPluginView();
+
+ UnderlayCountingWindow* window1 = StubUnderlayWindow();
+ UnderlayCountingWindow* window2 = StubUnderlayWindow();
+ EXPECT_EQ(0, [window1 underlayCount]);
+ EXPECT_EQ(0, [window2 underlayCount]);
+
+ [[window1 contentView] addSubview:view];
+ EXPECT_EQ(1, [window1 underlayCount]);
+ EXPECT_EQ(0, [window2 underlayCount]);
+
+ [[window2 contentView] addSubview:view];
+ EXPECT_EQ(0, [window1 underlayCount]);
+ EXPECT_EQ(1, [window2 underlayCount]);
+}
+
+TEST_F(AcceleratedPluginViewTest, HiddenWhenAdding) {
+ AcceleratedPluginView* view = StubAcceleratedPluginView();
+ [view setHidden:YES];
+
+ UnderlayCountingWindow* window = StubUnderlayWindow();
+ EXPECT_EQ(0, [window underlayCount]);
+
+ [[window contentView] addSubview:view];
+ EXPECT_EQ(0, [window underlayCount]);
+
+ [view setHidden:NO];
+ EXPECT_EQ(1, [window underlayCount]);
+
+ [view setHidden:YES];
+ EXPECT_EQ(0, [window underlayCount]);
+
+ [view removeFromSuperview];
+ EXPECT_EQ(0, [window underlayCount]);
+}
+
+TEST_F(AcceleratedPluginViewTest, HiddenAfterAdding) {
+ AcceleratedPluginView* view = StubAcceleratedPluginView();
+
+ UnderlayCountingWindow* window = StubUnderlayWindow();
+ EXPECT_EQ(0, [window underlayCount]);
+
+ [[window contentView] addSubview:view];
+ EXPECT_EQ(1, [window underlayCount]);
+
+ [view setHidden:YES];
+ EXPECT_EQ(0, [window underlayCount]);
+
+ [view setHidden:NO];
+ EXPECT_EQ(1, [window underlayCount]);
+
+ [view removeFromSuperview];
+ EXPECT_EQ(0, [window underlayCount]);
+}
+
+TEST_F(AcceleratedPluginViewTest, MoveBetweenWindowsWithHiding) {
+ AcceleratedPluginView* view = StubAcceleratedPluginView();
+ [view setHidden:YES];
+
+ UnderlayCountingWindow* window1 = StubUnderlayWindow();
+ UnderlayCountingWindow* window2 = StubUnderlayWindow();
+ EXPECT_EQ(0, [window1 underlayCount]);
+ EXPECT_EQ(0, [window2 underlayCount]);
+
+ [[window1 contentView] addSubview:view];
+ EXPECT_EQ(0, [window1 underlayCount]);
+ EXPECT_EQ(0, [window2 underlayCount]);
+
+ [view setHidden:NO];
+ EXPECT_EQ(1, [window1 underlayCount]);
+ EXPECT_EQ(0, [window2 underlayCount]);
+
+ // Move view while it's visible.
+ [[window2 contentView] addSubview:view];
+ EXPECT_EQ(0, [window1 underlayCount]);
+ EXPECT_EQ(1, [window2 underlayCount]);
+
+ [view setHidden:YES];
+ EXPECT_EQ(0, [window1 underlayCount]);
+ EXPECT_EQ(0, [window2 underlayCount]);
+
+ // Move view while it's hidden.
+ [[window1 contentView] addSubview:view];
+ EXPECT_EQ(0, [window1 underlayCount]);
+ EXPECT_EQ(0, [window2 underlayCount]);
+
+ [view setHidden:NO];
+ EXPECT_EQ(1, [window1 underlayCount]);
+ EXPECT_EQ(0, [window2 underlayCount]);
+}
+
+// Regression test for http://crbug.com/81737
+TEST_F(AcceleratedPluginViewTest, RemoveWithHiddenParent) {
+ AcceleratedPluginView* view = StubAcceleratedPluginView();
+
+ NSView* parent =
+ [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)] autorelease];
+ [parent addSubview:view];
+
+ UnderlayCountingWindow* window = StubUnderlayWindow();
+ EXPECT_EQ(0, [window underlayCount]);
+
+ [[window contentView] addSubview:parent];
+ EXPECT_EQ(1, [window underlayCount]);
+
+ [parent setHidden:YES];
+ EXPECT_EQ(0, [window underlayCount]);
+
+ [parent removeFromSuperview];
+ EXPECT_EQ(0, [window underlayCount]);
+}
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.h b/content/browser/renderer_host/render_widget_host_view_mac.h
new file mode 100644
index 0000000..92e48c0
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -0,0 +1,410 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_
+#pragma once
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task.h"
+#include "base/time.h"
+#include "content/browser/accessibility/browser_accessibility_delegate_mac.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/renderer_host/accelerated_surface_container_manager_mac.h"
+#include "content/browser/renderer_host/render_widget_host_view.h"
+#include "content/common/edit_command.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderline.h"
+#include "ui/base/cocoa/base_view.h"
+#include "webkit/glue/webcursor.h"
+
+@class AcceleratedPluginView;
+class RenderWidgetHostViewMac;
+@class RenderWidgetHostViewMacDelegate;
+class RenderWidgetHostViewMacEditCommandHelper;
+@class ToolTip;
+
+@protocol RenderWidgetHostViewMacOwner
+- (RenderWidgetHostViewMac*)renderWidgetHostViewMac;
+@end
+
+// This is the view that lives in the Cocoa view hierarchy. In Windows-land,
+// RenderWidgetHostViewWin is both the view and the delegate. We split the roles
+// but that means that the view needs to own the delegate and will dispose of it
+// when it's removed from the view system.
+@interface RenderWidgetHostViewCocoa
+ : BaseView <RenderWidgetHostViewMacOwner,
+ NSTextInputClient,
+ BrowserAccessibilityDelegateCocoa> {
+ @private
+ scoped_ptr<RenderWidgetHostViewMac> renderWidgetHostView_;
+ RenderWidgetHostViewMacDelegate* delegate_; // weak
+ BOOL canBeKeyView_;
+ BOOL takesFocusOnlyOnMouseDown_;
+ BOOL closeOnDeactivate_;
+ scoped_ptr<RenderWidgetHostViewMacEditCommandHelper> editCommand_helper_;
+
+ // These are part of the magic tooltip code from WebKit's WebHTMLView:
+ id trackingRectOwner_; // (not retained)
+ void *trackingRectUserData_;
+ NSTrackingRectTag lastToolTipTag_;
+ scoped_nsobject<NSString> toolTip_;
+
+ // Is YES if there was a mouse-down as yet unbalanced with a mouse-up.
+ BOOL hasOpenMouseDown_;
+
+ NSWindow* lastWindow_; // weak
+
+ // Variables used by our implementaion of the NSTextInput protocol.
+ // An input method of Mac calls the methods of this protocol not only to
+ // notify an application of its status, but also to retrieve the status of
+ // the application. That is, an application cannot control an input method
+ // directly.
+ // This object keeps the status of a composition of the renderer and returns
+ // it when an input method asks for it.
+ // We need to implement Objective-C methods for the NSTextInput protocol. On
+ // the other hand, we need to implement a C++ method for an IPC-message
+ // handler which receives input-method events from the renderer.
+
+ // Represents the input-method attributes supported by this object.
+ scoped_nsobject<NSArray> validAttributesForMarkedText_;
+
+ // Indicates if we are currently handling a key down event.
+ BOOL handlingKeyDown_;
+
+ // Indicates if there is any marked text.
+ BOOL hasMarkedText_;
+
+ // Indicates if unmarkText is called or not when handling a keyboard
+ // event.
+ BOOL unmarkTextCalled_;
+
+ // The range of current marked text inside the whole content of the DOM node
+ // being edited.
+ // TODO(suzhe): This is currently a fake value, as we do not support accessing
+ // the whole content yet.
+ NSRange markedRange_;
+
+ // The selected range, cached from a message sent by the renderer.
+ NSRange selectedRange_;
+
+ // Text to be inserted which was generated by handling a key down event.
+ string16 textToBeInserted_;
+
+ // Marked text which was generated by handling a key down event.
+ string16 markedText_;
+
+ // Underline information of the |markedText_|.
+ std::vector<WebKit::WebCompositionUnderline> underlines_;
+
+ // Indicates if doCommandBySelector method receives any edit command when
+ // handling a key down event.
+ BOOL hasEditCommands_;
+
+ // Contains edit commands received by the -doCommandBySelector: method when
+ // handling a key down event, not including inserting commands, eg. insertTab,
+ // etc.
+ EditCommands editCommands_;
+
+ // The plugin that currently has focus (-1 if no plugin has focus).
+ int focusedPluginIdentifier_;
+
+ // Whether or not plugin IME is currently enabled active.
+ BOOL pluginImeActive_;
+
+ // Whether the previous mouse event was ignored due to hitTest check.
+ BOOL mouseEventWasIgnored_;
+
+ // Event monitor for gesture-end events.
+ id endGestureMonitor_;
+}
+
+@property(nonatomic, readonly) NSRange selectedRange;
+
+- (void)setCanBeKeyView:(BOOL)can;
+- (void)setTakesFocusOnlyOnMouseDown:(BOOL)b;
+- (void)setCloseOnDeactivate:(BOOL)b;
+- (void)setToolTipAtMousePoint:(NSString *)string;
+// Set frame, then notify the RenderWidgetHost that the frame has been changed,
+// but do it in a separate task, using |performSelector:withObject:afterDelay:|.
+// This stops the flickering issue in http://crbug.com/31970
+- (void)setFrameWithDeferredUpdate:(NSRect)frame;
+// Notify the RenderWidgetHost that the frame was updated so it can resize
+// its contents.
+- (void)renderWidgetHostWasResized;
+// Cancel ongoing composition (abandon the marked text).
+- (void)cancelComposition;
+// Confirm ongoing composition.
+- (void)confirmComposition;
+// Enables or disables plugin IME.
+- (void)setPluginImeActive:(BOOL)active;
+// Updates the current plugin focus state.
+- (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId;
+// Evaluates the event in the context of plugin IME, if plugin IME is enabled.
+// Returns YES if the event was handled.
+- (BOOL)postProcessEventForPluginIme:(NSEvent*)event;
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewMac
+//
+// An object representing the "View" of a rendered web page. This object is
+// responsible for displaying the content of the web page, and integrating with
+// the Cocoa view system. It is the implementation of the RenderWidgetHostView
+// that the cross-platform RenderWidgetHost object uses
+// to display the data.
+//
+// Comment excerpted from render_widget_host.h:
+//
+// "The lifetime of the RenderWidgetHost* is tied to the render process.
+// If the render process dies, the RenderWidgetHost* goes away and all
+// references to it must become NULL."
+//
+class RenderWidgetHostViewMac : public RenderWidgetHostView {
+ public:
+ // The view will associate itself with the given widget. The native view must
+ // be hooked up immediately to the view hierarchy, or else when it is
+ // deleted it will delete this out from under the caller.
+ explicit RenderWidgetHostViewMac(RenderWidgetHost* widget);
+ virtual ~RenderWidgetHostViewMac();
+
+ RenderWidgetHostViewCocoa* native_view() const { return cocoa_view_; }
+
+ void SetDelegate(RenderWidgetHostViewMacDelegate* delegate);
+
+ // Implementation of RenderWidgetHostView:
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE;
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE;
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void DidBecomeSelected() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE;
+ virtual gfx::NativeView GetNativeView() const OVERRIDE;
+ virtual gfx::NativeViewId GetNativeViewId() const OVERRIDE;
+ virtual void MovePluginWindows(
+ const std::vector<webkit::npapi::WebPluginGeometry>& moves) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual bool HasFocus() OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE;
+ virtual void SetIsLoading(bool is_loading) OVERRIDE;
+ virtual void ImeUpdateTextInputState(ui::TextInputType state,
+ bool can_compose_inline,
+ const gfx::Rect& caret_rect) OVERRIDE;
+ virtual void ImeCancelComposition() OVERRIDE;
+ virtual void ImeCompositionRangeChanged(const ui::Range& range) OVERRIDE;
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy,
+ const std::vector<gfx::Rect>& copy_rects) OVERRIDE;
+ virtual void RenderViewGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+ virtual void SetTooltipText(const string16& tooltip_text) OVERRIDE;
+ virtual void SelectionChanged(const std::string& text,
+ const ui::Range& range,
+ const gfx::Point& start,
+ const gfx::Point& end) OVERRIDE;
+ virtual void ShowingContextMenu(bool showing) OVERRIDE;
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void SetTakesFocusOnlyOnMouseDown(bool flag) OVERRIDE;
+ // See comment in RenderWidgetHostView!
+ virtual gfx::Rect GetViewCocoaBounds() const OVERRIDE;
+ virtual void SetActive(bool active) OVERRIDE;
+ virtual void SetWindowVisibility(bool visible) OVERRIDE;
+ virtual void WindowFrameChanged() OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+
+ virtual void OnAccessibilityNotifications(
+ const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params
+ ) OVERRIDE;
+
+ virtual void PluginFocusChanged(bool focused, int plugin_id) OVERRIDE;
+ virtual void StartPluginIme() OVERRIDE;
+ virtual bool PostProcessEventForPluginIme(
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+
+ // Methods associated with GPU-accelerated plug-in instances and the
+ // accelerated compositor.
+ virtual gfx::PluginWindowHandle AllocateFakePluginWindowHandle(
+ bool opaque, bool root) OVERRIDE;
+ virtual void DestroyFakePluginWindowHandle(
+ gfx::PluginWindowHandle window) OVERRIDE;
+
+ // Exposed for testing.
+ AcceleratedPluginView* ViewForPluginWindowHandle(
+ gfx::PluginWindowHandle window);
+
+ // Helper to do the actual cleanup after a plugin handle has been destroyed.
+ // Required because DestroyFakePluginWindowHandle() isn't always called for
+ // all handles (it's e.g. not called on navigation, when the RWHVMac gets
+ // destroyed anyway).
+ void DeallocFakePluginWindowHandle(gfx::PluginWindowHandle window);
+
+ virtual void AcceleratedSurfaceSetIOSurface(
+ gfx::PluginWindowHandle window,
+ int32 width,
+ int32 height,
+ uint64 io_surface_identifier) OVERRIDE;
+ virtual void AcceleratedSurfaceSetTransportDIB(
+ gfx::PluginWindowHandle window,
+ int32 width,
+ int32 height,
+ TransportDIB::Handle transport_dib) OVERRIDE;
+ virtual void AcceleratedSurfaceBuffersSwapped(
+ gfx::PluginWindowHandle window,
+ uint64 surface_id,
+ int renderer_id,
+ int32 route_id,
+ int gpu_host_id,
+ uint64 swap_buffers_count) OVERRIDE;
+ virtual void GpuRenderingStateDidChange() OVERRIDE;
+ virtual void GetScreenInfo(WebKit::WebScreenInfo* results) OVERRIDE;
+ virtual gfx::Rect GetRootWindowBounds() OVERRIDE;
+ virtual gfx::PluginWindowHandle GetCompositingSurface() OVERRIDE;
+
+ // Returns |true| if a context menu is currently being shown.
+ bool is_showing_context_menu() const { return is_showing_context_menu_; }
+
+ void DrawAcceleratedSurfaceInstance(
+ CGLContextObj context,
+ gfx::PluginWindowHandle plugin_handle,
+ NSSize size);
+ // Forces the textures associated with any accelerated plugin instances
+ // to be reloaded.
+ void ForceTextureReload();
+
+ virtual void SetVisuallyDeemphasized(const SkColor* color, bool animate);
+
+ virtual void UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) OVERRIDE;
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE;
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE;
+
+ virtual bool LockMouse() OVERRIDE;
+ virtual void UnlockMouse() OVERRIDE;
+
+ void KillSelf();
+
+ void SetTextInputActive(bool active);
+
+ // Sends completed plugin IME notification and text back to the renderer.
+ void PluginImeCompositionCompleted(const string16& text, int plugin_id);
+
+ const std::string& selected_text() const { return selected_text_; }
+
+ void UpdateRootGpuViewVisibility(bool show_gpu_widget);
+
+ // When rendering transitions from gpu to software, the gpu widget can't be
+ // hidden until the software backing store has been updated. This method
+ // checks if the GPU view needs to be hidden and hides it if necessary. It
+ // should be called after the software backing store has been painted to.
+ void HandleDelayedGpuViewHiding();
+
+ // This is called from the display link thread, and provides the GPU
+ // process a notion of how quickly the browser is able to keep up with it.
+ void AcknowledgeSwapBuffers(int renderer_id,
+ int32 route_id,
+ int gpu_host_id,
+ uint64 swap_buffers_count);
+
+ // These member variables should be private, but the associated ObjC class
+ // needs access to them and can't be made a friend.
+
+ // The associated Model. Can be NULL if Destroy() is called when
+ // someone (other than superview) has retained |cocoa_view_|.
+ RenderWidgetHost* render_widget_host_;
+
+ // This is true when we are currently painting and thus should handle extra
+ // paint requests by expanding the invalid rect rather than actually painting.
+ bool about_to_validate_and_paint_;
+
+ scoped_ptr<BrowserAccessibilityManager> browser_accessibility_manager_;
+
+ // This is true when we have already scheduled a call to
+ // |-callSetNeedsDisplayInRect:| but it has not been fulfilled yet. Used to
+ // prevent us from scheduling multiple calls.
+ bool call_set_needs_display_in_rect_pending_;
+
+ // The invalid rect that needs to be painted by callSetNeedsDisplayInRect.
+ // This value is only meaningful when
+ // |call_set_needs_display_in_rect_pending_| is true.
+ NSRect invalid_rect_;
+
+ // The time at which this view started displaying white pixels as a result of
+ // not having anything to paint (empty backing store from renderer). This
+ // value returns true for is_null() if we are not recording whiteout times.
+ base::TimeTicks whiteout_start_time_;
+
+ // The time it took after this view was selected for it to be fully painted.
+ base::TimeTicks tab_switch_paint_time_;
+
+ // Current text input type.
+ ui::TextInputType text_input_type_;
+
+ typedef std::map<gfx::PluginWindowHandle, AcceleratedPluginView*>
+ PluginViewMap;
+ PluginViewMap plugin_views_; // Weak values.
+
+ // Helper class for managing instances of accelerated plug-ins.
+ AcceleratedSurfaceContainerManagerMac plugin_container_manager_;
+
+ private:
+ // Returns whether this render view is a popup (autocomplete window).
+ bool IsPopup() const;
+
+ // Updates the display cursor if the current event is over the view's window.
+ void UpdateCursorIfNecessary();
+
+ // Shuts down the render_widget_host_. This is a separate function so we can
+ // invoke it from the message loop.
+ void ShutdownHost();
+
+ // The associated view. This is weak and is inserted into the view hierarchy
+ // to own this RenderWidgetHostViewMac object.
+ RenderWidgetHostViewCocoa* cocoa_view_;
+
+ // The cursor for the page. This is passed up from the renderer.
+ WebCursor current_cursor_;
+
+ // Indicates if the page is loading.
+ bool is_loading_;
+
+ // true if the View is not visible.
+ bool is_hidden_;
+
+ // Whether we are showing a context menu.
+ bool is_showing_context_menu_;
+
+ // The text to be shown in the tooltip, supplied by the renderer.
+ string16 tooltip_text_;
+
+ // Factory used to safely scope delayed calls to ShutdownHost().
+ ScopedRunnableMethodFactory<RenderWidgetHostViewMac> shutdown_factory_;
+
+ // selected text on the renderer.
+ std::string selected_text_;
+
+ // When rendering transitions from gpu to software, the gpu widget can't be
+ // hidden until the software backing store has been updated. This variable is
+ // set when the gpu widget needs to be hidden once a paint is completed.
+ bool needs_gpu_visibility_update_after_repaint_;
+
+ gfx::PluginWindowHandle compositing_surface_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMac);
+};
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
new file mode 100644
index 0000000..8f720a3
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -0,0 +1,2740 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+#include <QuartzCore/QuartzCore.h>
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#import "base/mac/scoped_nsautorelease_pool.h"
+#include "base/metrics/histogram.h"
+#import "base/memory/scoped_nsobject.h"
+#include "base/string_util.h"
+#include "base/sys_info.h"
+#include "base/sys_string_conversions.h"
+#import "content/browser/accessibility/browser_accessibility_cocoa.h"
+#include "content/browser/browser_thread.h"
+#include "content/browser/gpu/gpu_process_host.h"
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/browser/mac/closure_blocks_leopard_compat.h"
+#include "content/browser/plugin_process_host.h"
+#import "content/browser/renderer_host/accelerated_plugin_view_mac.h"
+#include "content/browser/renderer_host/backing_store_mac.h"
+#include "content/browser/renderer_host/render_process_host.h"
+#include "content/browser/renderer_host/render_view_host.h"
+#import "content/browser/renderer_host/render_widget_host_view_mac_delegate.h"
+#import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
+#import "content/browser/renderer_host/text_input_client_mac.h"
+#include "content/common/edit_command.h"
+#include "content/common/gpu/gpu_messages.h"
+#include "content/common/native_web_keyboard_event.h"
+#include "content/common/plugin_messages.h"
+#include "content/common/view_messages.h"
+#include "skia/ext/platform_canvas.h"
+#import "third_party/mozilla/ComplexTextInputPanel.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebInputEventFactory.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebScreenInfoFactory.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
+#include "ui/gfx/surface/io_surface_support_mac.h"
+#include "webkit/glue/webaccessibility.h"
+#include "webkit/plugins/npapi/webplugin.h"
+
+using WebKit::WebInputEvent;
+using WebKit::WebInputEventFactory;
+using WebKit::WebMouseEvent;
+using WebKit::WebMouseWheelEvent;
+using WebKit::WebGestureEvent;
+
+// Declare things that are part of the 10.6 SDK.
+#if !defined(MAC_OS_X_VERSION_10_6) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
+enum {
+ NSEventTypeBeginGesture = 19,
+ NSEventTypeEndGesture = 20
+};
+
+enum {
+ NSEventMaskBeginGesture = 1 << NSEventTypeBeginGesture,
+ NSEventMaskEndGesture = 1 << NSEventTypeEndGesture,
+};
+
+typedef unsigned long long NSEventMask;
+
+@class NSTextInputContext;
+@interface NSResponder (AppKitDetails)
+- (NSTextInputContext*)inputContext;
+@end
+#endif // 10.6
+
+// Declare things that are part of the 10.7 SDK.
+#if !defined(MAC_OS_X_VERSION_10_7) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
+@interface NSEvent (LionAPI)
++ (id)addLocalMonitorForEventsMatchingMask:(NSEventMask)mask
+ handler:(NSEvent* (^)(NSEvent*))block;
++ (void)removeMonitor:(id)eventMonitor;
+@end
+#endif // 10.7
+
+static inline int ToWebKitModifiers(NSUInteger flags) {
+ int modifiers = 0;
+ if (flags & NSControlKeyMask) modifiers |= WebInputEvent::ControlKey;
+ if (flags & NSShiftKeyMask) modifiers |= WebInputEvent::ShiftKey;
+ if (flags & NSAlternateKeyMask) modifiers |= WebInputEvent::AltKey;
+ if (flags & NSCommandKeyMask) modifiers |= WebInputEvent::MetaKey;
+ return modifiers;
+}
+
+// Private methods:
+@interface RenderWidgetHostViewCocoa ()
+@property(nonatomic, assign) NSRange selectedRange;
+@property(nonatomic, assign) NSRange markedRange;
+
++ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
+- (void)setRWHVDelegate:(RenderWidgetHostViewMacDelegate*)delegate;
+- (void)gotUnhandledWheelEvent;
+- (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right;
+- (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar;
+- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
+- (void)cancelChildPopups;
+- (void)checkForPluginImeCancellation;
+@end
+
+// NSEvent subtype for scroll gestures events.
+static const short kIOHIDEventTypeScroll = 6;
+
+namespace {
+
+// Maximum number of characters we allow in a tooltip.
+const size_t kMaxTooltipLength = 1024;
+
+// TODO(suzhe): Upstream this function.
+WebKit::WebColor WebColorFromNSColor(NSColor *color) {
+ CGFloat r, g, b, a;
+ [color getRed:&r green:&g blue:&b alpha:&a];
+
+ return
+ std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
+ std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
+ std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
+ std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
+}
+
+// Extract underline information from an attributed string. Mostly copied from
+// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
+void ExtractUnderlines(
+ NSAttributedString* string,
+ std::vector<WebKit::WebCompositionUnderline>* underlines) {
+ int length = [[string string] length];
+ int i = 0;
+ while (i < length) {
+ NSRange range;
+ NSDictionary* attrs = [string attributesAtIndex:i
+ longestEffectiveRange:&range
+ inRange:NSMakeRange(i, length - i)];
+ if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
+ WebKit::WebColor color = SK_ColorBLACK;
+ if (NSColor *colorAttr =
+ [attrs objectForKey:NSUnderlineColorAttributeName]) {
+ color = WebColorFromNSColor(
+ [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
+ }
+ underlines->push_back(WebKit::WebCompositionUnderline(
+ range.location, NSMaxRange(range), color, [style intValue] > 1));
+ }
+ i = range.location + range.length;
+ }
+}
+
+// EnablePasswordInput() and DisablePasswordInput() are copied from
+// enableSecureTextInput() and disableSecureTextInput() functions in
+// third_party/WebKit/WebCore/platform/SecureTextInput.cpp
+// But we don't call EnableSecureEventInput() and DisableSecureEventInput()
+// here, because they are already called in webkit and they are system wide
+// functions.
+void EnablePasswordInput() {
+ CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
+ TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
+ sizeof(CFArrayRef), &inputSources);
+ CFRelease(inputSources);
+}
+
+void DisablePasswordInput() {
+ TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
+}
+
+// Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
+// left of the primary screen (Carbon coordinates), and stuffs it into a
+// gfx::Rect.
+gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
+ gfx::Rect new_rect(NSRectToCGRect(rect));
+ if ([[NSScreen screens] count] > 0) {
+ new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height -
+ new_rect.y() - new_rect.height());
+ }
+ return new_rect;
+}
+
+// Returns the window that visually contains the given view. This is different
+// from [view window] in the case of tab dragging, where the view's owning
+// window is a floating panel attached to the actual browser window that the tab
+// is visually part of.
+NSWindow* ApparentWindowForView(NSView* view) {
+ // TODO(shess): In case of !window, the view has been removed from
+ // the view hierarchy because the tab isn't main. Could retrieve
+ // the information from the main tab for our window.
+ NSWindow* enclosing_window = [view window];
+
+ // See if this is a tab drag window. The width check is to distinguish that
+ // case from extension popup windows.
+ NSWindow* ancestor_window = [enclosing_window parentWindow];
+ if (ancestor_window && (NSWidth([enclosing_window frame]) ==
+ NSWidth([ancestor_window frame]))) {
+ enclosing_window = ancestor_window;
+ }
+
+ return enclosing_window;
+}
+
+} // namespace
+
+// RenderWidgetHostView --------------------------------------------------------
+
+// static
+RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
+ RenderWidgetHost* widget) {
+ return new RenderWidgetHostViewMac(widget);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewMac, public:
+
+RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
+ : render_widget_host_(widget),
+ about_to_validate_and_paint_(false),
+ call_set_needs_display_in_rect_pending_(false),
+ text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
+ is_loading_(false),
+ is_hidden_(false),
+ is_showing_context_menu_(false),
+ shutdown_factory_(this),
+ needs_gpu_visibility_update_after_repaint_(false),
+ compositing_surface_(gfx::kNullPluginWindow) {
+ // |cocoa_view_| owns us and we will be deleted when |cocoa_view_| goes away.
+ // Since we autorelease it, our caller must put |native_view()| into the view
+ // hierarchy right after calling us.
+ cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
+ initWithRenderWidgetHostViewMac:this] autorelease];
+ render_widget_host_->SetView(this);
+}
+
+RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
+}
+
+void RenderWidgetHostViewMac::SetDelegate(
+ RenderWidgetHostViewMacDelegate* delegate) {
+ [cocoa_view_ setRWHVDelegate:delegate];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RenderWidgetHostViewMac, RenderWidgetHostView implementation:
+
+void RenderWidgetHostViewMac::InitAsPopup(
+ RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) {
+ bool activatable = popup_type_ == WebKit::WebPopupTypeNone;
+ [cocoa_view_ setCloseOnDeactivate:YES];
+ [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
+ [parent_host_view->GetNativeView() addSubview:cocoa_view_];
+
+ NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
+ if ([[NSScreen screens] count] > 0) {
+ origin_global.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height -
+ pos.height() - origin_global.y;
+ }
+ NSPoint origin_window =
+ [[cocoa_view_ window] convertScreenToBase:origin_global];
+ NSPoint origin_view =
+ [cocoa_view_ convertPoint:origin_window fromView:nil];
+ NSRect initial_frame = NSMakeRect(origin_view.x,
+ origin_view.y,
+ pos.width(),
+ pos.height());
+ [cocoa_view_ setFrame:initial_frame];
+}
+
+void RenderWidgetHostViewMac::InitAsFullscreen(
+ RenderWidgetHostView* /*reference_host_view*/) {
+ NOTIMPLEMENTED() << "Full screen not implemented on Mac";
+}
+
+RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
+ return render_widget_host_;
+}
+
+void RenderWidgetHostViewMac::DidBecomeSelected() {
+ if (!is_hidden_)
+ return;
+
+ if (tab_switch_paint_time_.is_null())
+ tab_switch_paint_time_ = base::TimeTicks::Now();
+ is_hidden_ = false;
+ render_widget_host_->WasRestored();
+}
+
+void RenderWidgetHostViewMac::WasHidden() {
+ if (is_hidden_)
+ return;
+
+ // If we receive any more paint messages while we are hidden, we want to
+ // ignore them so we don't re-allocate the backing store. We will paint
+ // everything again when we become selected again.
+ is_hidden_ = true;
+
+ // If we have a renderer, then inform it that we are being hidden so it can
+ // reduce its resource utilization.
+ render_widget_host_->WasHidden();
+}
+
+void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
+ gfx::Rect rect = GetViewBounds();
+ rect.set_size(size);
+ SetBounds(rect);
+}
+
+void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
+ // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
+ // TODO(thakis): fix, http://crbug.com/73362
+ if (is_hidden_)
+ return;
+
+ // During the initial creation of the RenderWidgetHostView in
+ // TabContents::CreateRenderViewForRenderManager, SetSize is called with an
+ // empty size. In the Windows code flow, it is not ignored because subsequent
+ // sizing calls from the OS flow through TCVW::WasSized which calls SetSize()
+ // again. On Cocoa, we rely on the Cocoa view struture and resizer flags to
+ // keep things sized properly. On the other hand, if the size is not empty
+ // then this is a valid request for a pop-up.
+ if (rect.size().IsEmpty())
+ return;
+
+ // Ignore the position of |rect| for non-popup rwhvs. This is because
+ // background tabs do not have a window, but the window is required for the
+ // coordinate conversions. Popups are always for a visible tab.
+ if (IsPopup()) {
+ // The position of |rect| is screen coordinate system and we have to
+ // consider Cocoa coordinate system is upside-down and also multi-screen.
+ NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
+ if ([[NSScreen screens] count] > 0) {
+ NSSize size = NSMakeSize(rect.width(), rect.height());
+ size = [cocoa_view_ convertSize:size toView:nil];
+ NSScreen* screen =
+ static_cast<NSScreen*>([[NSScreen screens] objectAtIndex:0]);
+ origin_global.y =
+ NSHeight([screen frame]) - size.height - origin_global.y;
+ }
+
+ // Then |origin_global| is converted to client coordinate system.
+ DCHECK([cocoa_view_ window]);
+ NSPoint origin_window =
+ [[cocoa_view_ window] convertScreenToBase:origin_global];
+ NSPoint origin_view =
+ [[cocoa_view_ superview] convertPoint:origin_window fromView:nil];
+ NSRect frame = NSMakeRect(origin_view.x, origin_view.y,
+ rect.width(), rect.height());
+ [cocoa_view_ setFrame:frame];
+ } else {
+ DCHECK([[cocoa_view_ superview] isKindOfClass:[BaseView class]]);
+ BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
+ gfx::Rect rect = [superview flipNSRectToRect:[cocoa_view_ frame]];
+ rect.set_width(rect.width());
+ rect.set_height(rect.height());
+ [cocoa_view_ setFrame:[superview flipRectToNSRect:rect]];
+ }
+}
+
+gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
+ return native_view();
+}
+
+gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
+ return reinterpret_cast<gfx::NativeViewId>(native_view());
+}
+
+void RenderWidgetHostViewMac::MovePluginWindows(
+ const std::vector<webkit::npapi::WebPluginGeometry>& moves) {
+ TRACE_EVENT0("browser", "RenderWidgetHostViewMac::MovePluginWindows");
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Handle movement of accelerated plugins, which are the only "windowed"
+ // plugins that exist on the Mac.
+ for (std::vector<webkit::npapi::WebPluginGeometry>::const_iterator iter =
+ moves.begin();
+ iter != moves.end();
+ ++iter) {
+ webkit::npapi::WebPluginGeometry geom = *iter;
+
+ AcceleratedPluginView* view = ViewForPluginWindowHandle(geom.window);
+ DCHECK(view);
+ if (!view)
+ continue;
+
+ if (geom.rects_valid) {
+ gfx::Rect rect = geom.window_rect;
+ if (geom.visible) {
+ rect.set_x(rect.x() + geom.clip_rect.x());
+ rect.set_y(rect.y() + geom.clip_rect.y());
+ rect.set_width(geom.clip_rect.width());
+ rect.set_height(geom.clip_rect.height());
+ }
+ NSRect new_rect([cocoa_view_ flipRectToNSRect:rect]);
+ [view setFrame:new_rect];
+ NSMutableArray* cutout_rects =
+ [NSMutableArray arrayWithCapacity:geom.cutout_rects.size()];
+ for (unsigned int i = 0; i < geom.cutout_rects.size(); ++i) {
+ // Convert to NSRect, and flip vertically.
+ NSRect cutout_rect = NSRectFromCGRect(geom.cutout_rects[i].ToCGRect());
+ cutout_rect.origin.y = new_rect.size.height - NSMaxY(cutout_rect);
+ [cutout_rects addObject:[NSValue valueWithRect:cutout_rect]];
+ }
+ [view setCutoutRects:cutout_rects];
+ [view setNeedsDisplay:YES];
+ }
+
+ plugin_container_manager_.SetPluginContainerGeometry(geom);
+
+ BOOL visible =
+ plugin_container_manager_.SurfaceShouldBeVisible(geom.window);
+ [view setHidden:!visible];
+ }
+}
+
+void RenderWidgetHostViewMac::Focus() {
+ [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
+}
+
+void RenderWidgetHostViewMac::Blur() {
+ [[cocoa_view_ window] makeFirstResponder:nil];
+}
+
+bool RenderWidgetHostViewMac::HasFocus() {
+ return [[cocoa_view_ window] firstResponder] == cocoa_view_;
+}
+
+void RenderWidgetHostViewMac::Show() {
+ [cocoa_view_ setHidden:NO];
+
+ DidBecomeSelected();
+}
+
+void RenderWidgetHostViewMac::Hide() {
+ [cocoa_view_ setHidden:YES];
+
+ WasHidden();
+}
+
+bool RenderWidgetHostViewMac::IsShowing() {
+ return ![cocoa_view_ isHidden];
+}
+
+gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
+ // TODO(shess): In case of !window, the view has been removed from
+ // the view hierarchy because the tab isn't main. Could retrieve
+ // the information from the main tab for our window.
+ NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
+ if (!enclosing_window)
+ return gfx::Rect();
+
+ NSRect bounds = [cocoa_view_ bounds];
+ bounds = [cocoa_view_ convertRect:bounds toView:nil];
+ bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
+ return FlipNSRectToRectScreen(bounds);
+}
+
+void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
+ current_cursor_ = cursor;
+ UpdateCursorIfNecessary();
+}
+
+void RenderWidgetHostViewMac::UpdateCursorIfNecessary() {
+ // Do something special (as Win Chromium does) for arrow cursor while loading
+ // a page? TODO(avi): decide
+
+ // Don't update the cursor if a context menu is being shown.
+ if (is_showing_context_menu_)
+ return;
+
+ // Can we synchronize to the event stream? Switch to -[NSWindow
+ // mouseLocationOutsideOfEventStream] if we cannot. TODO(avi): test and see
+ NSEvent* event = [[cocoa_view_ window] currentEvent];
+ if ([event window] != [cocoa_view_ window])
+ return;
+
+ NSCursor* ns_cursor = current_cursor_.GetCursor();
+ [ns_cursor set];
+}
+
+void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
+ is_loading_ = is_loading;
+ // If we ever decide to show the waiting cursor while the page is loading
+ // like Chrome does on Windows, call |UpdateCursorIfNecessary()| here.
+}
+
+void RenderWidgetHostViewMac::ImeUpdateTextInputState(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ const gfx::Rect& caret_rect) {
+ // TODO(kinaba): currently, can_compose_inline is ignored and always treated
+ // as true. We need to support "can_compose_inline=false" for PPAPI plugins
+ // that may want to avoid drawing composition-text by themselves and pass
+ // the responsibility to the browser.
+ if (text_input_type_ != type) {
+ text_input_type_ = type;
+ if (HasFocus()) {
+ SetTextInputActive(true);
+
+ // Let AppKit cache the new input context to make IMEs happy.
+ // See http://crbug.com/73039.
+ [NSApp updateWindows];
+ }
+ }
+}
+
+void RenderWidgetHostViewMac::ImeCancelComposition() {
+ [cocoa_view_ cancelComposition];
+}
+
+void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
+ const ui::Range& range) {
+ // The RangeChanged message is only sent with valid values. The current
+ // caret position (start == end) will be sent if there is no IME range.
+ [cocoa_view_ setMarkedRange:range.ToNSRange()];
+}
+
+void RenderWidgetHostViewMac::DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy,
+ const std::vector<gfx::Rect>& copy_rects) {
+ if (!is_hidden_) {
+ std::vector<gfx::Rect> rects(copy_rects);
+
+ // Because the findbar might be open, we cannot use scrollRect:by: here. For
+ // now, simply mark all of scroll rect as dirty.
+ if (!scroll_rect.IsEmpty())
+ rects.push_back(scroll_rect);
+
+ for (size_t i = 0; i < rects.size(); ++i) {
+ NSRect ns_rect = [cocoa_view_ flipRectToNSRect:rects[i]];
+
+ if (about_to_validate_and_paint_) {
+ // As much as we'd like to use -setNeedsDisplayInRect: here, we can't.
+ // We're in the middle of executing a -drawRect:, and as soon as it
+ // returns Cocoa will clear its record of what needs display. We
+ // instead use |performSelector:| to call |setNeedsDisplayInRect:|
+ // after returning to the main loop, at which point |drawRect:| is no
+ // longer on the stack.
+ DCHECK([NSThread isMainThread]);
+ if (!call_set_needs_display_in_rect_pending_) {
+ [cocoa_view_ performSelector:@selector(callSetNeedsDisplayInRect)
+ withObject:nil
+ afterDelay:0];
+ call_set_needs_display_in_rect_pending_ = true;
+ invalid_rect_ = ns_rect;
+ } else {
+ // The old invalid rect is probably invalid now, since the view has
+ // most likely been resized, but there's no harm in dirtying the
+ // union. In the limit, this becomes equivalent to dirtying the
+ // whole view.
+ invalid_rect_ = NSUnionRect(invalid_rect_, ns_rect);
+ }
+ } else {
+ [cocoa_view_ setNeedsDisplayInRect:ns_rect];
+ }
+ }
+
+ if (!about_to_validate_and_paint_)
+ [cocoa_view_ displayIfNeeded];
+ }
+
+ // If |about_to_validate_and_paint_| is set, then -drawRect: is on the stack
+ // and it's not allowed to call -setHidden on the accelerated view. In that
+ // case, -callSetNeedsDisplayInRect: will hide it later.
+ // If |about_to_validate_and_paint_| is not set, do it now.
+ if (!about_to_validate_and_paint_)
+ HandleDelayedGpuViewHiding();
+}
+
+void RenderWidgetHostViewMac::RenderViewGone(base::TerminationStatus status,
+ int error_code) {
+ // TODO(darin): keep this around, and draw sad-tab into it.
+ Destroy();
+}
+
+void RenderWidgetHostViewMac::Destroy() {
+ // On Windows, popups are implemented with a popup window style, so that when
+ // an event comes in that would "cancel" it, it receives the OnCancelMode
+ // message and can kill itself. Alas, on the Mac, views cannot capture events
+ // outside of themselves. On Windows, if Destroy is being called on a view,
+ // then the event causing the destroy had also cancelled any popups by the
+ // time Destroy() was called. On the Mac we have to destroy all the popups
+ // ourselves.
+
+ // Depth-first destroy all popups. Use ShutdownHost() to enforce
+ // deepest-first ordering.
+ for (NSView* subview in [cocoa_view_ subviews]) {
+ if ([subview isKindOfClass:[RenderWidgetHostViewCocoa class]]) {
+ [static_cast<RenderWidgetHostViewCocoa*>(subview)
+ renderWidgetHostViewMac]->ShutdownHost();
+ } else if ([subview isKindOfClass:[AcceleratedPluginView class]]) {
+ [static_cast<AcceleratedPluginView*>(subview)
+ onRenderWidgetHostViewGone];
+ }
+ }
+
+ // We've been told to destroy.
+ [cocoa_view_ retain];
+ [cocoa_view_ removeFromSuperview];
+ [cocoa_view_ autorelease];
+
+ // We get this call just before |render_widget_host_| deletes
+ // itself. But we are owned by |cocoa_view_|, which may be retained
+ // by some other code. Examples are TabContentsViewMac's
+ // |latent_focus_view_| and TabWindowController's
+ // |cachedContentView_|.
+ render_widget_host_ = NULL;
+}
+
+// Called from the renderer to tell us what the tooltip text should be. It
+// calls us frequently so we need to cache the value to prevent doing a lot
+// of repeat work.
+void RenderWidgetHostViewMac::SetTooltipText(const string16& tooltip_text) {
+ if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
+ tooltip_text_ = tooltip_text;
+
+ // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
+ // Windows; we're just trying to be polite. Don't persist the trimmed
+ // string, as then the comparison above will always fail and we'll try to
+ // set it again every single time the mouse moves.
+ string16 display_text = tooltip_text_;
+ if (tooltip_text_.length() > kMaxTooltipLength)
+ display_text = tooltip_text_.substr(0, kMaxTooltipLength);
+
+ NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
+ [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
+ }
+}
+
+//
+// RenderWidgetHostViewCocoa uses the stored selection text,
+// which implements NSServicesRequests protocol.
+//
+void RenderWidgetHostViewMac::SelectionChanged(const std::string& text,
+ const ui::Range& range,
+ const gfx::Point& start,
+ const gfx::Point& end) {
+ selected_text_ = text;
+ [cocoa_view_ setSelectedRange:range.ToNSRange()];
+ // Updaes markedRange when there is no marked text so that retrieving
+ // markedRange immediately after calling setMarkdText: returns the current
+ // caret position.
+ if (![cocoa_view_ hasMarkedText]) {
+ [cocoa_view_ setMarkedRange:range.ToNSRange()];
+ }
+}
+
+void RenderWidgetHostViewMac::ShowingContextMenu(bool showing) {
+ DCHECK_NE(is_showing_context_menu_, showing);
+ is_showing_context_menu_ = showing;
+
+ // If the menu was closed, restore the cursor to the saved version initially,
+ // as the renderer will not re-send it if there was no change.
+ if (!showing)
+ UpdateCursorIfNecessary();
+
+ // Create a fake mouse event to inform the render widget that the mouse
+ // left or entered.
+ NSWindow* window = [cocoa_view_ window];
+ // TODO(asvitkine): If the location outside of the event stream doesn't
+ // correspond to the current event (due to delayed event processing), then
+ // this may result in a cursor flicker if there are later mouse move events
+ // in the pipeline. Find a way to use the mouse location from the event that
+ // dismissed the context menu.
+ NSPoint location = [window mouseLocationOutsideOfEventStream];
+ NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
+ location:location
+ modifierFlags:0
+ timestamp:0
+ windowNumber:[window windowNumber]
+ context:nil
+ eventNumber:0
+ clickCount:0
+ pressure:0];
+ WebMouseEvent web_event =
+ WebInputEventFactory::mouseEvent(event, cocoa_view_);
+ if (showing)
+ web_event.type = WebInputEvent::MouseLeave;
+ render_widget_host_->ForwardMouseEvent(web_event);
+}
+
+bool RenderWidgetHostViewMac::IsPopup() const {
+ return popup_type_ != WebKit::WebPopupTypeNone;
+}
+
+BackingStore* RenderWidgetHostViewMac::AllocBackingStore(
+ const gfx::Size& size) {
+ return new BackingStoreMac(render_widget_host_, size);
+}
+
+// Sets whether or not to accept first responder status.
+void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
+ [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
+}
+
+void RenderWidgetHostViewMac::KillSelf() {
+ if (shutdown_factory_.empty()) {
+ [cocoa_view_ setHidden:YES];
+ MessageLoop::current()->PostTask(FROM_HERE,
+ shutdown_factory_.NewRunnableMethod(
+ &RenderWidgetHostViewMac::ShutdownHost));
+ }
+}
+
+void RenderWidgetHostViewMac::PluginFocusChanged(bool focused, int plugin_id) {
+ [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
+}
+
+void RenderWidgetHostViewMac::StartPluginIme() {
+ [cocoa_view_ setPluginImeActive:YES];
+}
+
+bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
+ const NativeWebKeyboardEvent& event) {
+ // Check WebInputEvent type since multiple types of events can be sent into
+ // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
+ // necessary to avoid double processing.
+ // Also check the native type, since NSFlagsChanged is considered a key event
+ // for WebKit purposes, but isn't considered a key event by the OS.
+ if (event.type == WebInputEvent::RawKeyDown &&
+ [event.os_event type] == NSKeyDown)
+ return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
+ return false;
+}
+
+void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
+ const string16& text, int plugin_id) {
+ if (render_widget_host_) {
+ render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
+ render_widget_host_->routing_id(), text, plugin_id));
+ }
+}
+
+gfx::PluginWindowHandle
+RenderWidgetHostViewMac::AllocateFakePluginWindowHandle(bool opaque,
+ bool root) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // |render_widget_host_| is set to NULL when |RWHVMac::Destroy()| has
+ // completed. If |AllocateFakePluginWindowHandle()| is called after that,
+ // we will crash when the AcceleratedPluginView we allocate below is
+ // destroyed.
+ DCHECK(render_widget_host_);
+
+ // Create an NSView to host the plugin's/compositor's pixels.
+ gfx::PluginWindowHandle handle =
+ plugin_container_manager_.AllocateFakePluginWindowHandle(opaque, root);
+
+ scoped_nsobject<AcceleratedPluginView> plugin_view(
+ [[AcceleratedPluginView alloc] initWithRenderWidgetHostViewMac:this
+ pluginHandle:handle]);
+ [plugin_view setHidden:YES];
+
+ [cocoa_view_ addSubview:plugin_view];
+ plugin_views_[handle] = plugin_view;
+
+ return handle;
+}
+
+void RenderWidgetHostViewMac::DestroyFakePluginWindowHandle(
+ gfx::PluginWindowHandle window) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ PluginViewMap::iterator it = plugin_views_.find(window);
+ DCHECK(plugin_views_.end() != it);
+ if (plugin_views_.end() == it) {
+ return;
+ }
+ [it->second removeFromSuperview];
+ plugin_views_.erase(it);
+
+ // The view's dealloc will call DeallocFakePluginWindowHandle(), which will
+ // remove the handle from |plugin_container_manager_|. This code path is
+ // taken if a plugin is removed, but the RWHVMac itself stays alive.
+}
+
+// This is called by AcceleratedPluginView's -dealloc.
+void RenderWidgetHostViewMac::DeallocFakePluginWindowHandle(
+ gfx::PluginWindowHandle window) {
+ // When a browser window with a GpuScheduler is closed, the render process
+ // will attempt to finish all GL commands. It will busy-wait on the GPU
+ // process until the command queue is empty. If a paint is pending, the GPU
+ // process won't process any GL commands until the browser sends a paint ack,
+ // but since the browser window is already closed, it will never arrive.
+ // To resolve this we ask the GPU process to destroy the command buffer
+ // associated with the given render widget. Once the command buffer is
+ // destroyed, all GL commands from the renderer will immediately receive
+ // channel error.
+ if (render_widget_host_ &&
+ plugin_container_manager_.IsRootContainer(window)) {
+ GpuProcessHost::SendOnIO(
+ render_widget_host_->process()->id(),
+ content::CAUSE_FOR_GPU_LAUNCH_NO_LAUNCH,
+ new GpuMsg_DestroyCommandBuffer(
+ render_widget_host_->process()->id(),
+ render_widget_host_->routing_id()));
+ }
+
+ plugin_container_manager_.DestroyFakePluginWindowHandle(window);
+}
+
+AcceleratedPluginView* RenderWidgetHostViewMac::ViewForPluginWindowHandle(
+ gfx::PluginWindowHandle window) {
+ PluginViewMap::iterator it = plugin_views_.find(window);
+ DCHECK(plugin_views_.end() != it);
+ if (plugin_views_.end() == it)
+ return nil;
+ return it->second;
+}
+
+void RenderWidgetHostViewMac::AcceleratedSurfaceSetIOSurface(
+ gfx::PluginWindowHandle window,
+ int32 width,
+ int32 height,
+ uint64 io_surface_identifier) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ plugin_container_manager_.SetSizeAndIOSurface(window,
+ width,
+ height,
+ io_surface_identifier);
+
+ if (plugin_container_manager_.IsRootContainer(window)) {
+ // Fake up a WebPluginGeometry for the root window to set the
+ // container's size; we will never get a notification from the
+ // browser about the root window, only plugins.
+ webkit::npapi::WebPluginGeometry geom;
+ gfx::Rect rect(0, 0, width, height);
+ geom.window = window;
+ geom.window_rect = rect;
+ geom.clip_rect = rect;
+ geom.visible = true;
+ geom.rects_valid = true;
+ MovePluginWindows(std::vector<webkit::npapi::WebPluginGeometry>(1, geom));
+ }
+}
+
+void RenderWidgetHostViewMac::AcceleratedSurfaceSetTransportDIB(
+ gfx::PluginWindowHandle window,
+ int32 width,
+ int32 height,
+ TransportDIB::Handle transport_dib) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ plugin_container_manager_.SetSizeAndTransportDIB(window,
+ width,
+ height,
+ transport_dib);
+}
+
+void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
+ gfx::PluginWindowHandle window,
+ uint64 surface_id,
+ int renderer_id,
+ int32 route_id,
+ int gpu_host_id,
+ uint64 swap_buffers_count) {
+ TRACE_EVENT1("browser",
+ "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped",
+ "frameNum", swap_buffers_count);
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ AcceleratedPluginView* view = ViewForPluginWindowHandle(window);
+ DCHECK(view);
+ if (view) {
+ plugin_container_manager_.SetSurfaceWasPaintedTo(window, surface_id);
+
+ // The surface is hidden until its first paint, to not show gargabe.
+ if (plugin_container_manager_.SurfaceShouldBeVisible(window))
+ [view setHidden:NO];
+ [view drawView];
+ }
+
+ if (renderer_id != 0 || route_id != 0) {
+ AcknowledgeSwapBuffers(renderer_id,
+ route_id,
+ gpu_host_id,
+ swap_buffers_count);
+ }
+}
+
+void RenderWidgetHostViewMac::UpdateRootGpuViewVisibility(
+ bool show_gpu_widget) {
+ // Plugins are destroyed on page navigate. The compositor layer on the other
+ // hand is created on demand and then stays alive until its renderer process
+ // dies (usually on cross-domain navigation). Instead, only a flag
+ // |is_accelerated_compositing_active()| is flipped when the compositor output
+ // should be shown/hidden.
+ // Show/hide the view belonging to the compositor here.
+ plugin_container_manager_.set_gpu_rendering_active(show_gpu_widget);
+
+ gfx::PluginWindowHandle root_handle =
+ plugin_container_manager_.root_container_handle();
+ if (root_handle != gfx::kNullPluginWindow) {
+ AcceleratedPluginView* view = ViewForPluginWindowHandle(root_handle);
+ DCHECK(view);
+ bool visible =
+ plugin_container_manager_.SurfaceShouldBeVisible(root_handle);
+ [[view window] disableScreenUpdatesUntilFlush];
+ [view setHidden:!visible];
+ }
+}
+
+void RenderWidgetHostViewMac::HandleDelayedGpuViewHiding() {
+ if (needs_gpu_visibility_update_after_repaint_) {
+ UpdateRootGpuViewVisibility(false);
+ needs_gpu_visibility_update_after_repaint_ = false;
+ }
+}
+
+void RenderWidgetHostViewMac::AcknowledgeSwapBuffers(
+ int renderer_id,
+ int32 route_id,
+ int gpu_host_id,
+ uint64 swap_buffers_count) {
+ TRACE_EVENT1("gpu", "RenderWidgetHostViewMac::AcknowledgeSwapBuffers",
+ "swap_buffers_count", swap_buffers_count);
+ // Called on the display link thread. Hand actual work off to the IO thread,
+ // because |GpuProcessHost::Get()| can only be called there.
+ // Currently, this is never called for plugins.
+ if (render_widget_host_) {
+ DCHECK_EQ(render_widget_host_->process()->id(), renderer_id);
+ // |render_widget_host_->routing_id()| and |route_id| are usually not
+ // equal: The former identifies the channel from the RWH in the browser
+ // process to the corresponding render widget in the renderer process, while
+ // the latter identifies the channel from the GpuCommandBufferStub in the
+ // GPU process to the corresponding command buffer client in the renderer.
+ }
+
+ // TODO(apatrick): Send the acknowledgement via the UI thread when running in
+ // single process or in process GPU mode for now. This is bad from a
+ // performance point of view but the plan is to not use AcceleratedSurface at
+ // all in these cases.
+ if (gpu_host_id == 0) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ NewRunnableFunction(&GpuProcessHostUIShim::SendToGpuHost,
+ gpu_host_id,
+ new GpuMsg_AcceleratedSurfaceBuffersSwappedACK(
+ renderer_id,
+ route_id,
+ swap_buffers_count)));
+ } else {
+ GpuProcessHost::SendOnIO(
+ gpu_host_id,
+ content::CAUSE_FOR_GPU_LAUNCH_NO_LAUNCH,
+ new GpuMsg_AcceleratedSurfaceBuffersSwappedACK(
+ renderer_id, route_id, swap_buffers_count));
+ }
+}
+
+void RenderWidgetHostViewMac::GpuRenderingStateDidChange() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (GetRenderWidgetHost()->is_accelerated_compositing_active()) {
+ UpdateRootGpuViewVisibility(
+ GetRenderWidgetHost()->is_accelerated_compositing_active());
+ } else {
+ needs_gpu_visibility_update_after_repaint_ = true;
+ }
+}
+
+void RenderWidgetHostViewMac::GetScreenInfo(WebKit::WebScreenInfo* results) {
+ *results = WebKit::WebScreenInfoFactory::screenInfo(GetNativeView());
+}
+
+gfx::Rect RenderWidgetHostViewMac::GetRootWindowBounds() {
+ // TODO(shess): In case of !window, the view has been removed from
+ // the view hierarchy because the tab isn't main. Could retrieve
+ // the information from the main tab for our window.
+ NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
+ if (!enclosing_window)
+ return gfx::Rect();
+
+ NSRect bounds = [enclosing_window frame];
+ return FlipNSRectToRectScreen(bounds);
+}
+
+gfx::PluginWindowHandle RenderWidgetHostViewMac::GetCompositingSurface() {
+ if (compositing_surface_ == gfx::kNullPluginWindow)
+ compositing_surface_ = AllocateFakePluginWindowHandle(
+ /*opaque=*/true, /*root=*/true);
+ return compositing_surface_;
+}
+
+void RenderWidgetHostViewMac::DrawAcceleratedSurfaceInstance(
+ CGLContextObj context,
+ gfx::PluginWindowHandle plugin_handle,
+ NSSize size) {
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewMac::DrawAcceleratedSurfaceInstance");
+ // Called on the display link thread.
+ CGLSetCurrentContext(context);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ // Note that we place the origin at the upper left corner with +y
+ // going down
+ glOrtho(0, size.width, size.height, 0, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ plugin_container_manager_.Draw(context, plugin_handle);
+}
+
+void RenderWidgetHostViewMac::ForceTextureReload() {
+ plugin_container_manager_.ForceTextureReload();
+}
+
+void RenderWidgetHostViewMac::SetVisuallyDeemphasized(const SkColor* color,
+ bool animate) {
+ // This is not used on mac.
+}
+
+void RenderWidgetHostViewMac::UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) {
+ [cocoa_view_ gotUnhandledWheelEvent];
+}
+
+void RenderWidgetHostViewMac::SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) {
+ [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
+}
+
+void RenderWidgetHostViewMac::SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+ [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
+ toRight:is_pinned_to_right];
+}
+
+bool RenderWidgetHostViewMac::LockMouse() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void RenderWidgetHostViewMac::UnlockMouse() {
+ NOTIMPLEMENTED();
+}
+
+void RenderWidgetHostViewMac::ShutdownHost() {
+ shutdown_factory_.RevokeAll();
+ render_widget_host_->Shutdown();
+ // Do not touch any members at this point, |this| has been deleted.
+}
+
+gfx::Rect RenderWidgetHostViewMac::GetViewCocoaBounds() const {
+ return gfx::Rect(NSRectToCGRect([cocoa_view_ bounds]));
+}
+
+void RenderWidgetHostViewMac::SetActive(bool active) {
+ if (render_widget_host_) {
+ render_widget_host_->Send(new ViewMsg_SetActive(
+ render_widget_host_->routing_id(), active));
+ }
+ if (HasFocus())
+ SetTextInputActive(active);
+ if (!active)
+ [cocoa_view_ setPluginImeActive:NO];
+}
+
+void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
+ if (render_widget_host_) {
+ render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
+ render_widget_host_->routing_id(), visible));
+ }
+}
+
+void RenderWidgetHostViewMac::WindowFrameChanged() {
+ if (render_widget_host_) {
+ render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
+ render_widget_host_->routing_id(), GetRootWindowBounds(),
+ GetViewBounds()));
+ }
+}
+
+void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) {
+ RenderWidgetHostView::SetBackground(background);
+ if (render_widget_host_)
+ render_widget_host_->Send(new ViewMsg_SetBackground(
+ render_widget_host_->routing_id(), background));
+}
+
+void RenderWidgetHostViewMac::OnAccessibilityNotifications(
+ const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) {
+ if (!browser_accessibility_manager_.get()) {
+ browser_accessibility_manager_.reset(
+ BrowserAccessibilityManager::CreateEmptyDocument(
+ cocoa_view_, static_cast<WebAccessibility::State>(0), NULL));
+ }
+ browser_accessibility_manager_->OnAccessibilityNotifications(params);
+}
+
+void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
+ if (active) {
+ if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
+ EnablePasswordInput();
+ else
+ DisablePasswordInput();
+ } else {
+ if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
+ DisablePasswordInput();
+ }
+}
+
+// RenderWidgetHostViewCocoa ---------------------------------------------------
+
+@implementation RenderWidgetHostViewCocoa
+
+@synthesize selectedRange = selectedRange_;
+@synthesize markedRange = markedRange_;
+
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
+ self = [super initWithFrame:NSZeroRect];
+ if (self) {
+ editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
+ editCommand_helper_->AddEditingSelectorsToClass([self class]);
+
+ renderWidgetHostView_.reset(r);
+ canBeKeyView_ = YES;
+ focusedPluginIdentifier_ = -1;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (delegate_ && [delegate_ respondsToSelector:@selector(viewGone:)])
+ [delegate_ viewGone:self];
+
+ [super dealloc];
+}
+
+- (void)setRWHVDelegate:(RenderWidgetHostViewMacDelegate*)delegate {
+ delegate_ = delegate;
+}
+
+- (void)gotUnhandledWheelEvent {
+ if (delegate_ &&
+ [delegate_ respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
+ [delegate_ gotUnhandledWheelEvent];
+ }
+}
+
+- (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
+ if (delegate_ && [delegate_ respondsToSelector:
+ @selector(scrollOffsetPinnedToLeft:toRight:)]) {
+ [delegate_ scrollOffsetPinnedToLeft:left toRight:right];
+ }
+}
+
+- (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
+ if (delegate_ &&
+ [delegate_ respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
+ [delegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
+ }
+}
+
+- (BOOL)respondsToSelector:(SEL)selector {
+ // Trickiness: this doesn't mean "does this object's superclass respond to
+ // this selector" but rather "does the -respondsToSelector impl from the
+ // superclass say that this class responds to the selector".
+ if ([super respondsToSelector:selector])
+ return YES;
+
+ if (delegate_)
+ return [delegate_ respondsToSelector:selector];
+ else
+ return NO;
+}
+
+- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
+ // Trickiness: this doesn't mean "does this object's superclass respond to
+ // this selector" but rather "does the -respondsToSelector impl from the
+ // superclass say that this class responds to the selector".
+ if ([super respondsToSelector:selector])
+ return [super methodSignatureForSelector:selector];
+
+ if (delegate_)
+ return [delegate_ methodSignatureForSelector:selector];
+ else
+ return nil;
+}
+
+- (void)forwardInvocation:(NSInvocation*)invocation {
+ // TODO(avi): Oh, man, use -forwardingTargetForSelector: when 10.6 is the
+ // minimum requirement.
+
+ if (delegate_ && [delegate_ respondsToSelector:[invocation selector]])
+ [invocation invokeWithTarget:delegate_];
+ else
+ [super forwardInvocation:invocation];
+}
+
+- (void)setCanBeKeyView:(BOOL)can {
+ canBeKeyView_ = can;
+}
+
+- (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
+ takesFocusOnlyOnMouseDown_ = b;
+}
+
+- (void)setCloseOnDeactivate:(BOOL)b {
+ closeOnDeactivate_ = b;
+}
+
+- (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
+ NSWindow* window = [self window];
+ // If this is a background window, don't handle mouse movement events. This
+ // is the expected behavior on the Mac as evidenced by other applications.
+ // Do this only if the window level is NSNormalWindowLevel, as this
+ // does not necessarily apply in other contexts (e.g. balloons).
+ if ([theEvent type] == NSMouseMoved &&
+ [window level] == NSNormalWindowLevel && ![window isKeyWindow]) {
+ return YES;
+ }
+
+ // Use hitTest to check whether the mouse is over a nonWebContentView - in
+ // which case the mouse event should not be handled by the render host.
+ const SEL nonWebContentViewSelector = @selector(nonWebContentView);
+ NSView* contentView = [window contentView];
+ NSView* view = [contentView hitTest:[theEvent locationInWindow]];
+ // Traverse the superview hierarchy as the hitTest will return the frontmost
+ // view, such as an NSTextView, while nonWebContentView may be specified by
+ // its parent view.
+ while (view) {
+ if ([view respondsToSelector:nonWebContentViewSelector] &&
+ [view performSelector:nonWebContentViewSelector]) {
+ // The cursor is over a nonWebContentView - ignore this mouse event.
+ return YES;
+ }
+ view = [view superview];
+ }
+ return NO;
+}
+
+- (void)mouseEvent:(NSEvent*)theEvent {
+ if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
+ BOOL handled = [delegate_ handleEvent:theEvent];
+ if (handled)
+ return;
+ }
+
+ if ([self shouldIgnoreMouseEvent:theEvent]) {
+ // If this is the first such event, send a mouse exit to the host view.
+ if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
+ WebMouseEvent exitEvent =
+ WebInputEventFactory::mouseEvent(theEvent, self);
+ exitEvent.type = WebInputEvent::MouseLeave;
+ exitEvent.button = WebMouseEvent::ButtonNone;
+ renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(exitEvent);
+ }
+ mouseEventWasIgnored_ = YES;
+ return;
+ }
+
+ if (mouseEventWasIgnored_) {
+ // If this is the first mouse event after a previous event that was ignored
+ // due to the hitTest, send a mouse enter event to the host view.
+ if (renderWidgetHostView_->render_widget_host_) {
+ WebMouseEvent enterEvent =
+ WebInputEventFactory::mouseEvent(theEvent, self);
+ enterEvent.type = WebInputEvent::MouseMove;
+ enterEvent.button = WebMouseEvent::ButtonNone;
+ renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(enterEvent);
+ }
+ }
+ mouseEventWasIgnored_ = NO;
+
+ // TODO(rohitrao): Probably need to handle other mouse down events here.
+ if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->OnMouseActivate();
+
+ // Manually take focus after the click but before forwarding it to the
+ // renderer.
+ [[self window] makeFirstResponder:self];
+ }
+
+ // Don't cancel child popups; killing them on a mouse click would prevent the
+ // user from positioning the insertion point in the text field spawning the
+ // popup. A click outside the text field would cause the text field to drop
+ // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
+ // the popup anyway, so we're OK.
+
+ NSEventType type = [theEvent type];
+ if (type == NSLeftMouseDown)
+ hasOpenMouseDown_ = YES;
+ else if (type == NSLeftMouseUp)
+ hasOpenMouseDown_ = NO;
+
+ // TODO(suzhe): We should send mouse events to the input method first if it
+ // wants to handle them. But it won't work without implementing method
+ // - (NSUInteger)characterIndexForPoint:.
+ // See: http://code.google.com/p/chromium/issues/detail?id=47141
+ // Instead of sending mouse events to the input method first, we now just
+ // simply confirm all ongoing composition here.
+ if (type == NSLeftMouseDown || type == NSRightMouseDown ||
+ type == NSOtherMouseDown) {
+ [self confirmComposition];
+ }
+
+ const WebMouseEvent& event =
+ WebInputEventFactory::mouseEvent(theEvent, self);
+
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(event);
+}
+
+- (void)shortCircuitEndGestureWithEvent:(NSEvent*)event {
+ DCHECK(base::mac::IsOSLionOrLater());
+
+ if ([event subtype] != kIOHIDEventTypeScroll)
+ return;
+
+ if (renderWidgetHostView_->render_widget_host_) {
+ WebGestureEvent webEvent = WebInputEventFactory::gestureEvent(event, self);
+ renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(webEvent);
+ }
+
+ if (endGestureMonitor_) {
+ [NSEvent removeMonitor:endGestureMonitor_];
+ endGestureMonitor_ = nil;
+ }
+}
+
+- (void)beginGestureWithEvent:(NSEvent*)event {
+ if (base::mac::IsOSLionOrLater() &&
+ [event subtype] == kIOHIDEventTypeScroll &&
+ renderWidgetHostView_->render_widget_host_) {
+ WebGestureEvent webEvent = WebInputEventFactory::gestureEvent(event, self);
+ renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(webEvent);
+
+ // Use an NSEvent monitor to get the gesture-end event. This is done in
+ // order to get the gesture-end, even if the view is not visible, which is
+ // not the case with -endGestureWithEvent:. An example scenario where this
+ // may happen is switching tabs while a gesture is in progress.
+ if (!endGestureMonitor_) {
+ endGestureMonitor_ =
+ [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskEndGesture
+ handler:^(NSEvent* blockEvent) {
+ [self shortCircuitEndGestureWithEvent:blockEvent];
+ return blockEvent;
+ }];
+ }
+ }
+}
+
+- (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
+ // |performKeyEquivalent:| is sent to all views of a window, not only down the
+ // responder chain (cf. "Handling Key Equivalents" in
+ // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
+ // ). We only want to handle key equivalents if we're first responder.
+ if ([[self window] firstResponder] != self)
+ return NO;
+
+ // If we return |NO| from this function, cocoa will send the key event to
+ // the menu and only if the menu does not process the event to |keyDown:|. We
+ // want to send the event to a renderer _before_ sending it to the menu, so
+ // we need to return |YES| for all events that might be swallowed by the menu.
+ // We do not return |YES| for every keypress because we don't get |keyDown:|
+ // events for keys that we handle this way.
+ NSUInteger modifierFlags = [theEvent modifierFlags];
+ if ((modifierFlags & NSCommandKeyMask) == 0) {
+ // Make sure the menu does not contain key equivalents that don't
+ // contain cmd.
+ DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
+ return NO;
+ }
+
+ // Command key combinations are sent via performKeyEquivalent rather than
+ // keyDown:. We just forward this on and if WebCore doesn't want to handle
+ // it, we let the TabContentsView figure out how to reinject it.
+ [self keyEvent:theEvent wasKeyEquivalent:YES];
+ return YES;
+}
+
+- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
+ // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
+ // returned NO. If this function returns |YES|, Cocoa sends the event to
+ // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
+ // to us instead of doing key view loop control, ctrl-left/right get handled
+ // correctly, etc.
+ // (However, there are still some keys that Cocoa swallows, e.g. the key
+ // equivalent that Cocoa uses for toggling the input language. In this case,
+ // that's actually a good thing, though -- see http://crbug.com/26115 .)
+ return YES;
+}
+
+- (void)keyEvent:(NSEvent*)theEvent {
+ if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
+ BOOL handled = [delegate_ handleEvent:theEvent];
+ if (handled)
+ return;
+ }
+
+ [self keyEvent:theEvent wasKeyEquivalent:NO];
+}
+
+- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
+ DCHECK([theEvent type] != NSKeyDown ||
+ !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
+
+ if ([theEvent type] == NSFlagsChanged) {
+ // Ignore NSFlagsChanged events from the NumLock and Fn keys as
+ // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
+ int keyCode = [theEvent keyCode];
+ if (!keyCode || keyCode == 10 || keyCode == 63)
+ return;
+ }
+
+ // Don't cancel child popups; the key events are probably what's triggering
+ // the popup in the first place.
+
+ RenderWidgetHost* widgetHost = renderWidgetHostView_->render_widget_host_;
+ DCHECK(widgetHost);
+
+ NativeWebKeyboardEvent event(theEvent);
+
+ // We only handle key down events and just simply forward other events.
+ if ([theEvent type] != NSKeyDown) {
+ widgetHost->ForwardKeyboardEvent(event);
+
+ // Possibly autohide the cursor.
+ if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
+ [NSCursor setHiddenUntilMouseMoves:YES];
+
+ return;
+ }
+
+ scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
+
+ // Records the current marked text state, so that we can know if the marked
+ // text was deleted or not after handling the key down event.
+ BOOL oldHasMarkedText = hasMarkedText_;
+
+ // This method should not be called recursively.
+ DCHECK(!handlingKeyDown_);
+
+ // Tells insertText: and doCommandBySelector: that we are handling a key
+ // down event.
+ handlingKeyDown_ = YES;
+
+ // These variables might be set when handling the keyboard event.
+ // Clear them here so that we can know whether they have changed afterwards.
+ textToBeInserted_.clear();
+ markedText_.clear();
+ underlines_.clear();
+ unmarkTextCalled_ = NO;
+ hasEditCommands_ = NO;
+ editCommands_.clear();
+
+ // Before doing anything with a key down, check to see if plugin IME has been
+ // cancelled, since the plugin host needs to be informed of that before
+ // receiving the keydown.
+ if ([theEvent type] == NSKeyDown)
+ [self checkForPluginImeCancellation];
+
+ // Sends key down events to input method first, then we can decide what should
+ // be done according to input method's feedback.
+ // If a plugin is active, bypass this step since events are forwarded directly
+ // to the plugin IME.
+ if (focusedPluginIdentifier_ == -1)
+ [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
+
+ handlingKeyDown_ = NO;
+
+ // Indicates if we should send the key event and corresponding editor commands
+ // after processing the input method result.
+ BOOL delayEventUntilAfterImeCompostion = NO;
+
+ // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
+ // while an input method is composing or inserting a text.
+ // Gmail checks this code in its onkeydown handler to stop auto-completing
+ // e-mail addresses while composing a CJK text.
+ // If the text to be inserted has only one character, then we don't need this
+ // trick, because we'll send the text as a key press event instead.
+ if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
+ NativeWebKeyboardEvent fakeEvent = event;
+ fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
+ fakeEvent.setKeyIdentifierFromWindowsKeyCode();
+ fakeEvent.skip_in_browser = true;
+ widgetHost->ForwardKeyboardEvent(fakeEvent);
+ // If this key event was handled by the input method, but
+ // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
+ // enqueued edit commands, then in order to let webkit handle them
+ // correctly, we need to send the real key event and corresponding edit
+ // commands after processing the input method result.
+ // We shouldn't do this if a new marked text was set by the input method,
+ // otherwise the new marked text might be cancelled by webkit.
+ if (hasEditCommands_ && !hasMarkedText_)
+ delayEventUntilAfterImeCompostion = YES;
+ } else {
+ if (!editCommands_.empty()) {
+ widgetHost->Send(new ViewMsg_SetEditCommandsForNextKeyEvent(
+ widgetHost->routing_id(), editCommands_));
+ }
+ widgetHost->ForwardKeyboardEvent(event);
+ }
+
+ // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
+ // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
+ // be set to NULL. So we check it here and return immediately if it's NULL.
+ if (!renderWidgetHostView_->render_widget_host_)
+ return;
+
+ // Then send keypress and/or composition related events.
+ // If there was a marked text or the text to be inserted is longer than 1
+ // character, then we send the text by calling ConfirmComposition().
+ // Otherwise, if the text to be inserted only contains 1 character, then we
+ // can just send a keypress event which is fabricated by changing the type of
+ // the keydown event, so that we can retain all necessary informations, such
+ // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
+ // prevent the browser from handling it again.
+ // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
+ // handle BMP characters here, as we can always insert non-BMP characters as
+ // text.
+ BOOL textInserted = NO;
+ if (textToBeInserted_.length() >
+ ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
+ widgetHost->ImeConfirmComposition(textToBeInserted_);
+ textInserted = YES;
+ }
+
+ // Updates or cancels the composition. If some text has been inserted, then
+ // we don't need to cancel the composition explicitly.
+ if (hasMarkedText_ && markedText_.length()) {
+ // Sends the updated marked text to the renderer so it can update the
+ // composition node in WebKit.
+ // When marked text is available, |selectedRange_| will be the range being
+ // selected inside the marked text.
+ widgetHost->ImeSetComposition(markedText_, underlines_,
+ selectedRange_.location,
+ NSMaxRange(selectedRange_));
+ } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
+ if (unmarkTextCalled_)
+ widgetHost->ImeConfirmComposition();
+ else
+ widgetHost->ImeCancelComposition();
+ }
+
+ // If the key event was handled by the input method but it also generated some
+ // edit commands, then we need to send the real key event and corresponding
+ // edit commands here. This usually occurs when the input method wants to
+ // finish current composition session but still wants the application to
+ // handle the key event. See http://crbug.com/48161 for reference.
+ if (delayEventUntilAfterImeCompostion) {
+ // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
+ // with windowsKeyCode == 0xE5 has already been sent to webkit.
+ // So before sending the real key down event, we need to send a fake key up
+ // event to balance it.
+ NativeWebKeyboardEvent fakeEvent = event;
+ fakeEvent.type = WebKit::WebInputEvent::KeyUp;
+ fakeEvent.skip_in_browser = true;
+ widgetHost->ForwardKeyboardEvent(fakeEvent);
+ // Not checking |renderWidgetHostView_->render_widget_host_| here because
+ // a key event with |skip_in_browser| == true won't be handled by browser,
+ // thus it won't destroy the widget.
+
+ if (!editCommands_.empty()) {
+ widgetHost->Send(new ViewMsg_SetEditCommandsForNextKeyEvent(
+ widgetHost->routing_id(), editCommands_));
+ }
+ widgetHost->ForwardKeyboardEvent(event);
+
+ // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
+ // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
+ // be set to NULL. So we check it here and return immediately if it's NULL.
+ if (!renderWidgetHostView_->render_widget_host_)
+ return;
+ }
+
+ const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
+ // Only send a corresponding key press event if there is no marked text.
+ if (!hasMarkedText_) {
+ if (!textInserted && textToBeInserted_.length() == 1) {
+ // If a single character was inserted, then we just send it as a keypress
+ // event.
+ event.type = WebKit::WebInputEvent::Char;
+ event.text[0] = textToBeInserted_[0];
+ event.text[1] = 0;
+ event.skip_in_browser = true;
+ widgetHost->ForwardKeyboardEvent(event);
+ } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
+ [[theEvent characters] length] > 0 &&
+ (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
+ (hasEditCommands_ && editCommands_.empty()))) {
+ // We don't get insertText: calls if ctrl or cmd is down, or the key event
+ // generates an insert command. So synthesize a keypress event for these
+ // cases, unless the key event generated any other command.
+ event.type = WebKit::WebInputEvent::Char;
+ event.skip_in_browser = true;
+ widgetHost->ForwardKeyboardEvent(event);
+ }
+ }
+
+ // Possibly autohide the cursor.
+ if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
+ [NSCursor setHiddenUntilMouseMoves:YES];
+}
+
+- (void)scrollWheel:(NSEvent*)theEvent {
+ // Cancel popups before calling the delegate because even if the delegate eats
+ // the event, it's still an explicit user action outside the popup.
+ [self cancelChildPopups];
+
+ if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
+ BOOL handled = [delegate_ handleEvent:theEvent];
+ if (handled)
+ return;
+ }
+
+ const WebMouseWheelEvent& event =
+ WebInputEventFactory::mouseWheelEvent(theEvent, self);
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(event);
+}
+
+// See the comment in RenderWidgetHostViewMac::Destroy() about cancellation
+// events. On the Mac we must kill popups on outside events, thus this lovely
+// case of filicide caused by events on parent views.
+- (void)cancelChildPopups {
+ // If this view can be the key view, it is not a popup. Therefore, if it has
+ // any children, they are popups that need to be canceled.
+ if (canBeKeyView_) {
+ for (NSView* subview in [self subviews]) {
+ if (![subview isKindOfClass:[RenderWidgetHostViewCocoa class]])
+ continue; // Skip plugin views.
+
+ [static_cast<RenderWidgetHostViewCocoa*>(subview)
+ renderWidgetHostViewMac]->KillSelf();
+ }
+ }
+}
+
+- (void)setFrameSize:(NSSize)newSize {
+ [super setFrameSize:newSize];
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->WasResized();
+}
+
+- (void)setFrame:(NSRect)frameRect {
+ [super setFrame:frameRect];
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->WasResized();
+}
+
+- (void)setFrameWithDeferredUpdate:(NSRect)frameRect {
+ [super setFrame:frameRect];
+ [self performSelector:@selector(renderWidgetHostWasResized)
+ withObject:nil
+ afterDelay:0];
+}
+
+- (void)renderWidgetHostWasResized {
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->WasResized();
+}
+
+- (void)callSetNeedsDisplayInRect {
+ DCHECK([NSThread isMainThread]);
+ DCHECK(renderWidgetHostView_->call_set_needs_display_in_rect_pending_);
+ [self setNeedsDisplayInRect:renderWidgetHostView_->invalid_rect_];
+ renderWidgetHostView_->call_set_needs_display_in_rect_pending_ = false;
+ renderWidgetHostView_->invalid_rect_ = NSZeroRect;
+
+ renderWidgetHostView_->HandleDelayedGpuViewHiding();
+}
+
+// Fills with white the parts of the area to the right and bottom for |rect|
+// that intersect |damagedRect|.
+- (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
+ dirtyRect:(gfx::Rect)damagedRect {
+ if (damagedRect.right() > rect.right()) {
+ int x = std::max(rect.right(), damagedRect.x());
+ int y = std::min(rect.bottom(), damagedRect.bottom());
+ int width = damagedRect.right() - x;
+ int height = damagedRect.y() - y;
+
+ // Extra fun to get around the fact that gfx::Rects can't have
+ // negative sizes.
+ if (width < 0) {
+ x += width;
+ width = -width;
+ }
+ if (height < 0) {
+ y += height;
+ height = -height;
+ }
+
+ NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
+ [[NSColor whiteColor] set];
+ NSRectFill(r);
+ }
+ if (damagedRect.bottom() > rect.bottom()) {
+ int x = damagedRect.x();
+ int y = damagedRect.bottom();
+ int width = damagedRect.right() - x;
+ int height = std::max(rect.bottom(), damagedRect.y()) - y;
+
+ // Extra fun to get around the fact that gfx::Rects can't have
+ // negative sizes.
+ if (width < 0) {
+ x += width;
+ width = -width;
+ }
+ if (height < 0) {
+ y += height;
+ height = -height;
+ }
+
+ NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
+ [[NSColor whiteColor] set];
+ NSRectFill(r);
+ }
+}
+
+- (void)drawRect:(NSRect)dirtyRect {
+ if (!renderWidgetHostView_->render_widget_host_) {
+ // TODO(shess): Consider using something more noticable?
+ [[NSColor whiteColor] set];
+ NSRectFill(dirtyRect);
+ return;
+ }
+
+ const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
+
+ if (renderWidgetHostView_->render_widget_host_->
+ is_accelerated_compositing_active()) {
+ gfx::Rect gpuRect;
+
+ gfx::PluginWindowHandle root_handle =
+ renderWidgetHostView_->plugin_container_manager_.root_container_handle();
+ if (root_handle != gfx::kNullPluginWindow) {
+ AcceleratedPluginView* view =
+ renderWidgetHostView_->ViewForPluginWindowHandle(root_handle);
+ DCHECK(view);
+ if (view && ![view isHidden]) {
+ gpuRect = [self flipNSRectToRect:[view frame]];
+ }
+ }
+
+ [self fillBottomRightRemainderOfRect:gpuRect dirtyRect:damagedRect];
+ return;
+ }
+
+ DCHECK(!renderWidgetHostView_->about_to_validate_and_paint_);
+
+ renderWidgetHostView_->about_to_validate_and_paint_ = true;
+ BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
+ renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
+ renderWidgetHostView_->about_to_validate_and_paint_ = false;
+
+ if (backingStore) {
+ gfx::Rect bitmapRect(0, 0,
+ backingStore->size().width(),
+ backingStore->size().height());
+
+ // Specify the proper y offset to ensure that the view is rooted to the
+ // upper left corner. This can be negative, if the window was resized
+ // smaller and the renderer hasn't yet repainted.
+ int yOffset = NSHeight([self bounds]) - backingStore->size().height();
+
+ gfx::Rect paintRect = bitmapRect.Intersect(damagedRect);
+ if (!paintRect.IsEmpty()) {
+ // if we have a CGLayer, draw that into the window
+ if (backingStore->cg_layer()) {
+ CGContextRef context = static_cast<CGContextRef>(
+ [[NSGraphicsContext currentContext] graphicsPort]);
+
+ // TODO: add clipping to dirtyRect if it improves drawing performance.
+ CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset),
+ backingStore->cg_layer());
+ } else {
+ // if we haven't created a layer yet, draw the cached bitmap into
+ // the window. The CGLayer will be created the next time the renderer
+ // paints.
+ CGContextRef context = static_cast<CGContextRef>(
+ [[NSGraphicsContext currentContext] graphicsPort]);
+ base::mac::ScopedCFTypeRef<CGImageRef> image(
+ CGBitmapContextCreateImage(backingStore->cg_bitmap()));
+ CGRect imageRect = bitmapRect.ToCGRect();
+ imageRect.origin.y = yOffset;
+ CGContextDrawImage(context, imageRect, image);
+ }
+ }
+
+ // Fill the remaining portion of the damagedRect with white
+ [self fillBottomRightRemainderOfRect:bitmapRect dirtyRect:damagedRect];
+
+ if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
+ base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
+ renderWidgetHostView_->whiteout_start_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
+
+ // Reset the start time to 0 so that we start recording again the next
+ // time the backing store is NULL...
+ renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
+ }
+ if (!renderWidgetHostView_->tab_switch_paint_time_.is_null()) {
+ base::TimeDelta tab_switch_paint_duration = base::TimeTicks::Now() -
+ renderWidgetHostView_->tab_switch_paint_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
+ tab_switch_paint_duration);
+ // Reset tab_switch_paint_time_ to 0 so future tab selections are
+ // recorded.
+ renderWidgetHostView_->tab_switch_paint_time_ = base::TimeTicks();
+ }
+ } else {
+ [[NSColor whiteColor] set];
+ NSRectFill(dirtyRect);
+ if (renderWidgetHostView_->whiteout_start_time_.is_null())
+ renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
+ }
+}
+
+- (BOOL)canBecomeKeyView {
+ if (!renderWidgetHostView_->render_widget_host_)
+ return NO;
+
+ return canBeKeyView_;
+}
+
+- (BOOL)acceptsFirstResponder {
+ if (!renderWidgetHostView_->render_widget_host_)
+ return NO;
+
+ return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
+}
+
+- (BOOL)becomeFirstResponder {
+ if (!renderWidgetHostView_->render_widget_host_)
+ return NO;
+
+ renderWidgetHostView_->render_widget_host_->Focus();
+ renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
+ renderWidgetHostView_->SetTextInputActive(true);
+
+ // Cancel any onging composition text which was left before we lost focus.
+ // TODO(suzhe): We should do it in -resignFirstResponder: method, but
+ // somehow that method won't be called when switching among different tabs.
+ // See http://crbug.com/47209
+ [self cancelComposition];
+
+ NSNumber* direction = [NSNumber numberWithUnsignedInteger:
+ [[self window] keyViewSelectionDirection]];
+ NSDictionary* userInfo =
+ [NSDictionary dictionaryWithObject:direction
+ forKey:kSelectionDirection];
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:kViewDidBecomeFirstResponder
+ object:self
+ userInfo:userInfo];
+
+ return YES;
+}
+
+- (BOOL)resignFirstResponder {
+ renderWidgetHostView_->SetTextInputActive(false);
+ if (!renderWidgetHostView_->render_widget_host_)
+ return YES;
+
+ if (closeOnDeactivate_)
+ renderWidgetHostView_->KillSelf();
+
+ renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
+ renderWidgetHostView_->render_widget_host_->Blur();
+
+ // We should cancel any onging composition whenever RWH's Blur() method gets
+ // called, because in this case, webkit will confirm the ongoing composition
+ // internally.
+ [self cancelComposition];
+
+ return YES;
+}
+
+- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
+ if (delegate_ && [delegate_ respondsToSelector:
+ @selector(validateUserInterfaceItem:isValidItem:)]) {
+ BOOL valid;
+ BOOL known = [delegate_ validateUserInterfaceItem:item
+ isValidItem:&valid];
+ if (known)
+ return valid;
+ }
+
+ SEL action = [item action];
+
+ // For now, these actions are always enabled for render view,
+ // this is sub-optimal.
+ // TODO(suzhe): Plumb the "can*" methods up from WebCore.
+ if (action == @selector(undo:) ||
+ action == @selector(redo:) ||
+ action == @selector(cut:) ||
+ action == @selector(copy:) ||
+ action == @selector(copyToFindPboard:) ||
+ action == @selector(paste:) ||
+ action == @selector(pasteAsPlainText:)) {
+ return renderWidgetHostView_->render_widget_host_->IsRenderView();
+ }
+
+ return editCommand_helper_->IsMenuItemEnabled(action, self);
+}
+
+- (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
+ return renderWidgetHostView_.get();
+}
+
+// Determine whether we should autohide the cursor (i.e., hide it until mouse
+// move) for the given event. Customize here to be more selective about which
+// key presses to autohide on.
++ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
+ return ([event type] == NSKeyDown) ? YES : NO;
+}
+
+- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
+ index:(NSUInteger)index
+ maxCount:(NSUInteger)maxCount {
+ NSArray* fullArray = [self accessibilityAttributeValue:attribute];
+ NSUInteger totalLength = [fullArray count];
+ if (index >= totalLength)
+ return nil;
+ NSUInteger length = MIN(totalLength - index, maxCount);
+ return [fullArray subarrayWithRange:NSMakeRange(index, length)];
+}
+
+- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
+ NSArray* fullArray = [self accessibilityAttributeValue:attribute];
+ return [fullArray count];
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute {
+ BrowserAccessibilityManager* manager =
+ renderWidgetHostView_->browser_accessibility_manager_.get();
+
+ // Contents specifies document view of RenderWidgetHostViewCocoa provided by
+ // BrowserAccessibilityManager. Children includes all subviews in addition to
+ // contents. Currently we do not have subviews besides the document view.
+ if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
+ [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
+ manager) {
+ return [NSArray arrayWithObjects:manager->
+ GetRoot()->toBrowserAccessibilityCocoa(), nil];
+ } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
+ return NSAccessibilityScrollAreaRole;
+ }
+ id ret = [super accessibilityAttributeValue:attribute];
+ return ret;
+}
+
+- (NSArray*)accessibilityAttributeNames {
+ NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
+ [ret addObject:NSAccessibilityContentsAttribute];
+ [ret addObjectsFromArray:[super accessibilityAttributeNames]];
+ return ret;
+}
+
+- (id)accessibilityHitTest:(NSPoint)point {
+ if (!renderWidgetHostView_->browser_accessibility_manager_.get())
+ return self;
+ NSPoint pointInWindow = [[self window] convertScreenToBase:point];
+ NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
+ localPoint.y = NSHeight([self bounds]) - localPoint.y;
+ BrowserAccessibilityCocoa* root = renderWidgetHostView_->
+ browser_accessibility_manager_->
+ GetRoot()->toBrowserAccessibilityCocoa();
+ id obj = [root accessibilityHitTest:localPoint];
+ return obj;
+}
+
+- (BOOL)accessibilityIsIgnored {
+ return NO;
+}
+
+- (NSUInteger)accessibilityGetIndexOf:(id)child {
+ BrowserAccessibilityManager* manager =
+ renderWidgetHostView_->browser_accessibility_manager_.get();
+ // Only child is root.
+ if (manager &&
+ manager->GetRoot()->toBrowserAccessibilityCocoa() == child) {
+ return 0;
+ } else {
+ return NSNotFound;
+ }
+}
+
+- (id)accessibilityFocusedUIElement {
+ BrowserAccessibilityManager* manager =
+ renderWidgetHostView_->browser_accessibility_manager_.get();
+ if (manager) {
+ BrowserAccessibility* focused_item = manager->GetFocus(NULL);
+ DCHECK(focused_item);
+ if (focused_item) {
+ BrowserAccessibilityCocoa* focused_item_cocoa =
+ focused_item->toBrowserAccessibilityCocoa();
+ DCHECK(focused_item_cocoa);
+ if (focused_item_cocoa)
+ return focused_item_cocoa;
+ }
+ }
+ return [super accessibilityFocusedUIElement];
+}
+
+- (void)doDefaultAction:(int32)accessibilityObjectId {
+ RenderWidgetHost* rwh = renderWidgetHostView_->render_widget_host_;
+ rwh->Send(new ViewMsg_AccessibilityDoDefaultAction(
+ rwh->routing_id(), accessibilityObjectId));
+}
+
+// Convert a web accessibility's location in web coordinates into a cocoa
+// screen coordinate.
+- (NSPoint)accessibilityPointInScreen:
+ (BrowserAccessibilityCocoa*)accessibility {
+ NSPoint origin = [accessibility origin];
+ NSSize size = [[accessibility size] sizeValue];
+ origin.y = NSHeight([self bounds]) - origin.y;
+ NSPoint originInWindow = [self convertPoint:origin toView:nil];
+ NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow];
+ originInScreen.y = originInScreen.y - size.height;
+ return originInScreen;
+}
+
+- (void)setAccessibilityFocus:(BOOL)focus
+ accessibilityId:(int32)accessibilityObjectId {
+ if (focus) {
+ RenderWidgetHost* rwh = renderWidgetHostView_->render_widget_host_;
+ rwh->Send(new ViewMsg_SetAccessibilityFocus(
+ rwh->routing_id(), accessibilityObjectId));
+ }
+}
+
+- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
+ // Performs a right click copying WebKit's
+ // accessibilityPerformShowMenuAction.
+ NSPoint location = [self accessibilityPointInScreen:accessibility];
+ NSSize size = [[accessibility size] sizeValue];
+ location = [[self window] convertScreenToBase:location];
+ location.x += size.width/2;
+ location.y += size.height/2;
+
+ NSEvent* fakeRightClick = [NSEvent
+ mouseEventWithType:NSRightMouseDown
+ location:location
+ modifierFlags:nil
+ timestamp:0
+ windowNumber:[[self window] windowNumber]
+ context:[NSGraphicsContext currentContext]
+ eventNumber:0
+ clickCount:1
+ pressure:0];
+
+ [self mouseEvent:fakeRightClick];
+}
+
+// Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
+// with minor modifications for code style and commenting.
+//
+// The 'public' interface is -setToolTipAtMousePoint:. This differs from
+// -setToolTip: in that the updated tooltip takes effect immediately,
+// without the user's having to move the mouse out of and back into the view.
+//
+// Unfortunately, doing this requires sending fake mouseEnter/Exit events to
+// the view, which in turn requires overriding some internal tracking-rect
+// methods (to keep track of its owner & userdata, which need to be filled out
+// in the fake events.) --snej 7/6/09
+
+
+/*
+ * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
+ * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Any non-zero value will do, but using something recognizable might help us
+// debug some day.
+static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
+
+// Override of a public NSView method, replacing the inherited functionality.
+// See above for rationale.
+- (NSTrackingRectTag)addTrackingRect:(NSRect)rect
+ owner:(id)owner
+ userData:(void *)data
+ assumeInside:(BOOL)assumeInside {
+ DCHECK(trackingRectOwner_ == nil);
+ trackingRectOwner_ = owner;
+ trackingRectUserData_ = data;
+ return kTrackingRectTag;
+}
+
+// Override of (apparently) a private NSView method(!) See above for rationale.
+- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
+ owner:(id)owner
+ userData:(void *)data
+ assumeInside:(BOOL)assumeInside
+ useTrackingNum:(int)tag {
+ DCHECK(tag == 0 || tag == kTrackingRectTag);
+ DCHECK(trackingRectOwner_ == nil);
+ trackingRectOwner_ = owner;
+ trackingRectUserData_ = data;
+ return kTrackingRectTag;
+}
+
+// Override of (apparently) a private NSView method(!) See above for rationale.
+- (void)_addTrackingRects:(NSRect *)rects
+ owner:(id)owner
+ userDataList:(void **)userDataList
+ assumeInsideList:(BOOL *)assumeInsideList
+ trackingNums:(NSTrackingRectTag *)trackingNums
+ count:(int)count {
+ DCHECK(count == 1);
+ DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
+ DCHECK(trackingRectOwner_ == nil);
+ trackingRectOwner_ = owner;
+ trackingRectUserData_ = userDataList[0];
+ trackingNums[0] = kTrackingRectTag;
+}
+
+// Override of a public NSView method, replacing the inherited functionality.
+// See above for rationale.
+- (void)removeTrackingRect:(NSTrackingRectTag)tag {
+ if (tag == 0)
+ return;
+
+ if (tag == kTrackingRectTag) {
+ trackingRectOwner_ = nil;
+ return;
+ }
+
+ if (tag == lastToolTipTag_) {
+ [super removeTrackingRect:tag];
+ lastToolTipTag_ = 0;
+ return;
+ }
+
+ // If any other tracking rect is being removed, we don't know how it was
+ // created and it's possible there's a leak involved (see Radar 3500217).
+ NOTREACHED();
+}
+
+// Override of (apparently) a private NSView method(!)
+- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
+ for (int i = 0; i < count; ++i) {
+ int tag = tags[i];
+ if (tag == 0)
+ continue;
+ DCHECK(tag == kTrackingRectTag);
+ trackingRectOwner_ = nil;
+ }
+}
+
+// Sends a fake NSMouseExited event to the view for its current tracking rect.
+- (void)_sendToolTipMouseExited {
+ // Nothing matters except window, trackingNumber, and userData.
+ int windowNumber = [[self window] windowNumber];
+ NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
+ location:NSMakePoint(0, 0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:windowNumber
+ context:NULL
+ eventNumber:0
+ trackingNumber:kTrackingRectTag
+ userData:trackingRectUserData_];
+ [trackingRectOwner_ mouseExited:fakeEvent];
+}
+
+// Sends a fake NSMouseEntered event to the view for its current tracking rect.
+- (void)_sendToolTipMouseEntered {
+ // Nothing matters except window, trackingNumber, and userData.
+ int windowNumber = [[self window] windowNumber];
+ NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
+ location:NSMakePoint(0, 0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:windowNumber
+ context:NULL
+ eventNumber:0
+ trackingNumber:kTrackingRectTag
+ userData:trackingRectUserData_];
+ [trackingRectOwner_ mouseEntered:fakeEvent];
+}
+
+// Sets the view's current tooltip, to be displayed at the current mouse
+// location. (This does not make the tooltip appear -- as usual, it only
+// appears after a delay.) Pass null to remove the tooltip.
+- (void)setToolTipAtMousePoint:(NSString *)string {
+ NSString *toolTip = [string length] == 0 ? nil : string;
+ if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
+ (!toolTip && !toolTip_)) {
+ return;
+ }
+
+ if (toolTip_) {
+ [self _sendToolTipMouseExited];
+ }
+
+ toolTip_.reset([toolTip copy]);
+
+ if (toolTip) {
+ // See radar 3500217 for why we remove all tooltips
+ // rather than just the single one we created.
+ [self removeAllToolTips];
+ NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
+ lastToolTipTag_ = [self addToolTipRect:wideOpenRect
+ owner:self
+ userData:NULL];
+ [self _sendToolTipMouseEntered];
+ }
+}
+
+// NSView calls this to get the text when displaying the tooltip.
+- (NSString *)view:(NSView *)view
+ stringForToolTip:(NSToolTipTag)tag
+ point:(NSPoint)point
+ userData:(void *)data {
+ return [[toolTip_ copy] autorelease];
+}
+
+// Below is our NSTextInputClient implementation.
+//
+// When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
+// functions to process this event.
+//
+// [WebHTMLView keyDown] ->
+// EventHandler::keyEvent() ->
+// ...
+// [WebEditorClient handleKeyboardEvent] ->
+// [WebHTMLView _interceptEditingKeyEvent] ->
+// [NSResponder interpretKeyEvents] ->
+// [WebHTMLView insertText] ->
+// Editor::insertText()
+//
+// Unfortunately, it is hard for Chromium to use this implementation because
+// it causes key-typing jank.
+// RenderWidgetHostViewMac is running in a browser process. On the other
+// hand, Editor and EventHandler are running in a renderer process.
+// So, if we used this implementation, a NSKeyDown event is dispatched to
+// the following functions of Chromium.
+//
+// [RenderWidgetHostViewMac keyEvent] (browser) ->
+// |Sync IPC (KeyDown)| (*1) ->
+// EventHandler::keyEvent() (renderer) ->
+// ...
+// EditorClientImpl::handleKeyboardEvent() (renderer) ->
+// |Sync IPC| (*2) ->
+// [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
+// [self interpretKeyEvents] ->
+// [RenderWidgetHostViewMac insertText] (browser) ->
+// |Async IPC| ->
+// Editor::insertText() (renderer)
+//
+// (*1) we need to wait until this call finishes since WebHTMLView uses the
+// result of EventHandler::keyEvent().
+// (*2) we need to wait until this call finishes since WebEditorClient uses
+// the result of [WebHTMLView _interceptEditingKeyEvent].
+//
+// This needs many sync IPC messages sent between a browser and a renderer for
+// each key event, which would probably result in key-typing jank.
+// To avoid this problem, this implementation processes key events (and input
+// method events) totally in a browser process and sends asynchronous input
+// events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
+// renderer process.
+//
+// [RenderWidgetHostViewMac keyEvent] (browser) ->
+// |Async IPC (RawKeyDown)| ->
+// [self interpretKeyEvents] ->
+// [RenderWidgetHostViewMac insertText] (browser) ->
+// |Async IPC (Char)| ->
+// Editor::insertText() (renderer)
+//
+// Since this implementation doesn't have to wait any IPC calls, this doesn't
+// make any key-typing jank. --hbono 7/23/09
+//
+extern "C" {
+extern NSString *NSTextInputReplacementRangeAttributeName;
+}
+
+- (NSArray *)validAttributesForMarkedText {
+ // This code is just copied from WebKit except renaming variables.
+ if (!validAttributesForMarkedText_) {
+ validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
+ NSUnderlineStyleAttributeName,
+ NSUnderlineColorAttributeName,
+ NSMarkedClauseSegmentAttributeName,
+ NSTextInputReplacementRangeAttributeName,
+ nil]);
+ }
+ return validAttributesForMarkedText_.get();
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
+ DCHECK([self window]);
+ // |thePoint| is in screen coordinates, but needs to be converted to WebKit
+ // coordinates (upper left origin). Scroll offsets will be taken care of in
+ // the renderer.
+ thePoint = [[self window] convertScreenToBase:thePoint];
+ thePoint = [self convertPoint:thePoint fromView:nil];
+ thePoint.y = NSHeight([self frame]) - thePoint.y;
+
+ NSUInteger index =
+ TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
+ renderWidgetHostView_->render_widget_host_,
+ gfx::Point(thePoint.x, thePoint.y));
+ return index;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)theRange
+ actualRange:(NSRangePointer)actualRange {
+ // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
+ if (actualRange)
+ *actualRange = theRange;
+ NSRect rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
+ renderWidgetHostView_->render_widget_host_, theRange);
+
+ // The returned rectangle is in WebKit coordinates (upper left origin), so
+ // flip the coordinate system and then convert it into screen coordinates for
+ // return.
+ NSRect viewFrame = [self frame];
+ rect.origin.y = NSHeight(viewFrame) - rect.origin.y;
+ rect.origin.y -= rect.size.height;
+ rect = [self convertRectToBase:rect];
+ rect.origin = [[self window] convertBaseToScreen:rect.origin];
+ return rect;
+}
+
+- (NSRange)markedRange {
+ // An input method calls this method to check if an application really has
+ // a text being composed when hasMarkedText call returns true.
+ // Returns the range saved in the setMarkedText method so the input method
+ // calls the setMarkedText method and we can update the composition node
+ // there. (When this method returns an empty range, the input method doesn't
+ // call the setMarkedText method.)
+ return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
+}
+
+- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
+ actualRange:(NSRangePointer)actualRange {
+ // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
+ if (actualRange)
+ *actualRange = range;
+ NSAttributedString* str =
+ TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
+ renderWidgetHostView_->render_widget_host_, range);
+ return str;
+}
+
+- (NSInteger)conversationIdentifier {
+ return reinterpret_cast<NSInteger>(self);
+}
+
+// Each RenderWidgetHostViewCocoa has its own input context, but we return
+// nil when the caret is in non-editable content or password box to avoid
+// making input methods do their work.
+- (NSTextInputContext *)inputContext {
+ if (focusedPluginIdentifier_ != -1)
+ return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
+
+ switch(renderWidgetHostView_->text_input_type_) {
+ case ui::TEXT_INPUT_TYPE_NONE:
+ case ui::TEXT_INPUT_TYPE_PASSWORD:
+ return nil;
+ default:
+ return [super inputContext];
+ }
+}
+
+- (BOOL)hasMarkedText {
+ // An input method calls this function to figure out whether or not an
+ // application is really composing a text. If it is composing, it calls
+ // the markedRange method, and maybe calls the setMarkedText method.
+ // It seems an input method usually calls this function when it is about to
+ // cancel an ongoing composition. If an application has a non-empty marked
+ // range, it calls the setMarkedText method to delete the range.
+ return hasMarkedText_;
+}
+
+- (void)unmarkText {
+ // Delete the composition node of the renderer and finish an ongoing
+ // composition.
+ // It seems an input method calls the setMarkedText method and set an empty
+ // text when it cancels an ongoing composition, i.e. I have never seen an
+ // input method calls this method.
+ hasMarkedText_ = NO;
+ markedText_.clear();
+ underlines_.clear();
+
+ // If we are handling a key down event, then ConfirmComposition() will be
+ // called in keyEvent: method.
+ if (!handlingKeyDown_)
+ renderWidgetHostView_->render_widget_host_->ImeConfirmComposition();
+ else
+ unmarkTextCalled_ = YES;
+}
+
+- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
+ replacementRange:(NSRange)replacementRange {
+ // An input method updates the composition string.
+ // We send the given text and range to the renderer so it can update the
+ // composition node of WebKit.
+ // TODO(suzhe): It's hard for us to support replacementRange without accessing
+ // the full web content.
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ NSString* im_text = isAttributedString ? [string string] : string;
+ int length = [im_text length];
+
+ // |markedRange_| will get set on a callback from ImeSetComposition().
+ selectedRange_ = newSelRange;
+ markedText_ = base::SysNSStringToUTF16(im_text);
+ hasMarkedText_ = (length > 0);
+
+ underlines_.clear();
+ if (isAttributedString) {
+ ExtractUnderlines(string, &underlines_);
+ } else {
+ // Use a thin black underline by default.
+ underlines_.push_back(
+ WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
+ }
+
+ // If we are handling a key down event, then SetComposition() will be
+ // called in keyEvent: method.
+ // Input methods of Mac use setMarkedText calls with an empty text to cancel
+ // an ongoing composition. So, we should check whether or not the given text
+ // is empty to update the input method state. (Our input method backend can
+ // automatically cancels an ongoing composition when we send an empty text.
+ // So, it is OK to send an empty text to the renderer.)
+ if (!handlingKeyDown_) {
+ renderWidgetHostView_->render_widget_host_->ImeSetComposition(
+ markedText_, underlines_,
+ newSelRange.location, NSMaxRange(newSelRange));
+ }
+}
+
+- (void)doCommandBySelector:(SEL)selector {
+ // An input method calls this function to dispatch an editing command to be
+ // handled by this view.
+ if (selector == @selector(noop:))
+ return;
+
+ std::string command(
+ [RenderWidgetHostViewMacEditCommandHelper::
+ CommandNameForSelector(selector) UTF8String]);
+
+ // If this method is called when handling a key down event, then we need to
+ // handle the command in the key event handler. Otherwise we can just handle
+ // it here.
+ if (handlingKeyDown_) {
+ hasEditCommands_ = YES;
+ // We ignore commands that insert characters, because this was causing
+ // strange behavior (e.g. tab always inserted a tab rather than moving to
+ // the next field on the page).
+ if (!StartsWithASCII(command, "insert", false))
+ editCommands_.push_back(EditCommand(command, ""));
+ } else {
+ RenderWidgetHost* rwh = renderWidgetHostView_->render_widget_host_;
+ rwh->Send(new ViewMsg_ExecuteEditCommand(rwh->routing_id(), command, ""));
+ }
+}
+
+- (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
+ // An input method has characters to be inserted.
+ // Same as Linux, Mac calls this method not only:
+ // * when an input method finishs composing text, but also;
+ // * when we type an ASCII character (without using input methods).
+ // When we aren't using input methods, we should send the given character as
+ // a Char event so it is dispatched to an onkeypress() event handler of
+ // JavaScript.
+ // On the other hand, when we are using input methods, we should send the
+ // given characters as an input method event and prevent the characters from
+ // being dispatched to onkeypress() event handlers.
+ // Text inserting might be initiated by other source instead of keyboard
+ // events, such as the Characters dialog. In this case the text should be
+ // sent as an input method event as well.
+ // TODO(suzhe): It's hard for us to support replacementRange without accessing
+ // the full web content. NOTE: If someone adds support for this, make sure
+ // it works with the default range passed in by -insertText: below.
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ NSString* im_text = isAttributedString ? [string string] : string;
+ if (handlingKeyDown_) {
+ textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
+ } else {
+ renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
+ base::SysNSStringToUTF16(im_text));
+ }
+
+ // Inserting text will delete all marked text automatically.
+ hasMarkedText_ = NO;
+}
+
+- (void)insertText:(id)string {
+ // This is a method on NSTextInput, not NSTextInputClient. But on 10.5, this
+ // gets called anyway. Forward to the right method. http://crbug.com/47890
+ [self insertText:string replacementRange:NSMakeRange(0, 0)];
+}
+
+- (void)viewDidMoveToWindow {
+ if (canBeKeyView_) {
+ NSWindow* newWindow = [self window];
+ // Pointer comparison only, since we don't know if lastWindow_ is still
+ // valid.
+ if (newWindow) {
+ // If we move into a new window, refresh the frame information. We
+ // don't need to do it if it was the same window as it used to be in,
+ // since that case is covered by DidBecomeSelected. We only want to
+ // do this for real browser views, not popups.
+ if (newWindow != lastWindow_) {
+ lastWindow_ = newWindow;
+ renderWidgetHostView_->WindowFrameChanged();
+ }
+ renderWidgetHostView_->ForceTextureReload();
+ }
+ }
+
+ // If we switch windows (or are removed from the view hierarchy), cancel any
+ // open mouse-downs.
+ if (hasOpenMouseDown_) {
+ WebMouseEvent event;
+ event.type = WebInputEvent::MouseUp;
+ event.button = WebMouseEvent::ButtonLeft;
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(event);
+
+ hasOpenMouseDown_ = NO;
+ }
+}
+
+- (void)undo:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)->
+ Undo();
+ }
+}
+
+- (void)redo:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)->
+ Redo();
+ }
+}
+
+- (void)cut:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)->
+ Cut();
+ }
+}
+
+- (void)copy:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)->
+ Copy();
+ }
+}
+
+- (void)copyToFindPboard:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)->
+ CopyToFindPboard();
+ }
+}
+
+- (void)paste:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)->
+ Paste();
+ }
+}
+
+- (void)pasteAsPlainText:(id)sender {
+ if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
+ RenderWidgetHost* rwh = renderWidgetHostView_->render_widget_host_;
+ rwh->Send(new ViewMsg_ExecuteEditCommand(
+ rwh->routing_id(), "PasteAndMatchStyle", ""));
+ }
+}
+
+- (void)cancelComposition {
+ if (!hasMarkedText_)
+ return;
+
+ // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
+ // doesn't call any NSTextInput functions, such as setMarkedText or
+ // insertText. So, we need to send an IPC message to a renderer so it can
+ // delete the composition node.
+ NSInputManager *currentInputManager = [NSInputManager currentInputManager];
+ [currentInputManager markedTextAbandoned:self];
+
+ hasMarkedText_ = NO;
+ // Should not call [self unmarkText] here, because it'll send unnecessary
+ // cancel composition IPC message to the renderer.
+}
+
+- (void)confirmComposition {
+ if (!hasMarkedText_)
+ return;
+
+ if (renderWidgetHostView_->render_widget_host_)
+ renderWidgetHostView_->render_widget_host_->ImeConfirmComposition();
+
+ [self cancelComposition];
+}
+
+- (void)setPluginImeActive:(BOOL)active {
+ if (active == pluginImeActive_)
+ return;
+
+ pluginImeActive_ = active;
+ if (!active) {
+ [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
+ renderWidgetHostView_->PluginImeCompositionCompleted(
+ string16(), focusedPluginIdentifier_);
+ }
+}
+
+- (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
+ if (focused)
+ focusedPluginIdentifier_ = pluginId;
+ else if (focusedPluginIdentifier_ == pluginId)
+ focusedPluginIdentifier_ = -1;
+
+ // Whenever plugin focus changes, plugin IME resets.
+ [self setPluginImeActive:NO];
+}
+
+- (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
+ if (!pluginImeActive_)
+ return false;
+
+ ComplexTextInputPanel* inputPanel =
+ [ComplexTextInputPanel sharedComplexTextInputPanel];
+ NSString* composited_string = nil;
+ BOOL handled = [inputPanel interpretKeyEvent:event
+ string:&composited_string];
+ if (composited_string) {
+ renderWidgetHostView_->PluginImeCompositionCompleted(
+ base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
+ pluginImeActive_ = NO;
+ }
+ return handled;
+}
+
+- (void)checkForPluginImeCancellation {
+ if (pluginImeActive_ &&
+ ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
+ renderWidgetHostView_->PluginImeCompositionCompleted(
+ string16(), focusedPluginIdentifier_);
+ pluginImeActive_ = NO;
+ }
+}
+
+// Overriding a NSResponder method to support application services.
+
+- (id)validRequestorForSendType:(NSString*)sendType
+ returnType:(NSString*)returnType {
+ id requestor = nil;
+ BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
+ BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
+ BOOL hasText = !renderWidgetHostView_->selected_text().empty();
+ BOOL takesText =
+ renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
+
+ if (sendTypeIsString && hasText && !returnType) {
+ requestor = self;
+ } else if (!sendType && returnTypeIsString && takesText) {
+ requestor = self;
+ } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
+ requestor = self;
+ } else {
+ requestor = [super validRequestorForSendType:sendType
+ returnType:returnType];
+ }
+ return requestor;
+}
+
+- (void)viewWillStartLiveResize {
+ [super viewWillStartLiveResize];
+ RenderWidgetHost* widget = renderWidgetHostView_->render_widget_host_;
+ if (widget)
+ widget->Send(new ViewMsg_SetInLiveResize(widget->routing_id(), true));
+}
+
+- (void)viewDidEndLiveResize {
+ [super viewDidEndLiveResize];
+ RenderWidgetHost* widget = renderWidgetHostView_->render_widget_host_;
+ if (widget)
+ widget->Send(new ViewMsg_SetInLiveResize(widget->routing_id(), false));
+}
+
+@end
+
+//
+// Supporting application services
+//
+@implementation RenderWidgetHostViewCocoa(NSServicesRequests)
+
+- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
+ types:(NSArray*)types {
+ const std::string& str = renderWidgetHostView_->selected_text();
+ if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
+
+ scoped_nsobject<NSString> text([[NSString alloc]
+ initWithUTF8String:str.c_str()]);
+ NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
+ [pboard declareTypes:toDeclare owner:nil];
+ return [pboard setString:text forType:NSStringPboardType];
+}
+
+- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
+ NSString *string = [pboard stringForType:NSStringPboardType];
+ if (!string) return NO;
+
+ // If the user is currently using an IME, confirm the IME input,
+ // and then insert the text from the service, the same as TextEdit and Safari.
+ [self confirmComposition];
+ [self insertText:string];
+ return YES;
+}
+
+@end
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h
new file mode 100644
index 0000000..bdafbaf
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h
@@ -0,0 +1,70 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_EDITCOMMAND_HELPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_EDITCOMMAND_HELPER_H_
+#pragma once
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "base/hash_tables.h"
+#include "base/gtest_prod_util.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+// This class mimics the behavior of WebKit's WebView class in a way that makes
+// sense for Chrome.
+//
+// WebCore has the concept of "core commands", basically named actions such as
+// "Select All" and "Move Cursor Left". The commands are executed using their
+// string value by WebCore.
+//
+// This class is responsible for 2 things:
+// 1. Provide an abstraction to determine the enabled/disabled state of menu
+// items that correspond to edit commands.
+// 2. Hook up a bunch of objc selectors to the RenderWidgetHostViewCocoa object.
+// (note that this is not a misspelling of RenderWidgetHostViewMac, it's in
+// fact a distinct object) When these selectors are called, the relevant
+// edit command is executed in WebCore.
+class RenderWidgetHostViewMacEditCommandHelper {
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewMacEditCommandHelperTest,
+ TestAddEditingSelectorsToClass);
+ FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewMacEditCommandHelperTest,
+ TestEditingCommandDelivery);
+
+ public:
+ RenderWidgetHostViewMacEditCommandHelper();
+ ~RenderWidgetHostViewMacEditCommandHelper();
+
+ // Adds editing selectors to the objc class using the objc runtime APIs.
+ // Each selector is connected to a single c method which forwards the message
+ // to WebCore's ExecuteEditCommand() function.
+ // This method is idempotent.
+ // The class passed in must conform to the RenderWidgetHostViewMacOwner
+ // protocol.
+ void AddEditingSelectorsToClass(Class klass);
+
+ // Is a given menu item currently enabled?
+ // SEL - the objc selector currently associated with an NSMenuItem.
+ // owner - An object we can retrieve a RenderWidgetHostViewMac from to
+ // determine the command states.
+ bool IsMenuItemEnabled(SEL item_action,
+ id<RenderWidgetHostViewMacOwner> owner);
+
+ // Converts an editing selector into a command name that can be sent to
+ // webkit.
+ static NSString* CommandNameForSelector(SEL selector);
+
+ protected:
+ // Gets a list of all the selectors that AddEditingSelectorsToClass adds to
+ // the aforementioned class.
+ // returns an array of NSStrings WITHOUT the trailing ':'s.
+ NSArray* GetEditSelectorNames();
+
+ private:
+ base::hash_set<std::string> edit_command_set_;
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacEditCommandHelper);
+};
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_EDITCOMMAND_HELPER_H_
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.mm b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.mm
new file mode 100644
index 0000000..4ad3b76
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.mm
@@ -0,0 +1,235 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
+
+#import <objc/runtime.h>
+
+#include "content/browser/renderer_host/render_widget_host.h"
+#import "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+namespace {
+// The names of all the objc selectors w/o ':'s added to an object by
+// AddEditingSelectorsToClass().
+//
+// This needs to be kept in Sync with WEB_COMMAND list in the WebKit tree at:
+// WebKit/mac/WebView/WebHTMLView.mm .
+const char* kEditCommands[] = {
+ "alignCenter",
+ "alignJustified",
+ "alignLeft",
+ "alignRight",
+ "copy",
+ "cut",
+ "delete",
+ "deleteBackward",
+ "deleteBackwardByDecomposingPreviousCharacter",
+ "deleteForward",
+ "deleteToBeginningOfLine",
+ "deleteToBeginningOfParagraph",
+ "deleteToEndOfLine",
+ "deleteToEndOfParagraph",
+ "deleteToMark",
+ "deleteWordBackward",
+ "deleteWordForward",
+ "ignoreSpelling",
+ "indent",
+ "insertBacktab",
+ "insertLineBreak",
+ "insertNewline",
+ "insertNewlineIgnoringFieldEditor",
+ "insertParagraphSeparator",
+ "insertTab",
+ "insertTabIgnoringFieldEditor",
+ "makeTextWritingDirectionLeftToRight",
+ "makeTextWritingDirectionNatural",
+ "makeTextWritingDirectionRightToLeft",
+ "moveBackward",
+ "moveBackwardAndModifySelection",
+ "moveDown",
+ "moveDownAndModifySelection",
+ "moveForward",
+ "moveForwardAndModifySelection",
+ "moveLeft",
+ "moveLeftAndModifySelection",
+ "moveParagraphBackwardAndModifySelection",
+ "moveParagraphForwardAndModifySelection",
+ "moveRight",
+ "moveRightAndModifySelection",
+ "moveToBeginningOfDocument",
+ "moveToBeginningOfDocumentAndModifySelection",
+ "moveToBeginningOfLine",
+ "moveToBeginningOfLineAndModifySelection",
+ "moveToBeginningOfParagraph",
+ "moveToBeginningOfParagraphAndModifySelection",
+ "moveToBeginningOfSentence",
+ "moveToBeginningOfSentenceAndModifySelection",
+ "moveToEndOfDocument",
+ "moveToEndOfDocumentAndModifySelection",
+ "moveToEndOfLine",
+ "moveToEndOfLineAndModifySelection",
+ "moveToEndOfParagraph",
+ "moveToEndOfParagraphAndModifySelection",
+ "moveToEndOfSentence",
+ "moveToEndOfSentenceAndModifySelection",
+ "moveUp",
+ "moveUpAndModifySelection",
+ "moveWordBackward",
+ "moveWordBackwardAndModifySelection",
+ "moveWordForward",
+ "moveWordForwardAndModifySelection",
+ "moveWordLeft",
+ "moveWordLeftAndModifySelection",
+ "moveWordRight",
+ "moveWordRightAndModifySelection",
+ "outdent",
+ "pageDown",
+ "pageDownAndModifySelection",
+ "pageUp",
+ "pageUpAndModifySelection",
+ "selectAll",
+ "selectLine",
+ "selectParagraph",
+ "selectSentence",
+ "selectToMark",
+ "selectWord",
+ "setMark",
+ "showGuessPanel",
+ "subscript",
+ "superscript",
+ "swapWithMark",
+ "transpose",
+ "underline",
+ "unscript",
+ "yank",
+ "yankAndSelect"};
+
+
+// This function is installed via the objc runtime as the implementation of all
+// the various editing selectors.
+// The objc runtime hookup occurs in
+// RenderWidgetHostViewMacEditCommandHelper::AddEditingSelectorsToClass().
+//
+// self - the object we're attached to; it must implement the
+// RenderWidgetHostViewMacOwner protocol.
+// _cmd - the selector that fired.
+// sender - the id of the object that sent the message.
+//
+// The selector is translated into an edit comand and then forwarded down the
+// pipeline to WebCore.
+// The route the message takes is:
+// RenderWidgetHostViewMac -> RenderViewHost ->
+// | IPC | ->
+// RenderView -> currently focused WebFrame.
+// The WebFrame is in the Chrome glue layer and forwards the message to WebCore.
+void EditCommandImp(id self, SEL _cmd, id sender) {
+ // Make sure |self| is the right type.
+ DCHECK([self conformsToProtocol:@protocol(RenderWidgetHostViewMacOwner)]);
+
+ // SEL -> command name string.
+ NSString* command_name_ns =
+ RenderWidgetHostViewMacEditCommandHelper::CommandNameForSelector(_cmd);
+ std::string command([command_name_ns UTF8String]);
+
+ // Forward the edit command string down the pipeline.
+ RenderWidgetHostViewMac* rwhv = [(id<RenderWidgetHostViewMacOwner>)self
+ renderWidgetHostViewMac];
+ DCHECK(rwhv);
+
+ // The second parameter is the core command value which isn't used here.
+ RenderWidgetHost* rwh = rwhv->GetRenderWidgetHost();
+ rwh->ExecuteEditCommand(command, "");
+}
+
+} // namespace
+
+// Maps an objc-selector to a core command name.
+//
+// Returns the core command name (which is the selector name with the trailing
+// ':' stripped in most cases).
+//
+// Adapted from a function by the same name in
+// WebKit/mac/WebView/WebHTMLView.mm .
+// Capitalized names are returned from this function, but that's simply
+// matching WebHTMLView.mm.
+NSString* RenderWidgetHostViewMacEditCommandHelper::CommandNameForSelector(
+ SEL selector) {
+ if (selector == @selector(insertParagraphSeparator:) ||
+ selector == @selector(insertNewlineIgnoringFieldEditor:))
+ return @"InsertNewline";
+ if (selector == @selector(insertTabIgnoringFieldEditor:))
+ return @"InsertTab";
+ if (selector == @selector(pageDown:))
+ return @"MovePageDown";
+ if (selector == @selector(pageDownAndModifySelection:))
+ return @"MovePageDownAndModifySelection";
+ if (selector == @selector(pageUp:))
+ return @"MovePageUp";
+ if (selector == @selector(pageUpAndModifySelection:))
+ return @"MovePageUpAndModifySelection";
+
+ // Remove the trailing colon.
+ NSString* selector_str = NSStringFromSelector(selector);
+ int selector_len = [selector_str length];
+ return [selector_str substringToIndex:selector_len - 1];
+}
+
+RenderWidgetHostViewMacEditCommandHelper::
+ RenderWidgetHostViewMacEditCommandHelper() {
+ for (size_t i = 0; i < arraysize(kEditCommands); ++i) {
+ edit_command_set_.insert(kEditCommands[i]);
+ }
+}
+
+RenderWidgetHostViewMacEditCommandHelper::
+ ~RenderWidgetHostViewMacEditCommandHelper() {}
+
+// Dynamically adds Selectors to the aformentioned class.
+void RenderWidgetHostViewMacEditCommandHelper::AddEditingSelectorsToClass(
+ Class klass) {
+ for (size_t i = 0; i < arraysize(kEditCommands); ++i) {
+ // Append trailing ':' to command name to get selector name.
+ NSString* sel_str = [NSString stringWithFormat: @"%s:", kEditCommands[i]];
+
+ SEL edit_selector = NSSelectorFromString(sel_str);
+ // May want to use @encode() for the last parameter to this method.
+ // If class_addMethod fails we assume that all the editing selectors where
+ // added to the class.
+ // If a certain class already implements a method then class_addMethod
+ // returns NO, which we can safely ignore.
+ class_addMethod(klass, edit_selector, (IMP)EditCommandImp, "v@:@");
+ }
+}
+
+bool RenderWidgetHostViewMacEditCommandHelper::IsMenuItemEnabled(
+ SEL item_action,
+ id<RenderWidgetHostViewMacOwner> owner) {
+ const char* selector_name = sel_getName(item_action);
+ // TODO(jeremy): The final form of this function will check state
+ // associated with the Browser.
+
+ // For now just mark all edit commands as enabled.
+ NSString* selector_name_ns = [NSString stringWithUTF8String:selector_name];
+
+ // Remove trailing ':'
+ size_t str_len = [selector_name_ns length];
+ selector_name_ns = [selector_name_ns substringToIndex:str_len - 1];
+ std::string edit_command_name([selector_name_ns UTF8String]);
+
+ // search for presence in set and return.
+ bool ret = edit_command_set_.find(edit_command_name) !=
+ edit_command_set_.end();
+ return ret;
+}
+
+NSArray* RenderWidgetHostViewMacEditCommandHelper::GetEditSelectorNames() {
+ size_t num_edit_commands = arraysize(kEditCommands);
+ NSMutableArray* ret = [NSMutableArray arrayWithCapacity:num_edit_commands];
+
+ for (size_t i = 0; i < num_edit_commands; ++i) {
+ [ret addObject:[NSString stringWithUTF8String:kEditCommands[i]]];
+ }
+
+ return ret;
+}
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm
new file mode 100644
index 0000000..c81cc83
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm
@@ -0,0 +1,169 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/message_loop.h"
+#include "content/browser/renderer_host/mock_render_process_host.h"
+#include "content/browser/renderer_host/render_widget_host.h"
+#include "content/common/view_messages.h"
+#include "content/test/test_browser_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+class RenderWidgetHostViewMacEditCommandHelperTest : public PlatformTest {
+};
+
+// Bare bones obj-c class for testing purposes.
+@interface RenderWidgetHostViewMacEditCommandHelperTestClass : NSObject
+@end
+
+@implementation RenderWidgetHostViewMacEditCommandHelperTestClass
+@end
+
+// Class that owns a RenderWidgetHostViewMac.
+@interface RenderWidgetHostViewMacOwner :
+ NSObject<RenderWidgetHostViewMacOwner> {
+ RenderWidgetHostViewMac* rwhvm_;
+}
+
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)rwhvm;
+@end
+
+@implementation RenderWidgetHostViewMacOwner
+
+- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)rwhvm {
+ if ((self = [super init])) {
+ rwhvm_ = rwhvm;
+ }
+ return self;
+}
+
+- (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
+ return rwhvm_;
+}
+
+@end
+
+
+namespace {
+ // Returns true if all the edit command names in the array are present
+ // in test_obj.
+ // edit_commands is a list of NSStrings, selector names are formed by
+ // appending a trailing ':' to the string.
+ bool CheckObjectRespondsToEditCommands(NSArray* edit_commands,
+ id test_obj) {
+ for (NSString* edit_command_name in edit_commands) {
+ NSString* sel_str = [edit_command_name stringByAppendingString:@":"];
+ if (![test_obj respondsToSelector:NSSelectorFromString(sel_str)]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+} // namespace
+
+// Create a RenderWidget for which we can filter messages.
+class RenderWidgetHostEditCommandCounter : public RenderWidgetHost {
+ public:
+ RenderWidgetHostEditCommandCounter(RenderProcessHost* process,
+ int routing_id)
+ : RenderWidgetHost(process, routing_id),
+ edit_command_message_count_(0) {
+ }
+
+ virtual bool Send(IPC::Message* message) {
+ if (message->type() == ViewMsg_ExecuteEditCommand::ID)
+ edit_command_message_count_++;
+ return RenderWidgetHost::Send(message);
+ }
+
+ unsigned int edit_command_message_count_;
+};
+
+
+// Tests that editing commands make it through the pipeline all the way to
+// RenderWidgetHost.
+// Disabled, http://crbug.com/93286.
+TEST_F(RenderWidgetHostViewMacEditCommandHelperTest,
+ DISABLED_TestEditingCommandDelivery) {
+ RenderWidgetHostViewMacEditCommandHelper helper;
+ NSArray* edit_command_strings = helper.GetEditSelectorNames();
+
+ // Set up a mock render widget and set expectations.
+ MessageLoopForUI message_loop;
+ TestBrowserContext browser_context;
+ MockRenderProcessHost mock_process(&browser_context);
+ RenderWidgetHostEditCommandCounter render_widget(&mock_process, 0);
+
+ // RenderWidgetHostViewMac self destructs (RenderWidgetHostViewMacCocoa
+ // takes ownership) so no need to delete it ourselves.
+ RenderWidgetHostViewMac* rwhvm = new RenderWidgetHostViewMac(&render_widget);
+
+ RenderWidgetHostViewMacOwner* rwhwvm_owner =
+ [[[RenderWidgetHostViewMacOwner alloc]
+ initWithRenderWidgetHostViewMac:rwhvm] autorelease];
+
+ helper.AddEditingSelectorsToClass([rwhwvm_owner class]);
+
+ for (NSString* edit_command_name in edit_command_strings) {
+ NSString* sel_str = [edit_command_name stringByAppendingString:@":"];
+ [rwhwvm_owner performSelector:NSSelectorFromString(sel_str) withObject:nil];
+ }
+
+ size_t num_edit_commands = [edit_command_strings count];
+ EXPECT_EQ(render_widget.edit_command_message_count_, num_edit_commands);
+}
+
+// Test RenderWidgetHostViewMacEditCommandHelper::AddEditingSelectorsToClass
+TEST_F(RenderWidgetHostViewMacEditCommandHelperTest,
+ TestAddEditingSelectorsToClass) {
+ RenderWidgetHostViewMacEditCommandHelper helper;
+ NSArray* edit_command_strings = helper.GetEditSelectorNames();
+ ASSERT_GT([edit_command_strings count], 0U);
+
+ // Create a class instance and add methods to the class.
+ RenderWidgetHostViewMacEditCommandHelperTestClass* test_obj =
+ [[[RenderWidgetHostViewMacEditCommandHelperTestClass alloc] init]
+ autorelease];
+
+ // Check that edit commands aren't already attached to the object.
+ ASSERT_FALSE(CheckObjectRespondsToEditCommands(edit_command_strings,
+ test_obj));
+
+ helper.AddEditingSelectorsToClass([test_obj class]);
+
+ // Check that all edit commands where added.
+ ASSERT_TRUE(CheckObjectRespondsToEditCommands(edit_command_strings,
+ test_obj));
+
+ // AddEditingSelectorsToClass() should be idempotent.
+ helper.AddEditingSelectorsToClass([test_obj class]);
+
+ // Check that all edit commands are still there.
+ ASSERT_TRUE(CheckObjectRespondsToEditCommands(edit_command_strings,
+ test_obj));
+}
+
+// Test RenderWidgetHostViewMacEditCommandHelper::IsMenuItemEnabled.
+TEST_F(RenderWidgetHostViewMacEditCommandHelperTest, TestMenuItemEnabling) {
+ RenderWidgetHostViewMacEditCommandHelper helper;
+ RenderWidgetHostViewMacOwner* rwhvm_owner =
+ [[[RenderWidgetHostViewMacOwner alloc] init] autorelease];
+
+ // The select all menu should always be enabled.
+ SEL select_all = NSSelectorFromString(@"selectAll:");
+ ASSERT_TRUE(helper.IsMenuItemEnabled(select_all, rwhvm_owner));
+
+ // Random selectors should be enabled by the function.
+ SEL garbage_selector = NSSelectorFromString(@"randomGarbageSelector:");
+ ASSERT_FALSE(helper.IsMenuItemEnabled(garbage_selector, rwhvm_owner));
+
+ // TODO(jeremy): Currently IsMenuItemEnabled just returns true for all edit
+ // selectors. Once we go past that we should do more extensive testing here.
+}
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
new file mode 100644
index 0000000..43e5300
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -0,0 +1,194 @@
+// 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 "content/browser/renderer_host/render_widget_host_view_mac.h"
+
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "content/browser/browser_thread.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/test/cocoa_test_event_utils.h"
+#import "ui/base/test/ui_cocoa_test_helper.h"
+#include "webkit/plugins/npapi/webplugin.h"
+
+class RenderWidgetHostViewMacTest : public RenderViewHostTestHarness {
+ public:
+ RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
+
+ virtual void SetUp() {
+ RenderViewHostTestHarness::SetUp();
+
+ // TestRenderViewHost's destruction assumes that its view is a
+ // TestRenderWidgetHostView, so store its view and reset it back to the
+ // stored view in |TearDown()|.
+ old_rwhv_ = rvh()->view();
+
+ // Owned by its |native_view()|, i.e. |rwhv_cocoa_|.
+ rwhv_mac_ = new RenderWidgetHostViewMac(rvh());
+ rwhv_cocoa_.reset([rwhv_mac_->native_view() retain]);
+ }
+ virtual void TearDown() {
+ // See comment in SetUp().
+ rvh()->SetView(old_rwhv_);
+
+ // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
+ rwhv_cocoa_.reset();
+ pool_.Recycle();
+ MessageLoop::current()->RunAllPending();
+ pool_.Recycle();
+
+ RenderViewHostTestHarness::TearDown();
+ }
+ protected:
+ // Adds an accelerated plugin view to |rwhv_cocoa_|. Returns a handle to the
+ // newly-added view. Callers must ensure that a UI thread is present and
+ // running before calling this function.
+ gfx::PluginWindowHandle AddAcceleratedPluginView(int w, int h) {
+ // Create an accelerated view the size of the rhwvmac.
+ [rwhv_cocoa_.get() setFrame:NSMakeRect(0, 0, w, h)];
+ gfx::PluginWindowHandle accelerated_handle =
+ rwhv_mac_->AllocateFakePluginWindowHandle(/*opaque=*/false,
+ /*root=*/false);
+ rwhv_mac_->AcceleratedSurfaceSetIOSurface(accelerated_handle, w, h, 0);
+
+ // The accelerated view isn't shown until it has a valid rect and has been
+ // painted to.
+ rwhv_mac_->AcceleratedSurfaceBuffersSwapped(accelerated_handle,
+ 0, 0, 0, 0, 0);
+ webkit::npapi::WebPluginGeometry geom;
+ gfx::Rect rect(0, 0, w, h);
+ geom.window = accelerated_handle;
+ geom.window_rect = rect;
+ geom.clip_rect = rect;
+ geom.visible = true;
+ geom.rects_valid = true;
+ rwhv_mac_->MovePluginWindows(
+ std::vector<webkit::npapi::WebPluginGeometry>(1, geom));
+
+ return accelerated_handle;
+ }
+ private:
+ // This class isn't derived from PlatformTest.
+ base::mac::ScopedNSAutoreleasePool pool_;
+
+ RenderWidgetHostView* old_rwhv_;
+
+ protected:
+ RenderWidgetHostViewMac* rwhv_mac_;
+ scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
+};
+
+TEST_F(RenderWidgetHostViewMacTest, Basic) {
+}
+
+// Regression test for http://crbug.com/60318
+TEST_F(RenderWidgetHostViewMacTest, FocusAcceleratedView) {
+ // The accelerated view methods want to be called on the UI thread.
+ scoped_ptr<BrowserThread> ui_thread_(
+ new BrowserThread(BrowserThread::UI, MessageLoop::current()));
+
+ int w = 400, h = 300;
+ gfx::PluginWindowHandle accelerated_handle = AddAcceleratedPluginView(w, h);
+ EXPECT_FALSE([rwhv_cocoa_.get() isHidden]);
+ NSView* accelerated_view = static_cast<NSView*>(
+ rwhv_mac_->ViewForPluginWindowHandle(accelerated_handle));
+ EXPECT_FALSE([accelerated_view isHidden]);
+
+ // Take away first responder from the rwhvmac, then simulate the effect of a
+ // click on the accelerated view. The rwhvmac should be first responder
+ // again.
+ scoped_nsobject<NSWindow> window([[CocoaTestHelperWindow alloc] init]);
+ scoped_nsobject<NSView> other_view(
+ [[NSTextField alloc] initWithFrame:NSMakeRect(0, h, w, 40)]);
+ [[window contentView] addSubview:rwhv_cocoa_.get()];
+ [[window contentView] addSubview:other_view.get()];
+
+ EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
+ [window makeFirstResponder:rwhv_cocoa_.get()];
+ EXPECT_EQ(rwhv_cocoa_.get(), [window firstResponder]);
+ EXPECT_FALSE([accelerated_view acceptsFirstResponder]);
+
+ EXPECT_TRUE([other_view acceptsFirstResponder]);
+ [window makeFirstResponder:other_view];
+ EXPECT_NE(rwhv_cocoa_.get(), [window firstResponder]);
+
+ EXPECT_TRUE([accelerated_view acceptsFirstResponder]);
+ [window makeFirstResponder:accelerated_view];
+ EXPECT_EQ(rwhv_cocoa_.get(), [window firstResponder]);
+
+ // Clean up.
+ rwhv_mac_->DestroyFakePluginWindowHandle(accelerated_handle);
+}
+
+TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
+ // The RWHVCocoa should normally accept first responder status.
+ EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
+
+ // Unless we tell it not to.
+ rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
+ EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
+
+ // But we can set things back to the way they were originally.
+ rwhv_mac_->SetTakesFocusOnlyOnMouseDown(false);
+ EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
+}
+
+TEST_F(RenderWidgetHostViewMacTest, TakesFocusOnMouseDown) {
+ scoped_nsobject<CocoaTestHelperWindow>
+ window([[CocoaTestHelperWindow alloc] init]);
+ [[window contentView] addSubview:rwhv_cocoa_.get()];
+
+ // Even if the RWHVCocoa disallows first responder, clicking on it gives it
+ // focus.
+ [window setPretendIsKeyWindow:YES];
+ [window makeFirstResponder:nil];
+ ASSERT_NE(rwhv_cocoa_.get(), [window firstResponder]);
+
+ rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
+ EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
+
+ std::pair<NSEvent*, NSEvent*> clicks =
+ cocoa_test_event_utils::MouseClickInView(rwhv_cocoa_.get(), 1);
+ [rwhv_cocoa_.get() mouseDown:clicks.first];
+ EXPECT_EQ(rwhv_cocoa_.get(), [window firstResponder]);
+}
+
+// Regression test for http://crbug.com/64256
+TEST_F(RenderWidgetHostViewMacTest, TakesFocusOnMouseDownWithAcceleratedView) {
+ // The accelerated view methods want to be called on the UI thread.
+ scoped_ptr<BrowserThread> ui_thread_(
+ new BrowserThread(BrowserThread::UI, MessageLoop::current()));
+
+ int w = 400, h = 300;
+ gfx::PluginWindowHandle accelerated_handle = AddAcceleratedPluginView(w, h);
+ EXPECT_FALSE([rwhv_cocoa_.get() isHidden]);
+ NSView* accelerated_view = static_cast<NSView*>(
+ rwhv_mac_->ViewForPluginWindowHandle(accelerated_handle));
+ EXPECT_FALSE([accelerated_view isHidden]);
+
+ // Add the RWHVCocoa to the window and remove first responder status.
+ scoped_nsobject<CocoaTestHelperWindow>
+ window([[CocoaTestHelperWindow alloc] init]);
+ [[window contentView] addSubview:rwhv_cocoa_.get()];
+ [window setPretendIsKeyWindow:YES];
+ [window makeFirstResponder:nil];
+ EXPECT_NE(rwhv_cocoa_.get(), [window firstResponder]);
+
+ // Tell the RWHVMac to not accept first responder status. The accelerated
+ // view should also stop accepting first responder.
+ rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
+ EXPECT_FALSE([accelerated_view acceptsFirstResponder]);
+
+ // A click on the accelerated view should focus the RWHVCocoa.
+ std::pair<NSEvent*, NSEvent*> clicks =
+ cocoa_test_event_utils::MouseClickInView(accelerated_view, 1);
+ [rwhv_cocoa_.get() mouseDown:clicks.first];
+ EXPECT_EQ(rwhv_cocoa_.get(), [window firstResponder]);
+
+ // Clean up.
+ rwhv_mac_->DestroyFakePluginWindowHandle(accelerated_handle);
+}