render_widget_host_view_mac.mm revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <QuartzCore/QuartzCore.h> 6 7#include "chrome/browser/renderer_host/render_widget_host_view_mac.h" 8 9#include "app/app_switches.h" 10#include "app/surface/io_surface_support_mac.h" 11#include "base/command_line.h" 12#include "base/logging.h" 13#include "base/mac/scoped_cftyperef.h" 14#import "base/mac/scoped_nsautorelease_pool.h" 15#include "base/metrics/histogram.h" 16#import "base/scoped_nsobject.h" 17#include "base/string_util.h" 18#include "base/sys_info.h" 19#include "base/sys_string_conversions.h" 20#import "chrome/browser/accessibility/browser_accessibility_cocoa.h" 21#include "chrome/browser/accessibility/browser_accessibility_state.h" 22#include "chrome/browser/browser_thread.h" 23#include "chrome/browser/browser_trial.h" 24#include "chrome/browser/gpu_process_host.h" 25#include "chrome/browser/plugin_process_host.h" 26#include "chrome/browser/renderer_host/backing_store_mac.h" 27#include "chrome/browser/renderer_host/render_process_host.h" 28#include "chrome/browser/renderer_host/render_view_host.h" 29#include "chrome/browser/renderer_host/render_widget_host.h" 30#include "chrome/browser/spellchecker_platform_engine.h" 31#import "chrome/browser/ui/cocoa/rwhvm_editcommand_helper.h" 32#import "chrome/browser/ui/cocoa/view_id_util.h" 33#include "chrome/common/chrome_switches.h" 34#include "chrome/common/native_web_keyboard_event.h" 35#include "chrome/common/edit_command.h" 36#include "chrome/common/gpu_messages.h" 37#include "chrome/common/plugin_messages.h" 38#include "chrome/common/render_messages.h" 39#include "skia/ext/platform_canvas.h" 40#include "third_party/skia/include/core/SkColor.h" 41#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebInputEventFactory.h" 42#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" 43#include "webkit/glue/webaccessibility.h" 44#include "webkit/plugins/npapi/webplugin.h" 45#import "third_party/mozilla/ComplexTextInputPanel.h" 46 47using WebKit::WebInputEvent; 48using WebKit::WebInputEventFactory; 49using WebKit::WebMouseEvent; 50using WebKit::WebMouseWheelEvent; 51 52static inline int ToWebKitModifiers(NSUInteger flags) { 53 int modifiers = 0; 54 if (flags & NSControlKeyMask) modifiers |= WebInputEvent::ControlKey; 55 if (flags & NSShiftKeyMask) modifiers |= WebInputEvent::ShiftKey; 56 if (flags & NSAlternateKeyMask) modifiers |= WebInputEvent::AltKey; 57 if (flags & NSCommandKeyMask) modifiers |= WebInputEvent::MetaKey; 58 return modifiers; 59} 60 61@interface RenderWidgetHostViewCocoa (Private) 62+ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event; 63- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r; 64- (void)keyEvent:(NSEvent *)theEvent wasKeyEquivalent:(BOOL)equiv; 65- (void)cancelChildPopups; 66- (void)checkForPluginImeCancellation; 67@end 68 69// This API was published since 10.6. Provide the declaration so it can be 70// // called below when building with the 10.5 SDK. 71#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 72@class NSTextInputContext; 73@interface NSResponder (AppKitDetails) 74- (NSTextInputContext *)inputContext; 75@end 76#endif 77 78namespace { 79 80// Maximum number of characters we allow in a tooltip. 81const size_t kMaxTooltipLength = 1024; 82 83// TODO(suzhe): Upstream this function. 84WebKit::WebColor WebColorFromNSColor(NSColor *color) { 85 CGFloat r, g, b, a; 86 [color getRed:&r green:&g blue:&b alpha:&a]; 87 88 return 89 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 | 90 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 | 91 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 | 92 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255)); 93} 94 95// Extract underline information from an attributed string. Mostly copied from 96// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm 97void ExtractUnderlines( 98 NSAttributedString* string, 99 std::vector<WebKit::WebCompositionUnderline>* underlines) { 100 int length = [[string string] length]; 101 int i = 0; 102 while (i < length) { 103 NSRange range; 104 NSDictionary* attrs = [string attributesAtIndex:i 105 longestEffectiveRange:&range 106 inRange:NSMakeRange(i, length - i)]; 107 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { 108 WebKit::WebColor color = SK_ColorBLACK; 109 if (NSColor *colorAttr = 110 [attrs objectForKey:NSUnderlineColorAttributeName]) { 111 color = WebColorFromNSColor( 112 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); 113 } 114 underlines->push_back(WebKit::WebCompositionUnderline( 115 range.location, NSMaxRange(range), color, [style intValue] > 1)); 116 } 117 i = range.location + range.length; 118 } 119} 120 121// EnablePasswordInput() and DisablePasswordInput() are copied from 122// enableSecureTextInput() and disableSecureTextInput() functions in 123// third_party/WebKit/WebCore/platform/SecureTextInput.cpp 124// But we don't call EnableSecureEventInput() and DisableSecureEventInput() 125// here, because they are already called in webkit and they are system wide 126// functions. 127void EnablePasswordInput() { 128 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); 129 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag, 130 sizeof(CFArrayRef), &inputSources); 131 CFRelease(inputSources); 132} 133 134void DisablePasswordInput() { 135 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag); 136} 137 138// Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper 139// left of the primary screen (Carbon coordinates), and stuffs it into a 140// gfx::Rect. 141gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) { 142 gfx::Rect new_rect(NSRectToCGRect(rect)); 143 if ([[NSScreen screens] count] > 0) { 144 new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height - 145 new_rect.y() - new_rect.height()); 146 } 147 return new_rect; 148} 149 150// Returns the window that visually contains the given view. This is different 151// from [view window] in the case of tab dragging, where the view's owning 152// window is a floating panel attached to the actual browser window that the tab 153// is visually part of. 154NSWindow* ApparentWindowForView(NSView* view) { 155 // TODO(shess): In case of !window, the view has been removed from 156 // the view hierarchy because the tab isn't main. Could retrieve 157 // the information from the main tab for our window. 158 NSWindow* enclosing_window = [view window]; 159 160 // See if this is a tab drag window. The width check is to distinguish that 161 // case from extension popup windows. 162 NSWindow* ancestor_window = [enclosing_window parentWindow]; 163 if (ancestor_window && (NSWidth([enclosing_window frame]) == 164 NSWidth([ancestor_window frame]))) { 165 enclosing_window = ancestor_window; 166 } 167 168 return enclosing_window; 169} 170 171} // namespace 172 173// AcceleratedPluginView ------------------------------------------------------ 174 175// This subclass of NSView hosts the output of accelerated plugins on 176// the page. 177 178// This class takes a couple of locks, some indirectly. The lock hiearchy is: 179// 1. The DisplayLink lock, implicit to the display link owned by this view. 180// It is taken by the framework before |DrawOneAcceleratedPluginCallback()| 181// is called, and during CVDisplayLink* function calls. 182// 2. The CGL lock, taken explicitly. 183// 3. The AcceleratedSurfaceContainerManagerMac's lock, which it takes when any 184// of its methods are called. 185// 186// No code should ever try to acquire a lock further up in the hierarchy if it 187// already owns a lower lock. For example, while the CGL lock is taken, no 188// CVDisplayLink* functions must be called. 189 190// Informal protocol implemented by windows that need to be informed explicitly 191// about underlay surfaces. 192@interface NSObject (UnderlayableSurface) 193- (void)underlaySurfaceAdded; 194- (void)underlaySurfaceRemoved; 195@end 196 197@interface AcceleratedPluginView : NSView { 198 scoped_nsobject<NSOpenGLPixelFormat> glPixelFormat_; 199 CGLPixelFormatObj cglPixelFormat_; // weak, backed by |glPixelFormat_|. 200 scoped_nsobject<NSOpenGLContext> glContext_; 201 CGLContextObj cglContext_; // weak, backed by |glContext_|. 202 203 CVDisplayLinkRef displayLink_; // Owned by us. 204 205 RenderWidgetHostViewMac* renderWidgetHostView_; // weak 206 gfx::PluginWindowHandle pluginHandle_; // weak 207 208 // The number of swap buffers calls that have been requested by the 209 // GPU process, or a monotonically increasing number of calls to 210 // updateSwapBuffersCount:fromRenderer:routeId: if the update came 211 // from an accelerated plugin. 212 uint64 swapBuffersCount_; 213 // The number of swap buffers calls that have been processed by the 214 // display link thread. This is only used with the GPU process 215 // update path. 216 volatile uint64 acknowledgedSwapBuffersCount_; 217 218 // Auxiliary information needed to formulate an acknowledgment to 219 // the GPU process. These are constant after the first message. 220 // These are both zero for updates coming from a plugin process. 221 volatile int rendererId_; 222 volatile int32 routeId_; 223 224 // Cocoa methods can only be called on the main thread, so have a copy of the 225 // view's size, since it's required on the displaylink thread. 226 NSSize cachedSize_; 227 228 // Rects that should show web content rather than plugin content. 229 scoped_nsobject<NSArray> cutoutRects_; 230 231 // -globalFrameDidChange: can be called recursively, this counts how often it 232 // holds the CGL lock. 233 int globalFrameDidChangeCGLLockCount_; 234} 235 236- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r 237 pluginHandle:(gfx::PluginWindowHandle)pluginHandle; 238- (void)drawView; 239 240// Sets the list of rectangles that should show the web page, rather than the 241// accelerated plugin. This is used to simulate the iframe-based trick that web 242// pages have long used to show web content above windowed plugins on Windows 243// and Linux. 244- (void)setCutoutRects:(NSArray*)cutout_rects; 245 246// Updates the number of swap buffers calls that have been requested. 247// This is currently called with non-zero values only in response to 248// updates from the GPU process. For accelerated plugins, all zeros 249// are passed, and the view takes this as a hint that no flow control 250// or acknowledgment of the swap buffers are desired. 251- (void)updateSwapBuffersCount:(uint64)count 252 fromRenderer:(int)rendererId 253 routeId:(int32)routeId; 254 255// NSViews autorelease subviews when they die. The RWHVMac gets destroyed when 256// RHWVCocoa gets dealloc'd, which means the AcceleratedPluginView child views 257// can be around a little longer than the RWHVMac. This is called when the 258// RWHVMac is about to be deleted (but it's still valid while this method runs). 259- (void)onRenderWidgetHostViewGone; 260 261// This _must_ be atomic, since it's accessed from several threads. 262@property NSSize cachedSize; 263@end 264 265@implementation AcceleratedPluginView 266@synthesize cachedSize = cachedSize_; 267 268- (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime { 269 // There is no autorelease pool when this method is called because it will be 270 // called from a background thread. 271 base::mac::ScopedNSAutoreleasePool pool; 272 273 bool sendAck = (rendererId_ != 0 || routeId_ != 0); 274 uint64 currentSwapBuffersCount = swapBuffersCount_; 275 if (currentSwapBuffersCount == acknowledgedSwapBuffersCount_) { 276 return kCVReturnSuccess; 277 } 278 279 [self drawView]; 280 281 acknowledgedSwapBuffersCount_ = currentSwapBuffersCount; 282 if (sendAck && renderWidgetHostView_) { 283 renderWidgetHostView_->AcknowledgeSwapBuffers( 284 rendererId_, 285 routeId_, 286 acknowledgedSwapBuffersCount_); 287 } 288 289 return kCVReturnSuccess; 290} 291 292// This is the renderer output callback function 293static CVReturn DrawOneAcceleratedPluginCallback( 294 CVDisplayLinkRef displayLink, 295 const CVTimeStamp* now, 296 const CVTimeStamp* outputTime, 297 CVOptionFlags flagsIn, 298 CVOptionFlags* flagsOut, 299 void* displayLinkContext) { 300 CVReturn result = 301 [(AcceleratedPluginView*)displayLinkContext getFrameForTime:outputTime]; 302 return result; 303} 304 305- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r 306 pluginHandle:(gfx::PluginWindowHandle)pluginHandle { 307 if ((self = [super initWithFrame:NSZeroRect])) { 308 renderWidgetHostView_ = r; 309 pluginHandle_ = pluginHandle; 310 cachedSize_ = NSZeroSize; 311 swapBuffersCount_ = 0; 312 acknowledgedSwapBuffersCount_ = 0; 313 rendererId_ = 0; 314 routeId_ = 0; 315 316 [self setAutoresizingMask:NSViewMaxXMargin|NSViewMinYMargin]; 317 318 NSOpenGLPixelFormatAttribute attributes[] = 319 { NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, 0}; 320 321 glPixelFormat_.reset([[NSOpenGLPixelFormat alloc] 322 initWithAttributes:attributes]); 323 glContext_.reset([[NSOpenGLContext alloc] initWithFormat:glPixelFormat_ 324 shareContext:nil]); 325 326 if (!CommandLine::ForCurrentProcess()->HasSwitch( 327 switches::kDisableHolePunching)) { 328 // We "punch a hole" in the window, and have the WindowServer render the 329 // OpenGL surface underneath so we can draw over it. 330 GLint belowWindow = -1; 331 [glContext_ setValues:&belowWindow forParameter:NSOpenGLCPSurfaceOrder]; 332 } 333 334 cglContext_ = (CGLContextObj)[glContext_ CGLContextObj]; 335 cglPixelFormat_ = (CGLPixelFormatObj)[glPixelFormat_ CGLPixelFormatObj]; 336 337 // Draw at beam vsync. 338 GLint swapInterval; 339 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync)) 340 swapInterval = 0; 341 else 342 swapInterval = 1; 343 [glContext_ setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; 344 345 // Set up a display link to do OpenGL rendering on a background thread. 346 CVDisplayLinkCreateWithActiveCGDisplays(&displayLink_); 347 } 348 return self; 349} 350 351- (void)dealloc { 352 CVDisplayLinkRelease(displayLink_); 353 if (renderWidgetHostView_) 354 renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_); 355 [[NSNotificationCenter defaultCenter] removeObserver:self]; 356 [super dealloc]; 357} 358 359- (void)drawView { 360 // Called on a background thread. Synchronized via the CGL context lock. 361 CGLLockContext(cglContext_); 362 363 if (renderWidgetHostView_) { 364 // TODO(thakis): Pixel or view coordinates for size? 365 renderWidgetHostView_->DrawAcceleratedSurfaceInstance( 366 cglContext_, pluginHandle_, [self cachedSize]); 367 } 368 369 CGLFlushDrawable(cglContext_); 370 CGLSetCurrentContext(0); 371 CGLUnlockContext(cglContext_); 372} 373 374- (void)setCutoutRects:(NSArray*)cutout_rects { 375 cutoutRects_.reset([cutout_rects copy]); 376} 377 378- (void)updateSwapBuffersCount:(uint64)count 379 fromRenderer:(int)rendererId 380 routeId:(int32)routeId { 381 if (rendererId == 0 && routeId == 0) { 382 // This notification is coming from a plugin process, for which we 383 // don't have flow control implemented right now. Fake up a swap 384 // buffers count so that we can at least skip useless renders. 385 ++swapBuffersCount_; 386 } else { 387 rendererId_ = rendererId; 388 routeId_ = routeId; 389 swapBuffersCount_ = count; 390 } 391} 392 393- (void)onRenderWidgetHostViewGone { 394 if (!renderWidgetHostView_) 395 return; 396 397 CGLLockContext(cglContext_); 398 // Deallocate the plugin handle while we still can. 399 renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_); 400 renderWidgetHostView_ = NULL; 401 CGLUnlockContext(cglContext_); 402} 403 404- (void)drawRect:(NSRect)rect { 405 if (!CommandLine::ForCurrentProcess()->HasSwitch( 406 switches::kDisableHolePunching)) { 407 const NSRect* dirtyRects; 408 int dirtyRectCount; 409 [self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount]; 410 411 [NSGraphicsContext saveGraphicsState]; 412 413 // Mask out any cutout rects--somewhat counterintuitively cutout rects are 414 // places where clearColor is *not* drawn. The trick is that drawing nothing 415 // lets the parent view (i.e., the web page) show through, whereas drawing 416 // clearColor punches a hole in the window (letting OpenGL show through). 417 if ([cutoutRects_.get() count] > 0) { 418 NSBezierPath* path = [NSBezierPath bezierPath]; 419 // Trace the bounds clockwise to give a base clip rect of the whole view. 420 NSRect bounds = [self bounds]; 421 [path moveToPoint:bounds.origin]; 422 [path lineToPoint:NSMakePoint(NSMinX(bounds), NSMaxY(bounds))]; 423 [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))]; 424 [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMinY(bounds))]; 425 [path closePath]; 426 427 // Then trace each cutout rect counterclockwise to remove that region from 428 // the clip region. 429 for (NSValue* rectWrapper in cutoutRects_.get()) { 430 [path appendBezierPathWithRect:[rectWrapper rectValue]]; 431 } 432 433 [path addClip]; 434 } 435 436 // Punch a hole so that the OpenGL view shows through. 437 [[NSColor clearColor] set]; 438 NSRectFillList(dirtyRects, dirtyRectCount); 439 440 [NSGraphicsContext restoreGraphicsState]; 441 } 442 443 [self drawView]; 444} 445 446- (void)rightMouseDown:(NSEvent*)event { 447 // The NSResponder documentation: "Note: The NSView implementation of this 448 // method does not pass the message up the responder chain, it handles it 449 // directly." 450 // That's bad, we want the next responder (RWHVMac) to handle this event to 451 // dispatch it to the renderer. 452 [[self nextResponder] rightMouseDown:event]; 453} 454 455- (void)globalFrameDidChange:(NSNotification*)notification { 456 globalFrameDidChangeCGLLockCount_++; 457 CGLLockContext(cglContext_); 458 // This call to -update can call -globalFrameDidChange: again, see 459 // http://crbug.com/55754 comments 22 and 24. 460 [glContext_ update]; 461 462 // You would think that -update updates the viewport. You would be wrong. 463 CGLSetCurrentContext(cglContext_); 464 NSSize size = [self frame].size; 465 glViewport(0, 0, size.width, size.height); 466 467 CGLSetCurrentContext(0); 468 CGLUnlockContext(cglContext_); 469 globalFrameDidChangeCGLLockCount_--; 470 471 if (globalFrameDidChangeCGLLockCount_ == 0) { 472 // Make sure the view is synchronized with the correct display. 473 CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( 474 displayLink_, cglContext_, cglPixelFormat_); 475 } 476} 477 478- (void)renewGState { 479 // Synchronize with window server to avoid flashes or corrupt drawing. 480 [[self window] disableScreenUpdatesUntilFlush]; 481 [self globalFrameDidChange:nil]; 482 [super renewGState]; 483} 484 485- (void)lockFocus { 486 [super lockFocus]; 487 488 // If we're using OpenGL, make sure it is connected and that the display link 489 // is running. 490 if ([glContext_ view] != self) { 491 [glContext_ setView:self]; 492 493 [[NSNotificationCenter defaultCenter] 494 addObserver:self 495 selector:@selector(globalFrameDidChange:) 496 name:NSViewGlobalFrameDidChangeNotification 497 object:self]; 498 CVDisplayLinkSetOutputCallback( 499 displayLink_, &DrawOneAcceleratedPluginCallback, self); 500 CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( 501 displayLink_, cglContext_, cglPixelFormat_); 502 CVDisplayLinkStart(displayLink_); 503 } 504 [glContext_ makeCurrentContext]; 505} 506 507- (void)viewWillMoveToWindow:(NSWindow*)newWindow { 508 // Stop the display link thread while the view is not visible. 509 if (newWindow) { 510 if (displayLink_ && !CVDisplayLinkIsRunning(displayLink_)) 511 CVDisplayLinkStart(displayLink_); 512 } else { 513 if (displayLink_ && CVDisplayLinkIsRunning(displayLink_)) 514 CVDisplayLinkStop(displayLink_); 515 } 516 517 // If hole punching is enabled, inform the window hosting this accelerated 518 // view that it needs to be opaque. 519 if (!CommandLine::ForCurrentProcess()->HasSwitch( 520 switches::kDisableHolePunching)) { 521 if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)]) { 522 [static_cast<id>([self window]) underlaySurfaceRemoved]; 523 } 524 525 if ([newWindow respondsToSelector:@selector(underlaySurfaceAdded)]) { 526 [static_cast<id>(newWindow) underlaySurfaceAdded]; 527 } 528 } 529} 530 531- (void)setFrame:(NSRect)frameRect { 532 [self setCachedSize:frameRect.size]; 533 [super setFrame:frameRect]; 534} 535 536- (void)setFrameSize:(NSSize)newSize { 537 [self setCachedSize:newSize]; 538 [super setFrameSize:newSize]; 539} 540 541- (BOOL)acceptsFirstResponder { 542 // Accept first responder if the first responder isn't the RWHVMac, and if the 543 // RWHVMac accepts first responder. If the RWHVMac does not accept first 544 // responder, do not accept on its behalf. 545 return ([[self window] firstResponder] != [self superview] && 546 [[self superview] acceptsFirstResponder]); 547} 548 549- (BOOL)becomeFirstResponder { 550 // Delegate first responder to the RWHVMac. 551 [[self window] makeFirstResponder:[self superview]]; 552 return YES; 553} 554 555- (void)viewDidMoveToSuperview { 556 if (![self superview]) 557 [self onRenderWidgetHostViewGone]; 558} 559@end 560 561// RenderWidgetHostView -------------------------------------------------------- 562 563// static 564RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget( 565 RenderWidgetHost* widget) { 566 return new RenderWidgetHostViewMac(widget); 567} 568 569// static 570RenderWidgetHostView* RenderWidgetHostView:: 571 GetRenderWidgetHostViewFromNativeView(gfx::NativeView native_view) { 572 // TODO(port) 573 NOTREACHED() << 574 "RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView not" 575 "implemented"; 576 return NULL; 577} 578 579/////////////////////////////////////////////////////////////////////////////// 580// RenderWidgetHostViewMac, public: 581 582RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget) 583 : render_widget_host_(widget), 584 about_to_validate_and_paint_(false), 585 call_set_needs_display_in_rect_pending_(false), 586 text_input_type_(WebKit::WebTextInputTypeNone), 587 is_loading_(false), 588 is_hidden_(false), 589 shutdown_factory_(this), 590 needs_gpu_visibility_update_after_repaint_(false) { 591 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_| goes away. 592 // Since we autorelease it, our caller must put |native_view()| into the view 593 // hierarchy right after calling us. 594 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc] 595 initWithRenderWidgetHostViewMac:this] autorelease]; 596 render_widget_host_->set_view(this); 597 598 // Turn on accessibility only if VoiceOver is running. 599 if (IsVoiceOverRunning()) { 600 BrowserAccessibilityState::GetInstance()->OnScreenReaderDetected(); 601 render_widget_host_->EnableRendererAccessibility(); 602 } 603} 604 605RenderWidgetHostViewMac::~RenderWidgetHostViewMac() { 606} 607 608/////////////////////////////////////////////////////////////////////////////// 609// RenderWidgetHostViewMac, RenderWidgetHostView implementation: 610 611void RenderWidgetHostViewMac::InitAsPopup( 612 RenderWidgetHostView* parent_host_view, 613 const gfx::Rect& pos) { 614 bool activatable = popup_type_ == WebKit::WebPopupTypeNone; 615 [cocoa_view_ setCloseOnDeactivate:YES]; 616 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO]; 617 [parent_host_view->GetNativeView() addSubview:cocoa_view_]; 618 619 NSPoint global_origin = NSPointFromCGPoint(pos.origin().ToCGPoint()); 620 if ([[NSScreen screens] count] > 0) { 621 global_origin.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height - 622 pos.height() - global_origin.y; 623 } 624 NSPoint window_origin = 625 [[cocoa_view_ window] convertScreenToBase:global_origin]; 626 NSPoint view_origin = 627 [cocoa_view_ convertPoint:window_origin fromView:nil]; 628 NSRect initial_frame = NSMakeRect(view_origin.x, 629 view_origin.y, 630 pos.width(), 631 pos.height()); 632 [cocoa_view_ setFrame:initial_frame]; 633} 634 635void RenderWidgetHostViewMac::InitAsFullscreen() { 636 NOTIMPLEMENTED() << "Full screen not implemented on Mac"; 637} 638 639RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const { 640 return render_widget_host_; 641} 642 643void RenderWidgetHostViewMac::DidBecomeSelected() { 644 if (!is_hidden_) 645 return; 646 647 if (tab_switch_paint_time_.is_null()) 648 tab_switch_paint_time_ = base::TimeTicks::Now(); 649 is_hidden_ = false; 650 render_widget_host_->WasRestored(); 651} 652 653void RenderWidgetHostViewMac::WasHidden() { 654 if (is_hidden_) 655 return; 656 657 // If we receive any more paint messages while we are hidden, we want to 658 // ignore them so we don't re-allocate the backing store. We will paint 659 // everything again when we become selected again. 660 is_hidden_ = true; 661 662 // If we have a renderer, then inform it that we are being hidden so it can 663 // reduce its resource utilization. 664 render_widget_host_->WasHidden(); 665} 666 667void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) { 668 if (is_hidden_) 669 return; 670 671 // During the initial creation of the RenderWidgetHostView in 672 // TabContents::CreateRenderViewForRenderManager, SetSize is called with an 673 // empty size. In the Windows code flow, it is not ignored because subsequent 674 // sizing calls from the OS flow through TCVW::WasSized which calls SetSize() 675 // again. On Cocoa, we rely on the Cocoa view struture and resizer flags to 676 // keep things sized properly. On the other hand, if the size is not empty 677 // then this is a valid request for a pop-up. 678 if (size.IsEmpty()) 679 return; 680 681 // Do conversions to upper-left origin, as "set size" means keep the 682 // upper-left corner pinned. If the new size is valid, this is a popup whose 683 // superview is another RenderWidgetHostViewCocoa, but even if it's directly 684 // in a TabContentsViewCocoa, they're both BaseViews. 685 DCHECK([[cocoa_view_ superview] isKindOfClass:[BaseView class]]); 686 gfx::Rect rect = 687 [(BaseView*)[cocoa_view_ superview] flipNSRectToRect:[cocoa_view_ frame]]; 688 rect.set_width(size.width()); 689 rect.set_height(size.height()); 690 [cocoa_view_ setFrame: 691 [(BaseView*)[cocoa_view_ superview] flipRectToNSRect:rect]]; 692} 693 694gfx::NativeView RenderWidgetHostViewMac::GetNativeView() { 695 return native_view(); 696} 697 698void RenderWidgetHostViewMac::MovePluginWindows( 699 const std::vector<webkit::npapi::WebPluginGeometry>& moves) { 700 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 701 // Handle movement of accelerated plugins, which are the only "windowed" 702 // plugins that exist on the Mac. 703 for (std::vector<webkit::npapi::WebPluginGeometry>::const_iterator iter = 704 moves.begin(); 705 iter != moves.end(); 706 ++iter) { 707 webkit::npapi::WebPluginGeometry geom = *iter; 708 709 AcceleratedPluginView* view = ViewForPluginWindowHandle(geom.window); 710 DCHECK(view); 711 if (!view) 712 continue; 713 714 if (geom.rects_valid) { 715 gfx::Rect rect = geom.window_rect; 716 if (geom.visible) { 717 rect.set_x(rect.x() + geom.clip_rect.x()); 718 rect.set_y(rect.y() + geom.clip_rect.y()); 719 rect.set_width(geom.clip_rect.width()); 720 rect.set_height(geom.clip_rect.height()); 721 } 722 NSRect new_rect([cocoa_view_ flipRectToNSRect:rect]); 723 [view setFrame:new_rect]; 724 NSMutableArray* cutout_rects = 725 [NSMutableArray arrayWithCapacity:geom.cutout_rects.size()]; 726 for (unsigned int i = 0; i < geom.cutout_rects.size(); ++i) { 727 // Convert to NSRect, and flip vertically. 728 NSRect cutout_rect = NSRectFromCGRect(geom.cutout_rects[i].ToCGRect()); 729 cutout_rect.origin.y = new_rect.size.height - NSMaxY(cutout_rect); 730 [cutout_rects addObject:[NSValue valueWithRect:cutout_rect]]; 731 } 732 [view setCutoutRects:cutout_rects]; 733 [view setNeedsDisplay:YES]; 734 } 735 736 plugin_container_manager_.SetPluginContainerGeometry(geom); 737 738 BOOL visible = 739 plugin_container_manager_.SurfaceShouldBeVisible(geom.window); 740 [view setHidden:!visible]; 741 } 742} 743 744void RenderWidgetHostViewMac::Focus() { 745 [[cocoa_view_ window] makeFirstResponder:cocoa_view_]; 746} 747 748void RenderWidgetHostViewMac::Blur() { 749 [[cocoa_view_ window] makeFirstResponder:nil]; 750} 751 752bool RenderWidgetHostViewMac::HasFocus() { 753 return [[cocoa_view_ window] firstResponder] == cocoa_view_; 754} 755 756void RenderWidgetHostViewMac::Show() { 757 [cocoa_view_ setHidden:NO]; 758 759 DidBecomeSelected(); 760} 761 762void RenderWidgetHostViewMac::Hide() { 763 [cocoa_view_ setHidden:YES]; 764 765 WasHidden(); 766} 767 768bool RenderWidgetHostViewMac::IsShowing() { 769 return ![cocoa_view_ isHidden]; 770} 771 772gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const { 773 // TODO(shess): In case of !window, the view has been removed from 774 // the view hierarchy because the tab isn't main. Could retrieve 775 // the information from the main tab for our window. 776 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); 777 if (!enclosing_window) 778 return gfx::Rect(); 779 780 NSRect bounds = [cocoa_view_ bounds]; 781 bounds = [cocoa_view_ convertRect:bounds toView:nil]; 782 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin]; 783 return FlipNSRectToRectScreen(bounds); 784} 785 786void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) { 787 current_cursor_ = cursor; 788 UpdateCursorIfOverSelf(); 789} 790 791void RenderWidgetHostViewMac::UpdateCursorIfOverSelf() { 792 // Do something special (as Win Chromium does) for arrow cursor while loading 793 // a page? TODO(avi): decide 794 // Can we synchronize to the event stream? Switch to -[NSWindow 795 // mouseLocationOutsideOfEventStream] if we cannot. TODO(avi): test and see 796 NSEvent* event = [[cocoa_view_ window] currentEvent]; 797 if ([event window] != [cocoa_view_ window]) 798 return; 799 800 NSPoint event_location = [event locationInWindow]; 801 NSPoint local_point = [cocoa_view_ convertPoint:event_location fromView:nil]; 802 803 if (!NSPointInRect(local_point, [cocoa_view_ bounds])) 804 return; 805 806 NSCursor* ns_cursor = current_cursor_.GetCursor(); 807 [ns_cursor set]; 808} 809 810void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) { 811 is_loading_ = is_loading; 812 UpdateCursorIfOverSelf(); 813} 814 815void RenderWidgetHostViewMac::ImeUpdateTextInputState( 816 WebKit::WebTextInputType type, 817 const gfx::Rect& caret_rect) { 818 if (text_input_type_ != type) { 819 text_input_type_ = type; 820 if (HasFocus()) 821 SetTextInputActive(true); 822 } 823 824 // We need to convert the coordinate of the cursor rectangle sent from the 825 // renderer and save it. Our input method backend uses a coordinate system 826 // whose origin is the upper-left corner of this view. On the other hand, 827 // Cocoa uses a coordinate system whose origin is the lower-left corner of 828 // this view. So, we convert the cursor rectangle and save it. 829 [cocoa_view_ setCaretRect:[cocoa_view_ flipRectToNSRect:caret_rect]]; 830} 831 832void RenderWidgetHostViewMac::ImeCancelComposition() { 833 [cocoa_view_ cancelComposition]; 834} 835 836void RenderWidgetHostViewMac::DidUpdateBackingStore( 837 const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, 838 const std::vector<gfx::Rect>& copy_rects) { 839 if (!is_hidden_) { 840 std::vector<gfx::Rect> rects(copy_rects); 841 842 // Because the findbar might be open, we cannot use scrollRect:by: here. For 843 // now, simply mark all of scroll rect as dirty. 844 if (!scroll_rect.IsEmpty()) 845 rects.push_back(scroll_rect); 846 847 for (size_t i = 0; i < rects.size(); ++i) { 848 NSRect ns_rect = [cocoa_view_ flipRectToNSRect:rects[i]]; 849 850 if (about_to_validate_and_paint_) { 851 // As much as we'd like to use -setNeedsDisplayInRect: here, we can't. 852 // We're in the middle of executing a -drawRect:, and as soon as it 853 // returns Cocoa will clear its record of what needs display. We 854 // instead use |performSelector:| to call |setNeedsDisplayInRect:| 855 // after returning to the main loop, at which point |drawRect:| is no 856 // longer on the stack. 857 DCHECK([NSThread isMainThread]); 858 if (!call_set_needs_display_in_rect_pending_) { 859 [cocoa_view_ performSelector:@selector(callSetNeedsDisplayInRect) 860 withObject:nil 861 afterDelay:0]; 862 call_set_needs_display_in_rect_pending_ = true; 863 invalid_rect_ = ns_rect; 864 } else { 865 // The old invalid rect is probably invalid now, since the view has 866 // most likely been resized, but there's no harm in dirtying the 867 // union. In the limit, this becomes equivalent to dirtying the 868 // whole view. 869 invalid_rect_ = NSUnionRect(invalid_rect_, ns_rect); 870 } 871 } else { 872 [cocoa_view_ setNeedsDisplayInRect:ns_rect]; 873 } 874 } 875 876 if (!about_to_validate_and_paint_) 877 [cocoa_view_ displayIfNeeded]; 878 } 879 880 // If |about_to_validate_and_paint_| is set, then -drawRect: is on the stack 881 // and it's not allowed to call -setHidden on the accelerated view. In that 882 // case, -callSetNeedsDisplayInRect: will hide it later. 883 // If |about_to_validate_and_paint_| is not set, do it now. 884 if (!about_to_validate_and_paint_) 885 HandleDelayedGpuViewHiding(); 886} 887 888void RenderWidgetHostViewMac::RenderViewGone(base::TerminationStatus status, 889 int error_code) { 890 // TODO(darin): keep this around, and draw sad-tab into it. 891 UpdateCursorIfOverSelf(); 892 Destroy(); 893} 894 895void RenderWidgetHostViewMac::Destroy() { 896 // On Windows, popups are implemented with a popup window style, so that when 897 // an event comes in that would "cancel" it, it receives the OnCancelMode 898 // message and can kill itself. Alas, on the Mac, views cannot capture events 899 // outside of themselves. On Windows, if Destroy is being called on a view, 900 // then the event causing the destroy had also cancelled any popups by the 901 // time Destroy() was called. On the Mac we have to destroy all the popups 902 // ourselves. 903 904 // Depth-first destroy all popups. Use ShutdownHost() to enforce 905 // deepest-first ordering. 906 for (NSView* subview in [cocoa_view_ subviews]) { 907 if ([subview isKindOfClass:[RenderWidgetHostViewCocoa class]]) { 908 [static_cast<RenderWidgetHostViewCocoa*>(subview) 909 renderWidgetHostViewMac]->ShutdownHost(); 910 } else if ([subview isKindOfClass:[AcceleratedPluginView class]]) { 911 [static_cast<AcceleratedPluginView*>(subview) 912 onRenderWidgetHostViewGone]; 913 } 914 } 915 916 // We've been told to destroy. 917 [cocoa_view_ retain]; 918 [cocoa_view_ removeFromSuperview]; 919 [cocoa_view_ autorelease]; 920 921 // We get this call just before |render_widget_host_| deletes 922 // itself. But we are owned by |cocoa_view_|, which may be retained 923 // by some other code. Examples are TabContentsViewMac's 924 // |latent_focus_view_| and TabWindowController's 925 // |cachedContentView_|. 926 render_widget_host_ = NULL; 927} 928 929// Called from the renderer to tell us what the tooltip text should be. It 930// calls us frequently so we need to cache the value to prevent doing a lot 931// of repeat work. 932void RenderWidgetHostViewMac::SetTooltipText(const std::wstring& tooltip_text) { 933 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) { 934 tooltip_text_ = tooltip_text; 935 936 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on 937 // Windows; we're just trying to be polite. Don't persist the trimmed 938 // string, as then the comparison above will always fail and we'll try to 939 // set it again every single time the mouse moves. 940 std::wstring display_text = tooltip_text_; 941 if (tooltip_text_.length() > kMaxTooltipLength) 942 display_text = tooltip_text_.substr(0, kMaxTooltipLength); 943 944 NSString* tooltip_nsstring = base::SysWideToNSString(display_text); 945 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring]; 946 } 947} 948 949// 950// RenderWidgetHostViewCocoa uses the stored selection text, 951// which implements NSServicesRequests protocol. 952// 953void RenderWidgetHostViewMac::SelectionChanged(const std::string& text) { 954 selected_text_ = text; 955} 956 957BackingStore* RenderWidgetHostViewMac::AllocBackingStore( 958 const gfx::Size& size) { 959 return new BackingStoreMac(render_widget_host_, size); 960} 961 962// Sets whether or not to accept first responder status. 963void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) { 964 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag]; 965} 966 967void RenderWidgetHostViewMac::KillSelf() { 968 if (shutdown_factory_.empty()) { 969 [cocoa_view_ setHidden:YES]; 970 MessageLoop::current()->PostTask(FROM_HERE, 971 shutdown_factory_.NewRunnableMethod( 972 &RenderWidgetHostViewMac::ShutdownHost)); 973 } 974} 975 976void RenderWidgetHostViewMac::PluginFocusChanged(bool focused, int plugin_id) { 977 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id]; 978} 979 980void RenderWidgetHostViewMac::StartPluginIme() { 981 [cocoa_view_ setPluginImeActive:YES]; 982} 983 984bool RenderWidgetHostViewMac::PostProcessEventForPluginIme( 985 const NativeWebKeyboardEvent& event) { 986 // Check WebInputEvent type since multiple types of events can be sent into 987 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is 988 // necessary to avoid double processing. 989 // Also check the native type, since NSFlagsChanged is considered a key event 990 // for WebKit purposes, but isn't considered a key event by the OS. 991 if (event.type == WebInputEvent::RawKeyDown && 992 [event.os_event type] == NSKeyDown) 993 return [cocoa_view_ postProcessEventForPluginIme:event.os_event]; 994 return false; 995} 996 997void RenderWidgetHostViewMac::PluginImeCompositionCompleted( 998 const string16& text, int plugin_id) { 999 if (render_widget_host_) { 1000 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted( 1001 render_widget_host_->routing_id(), text, plugin_id)); 1002 } 1003} 1004 1005gfx::PluginWindowHandle 1006RenderWidgetHostViewMac::AllocateFakePluginWindowHandle(bool opaque, 1007 bool root) { 1008 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1009 1010 // |render_widget_host_| is set to NULL when |RWHVMac::Destroy()| has 1011 // completed. If |AllocateFakePluginWindowHandle()| is called after that, 1012 // we will crash when the AcceleratedPluginView we allocate below is 1013 // destroyed. 1014 DCHECK(render_widget_host_); 1015 1016 // Create an NSView to host the plugin's/compositor's pixels. 1017 gfx::PluginWindowHandle handle = 1018 plugin_container_manager_.AllocateFakePluginWindowHandle(opaque, root); 1019 1020 scoped_nsobject<AcceleratedPluginView> plugin_view( 1021 [[AcceleratedPluginView alloc] initWithRenderWidgetHostViewMac:this 1022 pluginHandle:handle]); 1023 [plugin_view setHidden:YES]; 1024 1025 [cocoa_view_ addSubview:plugin_view]; 1026 plugin_views_[handle] = plugin_view; 1027 1028 return handle; 1029} 1030 1031void RenderWidgetHostViewMac::DestroyFakePluginWindowHandle( 1032 gfx::PluginWindowHandle window) { 1033 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1034 PluginViewMap::iterator it = plugin_views_.find(window); 1035 DCHECK(plugin_views_.end() != it); 1036 if (plugin_views_.end() == it) { 1037 return; 1038 } 1039 [it->second removeFromSuperview]; 1040 plugin_views_.erase(it); 1041 1042 // The view's dealloc will call DeallocFakePluginWindowHandle(), which will 1043 // remove the handle from |plugin_container_manager_|. This code path is 1044 // taken if a plugin is removed, but the RWHVMac itself stays alive. 1045} 1046 1047namespace { 1048class DidDestroyAcceleratedSurfaceSender : public Task { 1049 public: 1050 DidDestroyAcceleratedSurfaceSender( 1051 int renderer_id, 1052 int32 renderer_route_id) 1053 : renderer_id_(renderer_id), 1054 renderer_route_id_(renderer_route_id) { 1055 } 1056 1057 void Run() { 1058 GpuProcessHost::Get()->Send( 1059 new GpuMsg_DidDestroyAcceleratedSurface( 1060 renderer_id_, renderer_route_id_)); 1061 } 1062 1063 private: 1064 int renderer_id_; 1065 int32 renderer_route_id_; 1066 1067 DISALLOW_COPY_AND_ASSIGN(DidDestroyAcceleratedSurfaceSender); 1068}; 1069} // anonymous namespace 1070 1071// This is called by AcceleratedPluginView's -dealloc. 1072void RenderWidgetHostViewMac::DeallocFakePluginWindowHandle( 1073 gfx::PluginWindowHandle window) { 1074 // When a browser window with a GPUProcessor is closed, the render process 1075 // will attempt to finish all GL commands. It will busy-wait on the GPU 1076 // process until the command queue is empty. If a paint is pending, the GPU 1077 // process won't process any GL commands until the browser sends a paint ack, 1078 // but since the browser window is already closed, it will never arrive. 1079 // To break this infinite loop, the browser tells the GPU process that the 1080 // surface became invalid, which causes the GPU process to not wait for paint 1081 // acks. 1082 if (render_widget_host_ && 1083 plugin_container_manager_.IsRootContainer(window)) { 1084 BrowserThread::PostTask( 1085 BrowserThread::IO, FROM_HERE, 1086 new DidDestroyAcceleratedSurfaceSender( 1087 render_widget_host_->process()->id(), 1088 render_widget_host_->routing_id())); 1089 } 1090 1091 plugin_container_manager_.DestroyFakePluginWindowHandle(window); 1092} 1093 1094AcceleratedPluginView* RenderWidgetHostViewMac::ViewForPluginWindowHandle( 1095 gfx::PluginWindowHandle window) { 1096 PluginViewMap::iterator it = plugin_views_.find(window); 1097 DCHECK(plugin_views_.end() != it); 1098 if (plugin_views_.end() == it) 1099 return nil; 1100 return it->second; 1101} 1102 1103void RenderWidgetHostViewMac::AcceleratedSurfaceSetIOSurface( 1104 gfx::PluginWindowHandle window, 1105 int32 width, 1106 int32 height, 1107 uint64 io_surface_identifier) { 1108 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1109 plugin_container_manager_.SetSizeAndIOSurface(window, 1110 width, 1111 height, 1112 io_surface_identifier); 1113 1114 if (plugin_container_manager_.IsRootContainer(window)) { 1115 // Fake up a WebPluginGeometry for the root window to set the 1116 // container's size; we will never get a notification from the 1117 // browser about the root window, only plugins. 1118 webkit::npapi::WebPluginGeometry geom; 1119 gfx::Rect rect(0, 0, width, height); 1120 geom.window = window; 1121 geom.window_rect = rect; 1122 geom.clip_rect = rect; 1123 geom.visible = true; 1124 geom.rects_valid = true; 1125 MovePluginWindows(std::vector<webkit::npapi::WebPluginGeometry>(1, geom)); 1126 } 1127} 1128 1129void RenderWidgetHostViewMac::AcceleratedSurfaceSetTransportDIB( 1130 gfx::PluginWindowHandle window, 1131 int32 width, 1132 int32 height, 1133 TransportDIB::Handle transport_dib) { 1134 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1135 plugin_container_manager_.SetSizeAndTransportDIB(window, 1136 width, 1137 height, 1138 transport_dib); 1139} 1140 1141void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped( 1142 gfx::PluginWindowHandle window, 1143 uint64 surface_id, 1144 int renderer_id, 1145 int32 route_id, 1146 uint64 swap_buffers_count) { 1147 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1148 AcceleratedPluginView* view = ViewForPluginWindowHandle(window); 1149 DCHECK(view); 1150 if (!view) 1151 return; 1152 1153 plugin_container_manager_.SetSurfaceWasPaintedTo(window, surface_id); 1154 1155 // The surface is hidden until its first paint, to not show gargabe. 1156 if (plugin_container_manager_.SurfaceShouldBeVisible(window)) 1157 [view setHidden:NO]; 1158 [view updateSwapBuffersCount:swap_buffers_count 1159 fromRenderer:renderer_id 1160 routeId:route_id]; 1161} 1162 1163void RenderWidgetHostViewMac::UpdateRootGpuViewVisibility( 1164 bool show_gpu_widget) { 1165 // Plugins are destroyed on page navigate. The compositor layer on the other 1166 // hand is created on demand and then stays alive until its renderer process 1167 // dies (usually on cross-domain navigation). Instead, only a flag 1168 // |is_accelerated_compositing_active()| is flipped when the compositor output 1169 // should be shown/hidden. 1170 // Show/hide the view belonging to the compositor here. 1171 plugin_container_manager_.set_gpu_rendering_active(show_gpu_widget); 1172 1173 gfx::PluginWindowHandle root_handle = 1174 plugin_container_manager_.root_container_handle(); 1175 if (root_handle != gfx::kNullPluginWindow) { 1176 AcceleratedPluginView* view = ViewForPluginWindowHandle(root_handle); 1177 DCHECK(view); 1178 bool visible = 1179 plugin_container_manager_.SurfaceShouldBeVisible(root_handle); 1180 [[view window] disableScreenUpdatesUntilFlush]; 1181 [view setHidden:!visible]; 1182 } 1183} 1184 1185void RenderWidgetHostViewMac::HandleDelayedGpuViewHiding() { 1186 if (needs_gpu_visibility_update_after_repaint_) { 1187 UpdateRootGpuViewVisibility(false); 1188 needs_gpu_visibility_update_after_repaint_ = false; 1189 } 1190} 1191 1192namespace { 1193class BuffersSwappedAcknowledger : public Task { 1194 public: 1195 BuffersSwappedAcknowledger( 1196 int renderer_id, 1197 int32 route_id, 1198 uint64 swap_buffers_count) 1199 : renderer_id_(renderer_id), 1200 route_id_(route_id), 1201 swap_buffers_count_(swap_buffers_count) { 1202 } 1203 1204 void Run() { 1205 GpuProcessHost::Get()->Send( 1206 new GpuMsg_AcceleratedSurfaceBuffersSwappedACK( 1207 renderer_id_, route_id_, swap_buffers_count_)); 1208 } 1209 1210 private: 1211 int renderer_id_; 1212 int32 route_id_; 1213 uint64 swap_buffers_count_; 1214 1215 DISALLOW_COPY_AND_ASSIGN(BuffersSwappedAcknowledger); 1216}; 1217} // anonymous namespace 1218 1219void RenderWidgetHostViewMac::AcknowledgeSwapBuffers( 1220 int renderer_id, 1221 int32 route_id, 1222 uint64 swap_buffers_count) { 1223 // Called on the display link thread. Hand actual work off to the IO thread, 1224 // because |GpuProcessHost::Get()| can only be called there. 1225 // Currently, this is never called for plugins. 1226 if (render_widget_host_) { 1227 DCHECK_EQ(render_widget_host_->process()->id(), renderer_id); 1228 // |render_widget_host_->routing_id()| and |route_id| are usually not 1229 // equal: The former identifies the channel from the RWH in the browser 1230 // process to the corresponding render widget in the renderer process, while 1231 // the latter identifies the channel from the GpuCommandBufferStub in the 1232 // GPU process to the corresponding command buffer client in the renderer. 1233 } 1234 BrowserThread::PostTask( 1235 BrowserThread::IO, FROM_HERE, 1236 new BuffersSwappedAcknowledger( 1237 renderer_id, route_id, swap_buffers_count)); 1238} 1239 1240void RenderWidgetHostViewMac::GpuRenderingStateDidChange() { 1241 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1242 if (GetRenderWidgetHost()->is_accelerated_compositing_active()) { 1243 UpdateRootGpuViewVisibility( 1244 GetRenderWidgetHost()->is_accelerated_compositing_active()); 1245 } else { 1246 needs_gpu_visibility_update_after_repaint_ = true; 1247 } 1248} 1249 1250void RenderWidgetHostViewMac::DrawAcceleratedSurfaceInstance( 1251 CGLContextObj context, 1252 gfx::PluginWindowHandle plugin_handle, 1253 NSSize size) { 1254 // Called on the display link thread. 1255 CGLSetCurrentContext(context); 1256 1257 glMatrixMode(GL_PROJECTION); 1258 glLoadIdentity(); 1259 // Note that we place the origin at the upper left corner with +y 1260 // going down 1261 glOrtho(0, size.width, size.height, 0, -1, 1); 1262 glMatrixMode(GL_MODELVIEW); 1263 glLoadIdentity(); 1264 1265 plugin_container_manager_.Draw(context, plugin_handle); 1266} 1267 1268void RenderWidgetHostViewMac::ForceTextureReload() { 1269 plugin_container_manager_.ForceTextureReload(); 1270} 1271 1272void RenderWidgetHostViewMac::SetVisuallyDeemphasized(const SkColor* color, 1273 bool animate) { 1274 // This is not used on mac. 1275} 1276 1277void RenderWidgetHostViewMac::ShutdownHost() { 1278 shutdown_factory_.RevokeAll(); 1279 render_widget_host_->Shutdown(); 1280 // Do not touch any members at this point, |this| has been deleted. 1281} 1282 1283bool RenderWidgetHostViewMac::IsVoiceOverRunning() { 1284 NSUserDefaults* user_defaults = [NSUserDefaults standardUserDefaults]; 1285 [user_defaults addSuiteNamed:@"com.apple.universalaccess"]; 1286 return 1 == [user_defaults integerForKey:@"voiceOverOnOffKey"]; 1287} 1288 1289gfx::Rect RenderWidgetHostViewMac::GetViewCocoaBounds() const { 1290 return gfx::Rect(NSRectToCGRect([cocoa_view_ bounds])); 1291} 1292 1293gfx::Rect RenderWidgetHostViewMac::GetRootWindowRect() { 1294 // TODO(shess): In case of !window, the view has been removed from 1295 // the view hierarchy because the tab isn't main. Could retrieve 1296 // the information from the main tab for our window. 1297 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); 1298 if (!enclosing_window) 1299 return gfx::Rect(); 1300 1301 NSRect bounds = [enclosing_window frame]; 1302 return FlipNSRectToRectScreen(bounds); 1303} 1304 1305void RenderWidgetHostViewMac::SetActive(bool active) { 1306 if (render_widget_host_) 1307 render_widget_host_->SetActive(active); 1308 if (HasFocus()) 1309 SetTextInputActive(active); 1310 if (!active) 1311 [cocoa_view_ setPluginImeActive:NO]; 1312} 1313 1314void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) { 1315 if (render_widget_host_) { 1316 render_widget_host_->Send(new ViewMsg_SetWindowVisibility( 1317 render_widget_host_->routing_id(), visible)); 1318 } 1319} 1320 1321void RenderWidgetHostViewMac::WindowFrameChanged() { 1322 if (render_widget_host_) { 1323 render_widget_host_->Send(new ViewMsg_WindowFrameChanged( 1324 render_widget_host_->routing_id(), GetRootWindowRect(), 1325 GetViewBounds())); 1326 } 1327} 1328 1329void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) { 1330 RenderWidgetHostView::SetBackground(background); 1331 if (render_widget_host_) 1332 render_widget_host_->Send(new ViewMsg_SetBackground( 1333 render_widget_host_->routing_id(), background)); 1334} 1335 1336bool RenderWidgetHostViewMac::ContainsNativeView( 1337 gfx::NativeView native_view) const { 1338 // TODO(port) 1339 NOTREACHED() << 1340 "RenderWidgetHostViewMac::ContainsNativeView not implemented."; 1341 return false; 1342} 1343 1344void RenderWidgetHostViewMac::OnAccessibilityNotifications( 1345 const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) { 1346 if (!browser_accessibility_manager_.get()) { 1347 // Use empty document to process notifications 1348 webkit_glue::WebAccessibility empty_document; 1349 empty_document.role = WebAccessibility::ROLE_WEB_AREA; 1350 empty_document.state = 0; 1351 browser_accessibility_manager_.reset( 1352 BrowserAccessibilityManager::Create(cocoa_view_, empty_document, NULL)); 1353 } 1354 browser_accessibility_manager_->OnAccessibilityNotifications(params); 1355} 1356 1357void RenderWidgetHostViewMac::SetTextInputActive(bool active) { 1358 if (active) { 1359 if (text_input_type_ == WebKit::WebTextInputTypePassword) 1360 EnablePasswordInput(); 1361 else 1362 DisablePasswordInput(); 1363 } else { 1364 if (text_input_type_ == WebKit::WebTextInputTypePassword) 1365 DisablePasswordInput(); 1366 } 1367} 1368 1369// RenderWidgetHostViewCocoa --------------------------------------------------- 1370 1371@implementation RenderWidgetHostViewCocoa 1372 1373@synthesize caretRect = caretRect_; 1374 1375- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r { 1376 self = [super initWithFrame:NSZeroRect]; 1377 if (self) { 1378 editCommand_helper_.reset(new RWHVMEditCommandHelper); 1379 editCommand_helper_->AddEditingSelectorsToClass([self class]); 1380 1381 renderWidgetHostView_.reset(r); 1382 canBeKeyView_ = YES; 1383 takesFocusOnlyOnMouseDown_ = NO; 1384 closeOnDeactivate_ = NO; 1385 focusedPluginIdentifier_ = -1; 1386 } 1387 return self; 1388} 1389 1390- (void)setCanBeKeyView:(BOOL)can { 1391 canBeKeyView_ = can; 1392} 1393 1394- (void)setTakesFocusOnlyOnMouseDown:(BOOL)b { 1395 takesFocusOnlyOnMouseDown_ = b; 1396} 1397 1398- (void)setCloseOnDeactivate:(BOOL)b { 1399 closeOnDeactivate_ = b; 1400} 1401 1402- (void)mouseEvent:(NSEvent*)theEvent { 1403 // TODO(rohitrao): Probably need to handle other mouse down events here. 1404 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) { 1405 if (renderWidgetHostView_->render_widget_host_) 1406 renderWidgetHostView_->render_widget_host_->OnMouseActivate(); 1407 1408 // Manually take focus after the click but before forwarding it to the 1409 // renderer. 1410 [[self window] makeFirstResponder:self]; 1411 } 1412 1413 // Don't cancel child popups; killing them on a mouse click would prevent the 1414 // user from positioning the insertion point in the text field spawning the 1415 // popup. A click outside the text field would cause the text field to drop 1416 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel 1417 // the popup anyway, so we're OK. 1418 1419 NSEventType type = [theEvent type]; 1420 if (type == NSLeftMouseDown) 1421 hasOpenMouseDown_ = YES; 1422 else if (type == NSLeftMouseUp) 1423 hasOpenMouseDown_ = NO; 1424 1425 // TODO(suzhe): We should send mouse events to the input method first if it 1426 // wants to handle them. But it won't work without implementing method 1427 // - (NSUInteger)characterIndexForPoint:. 1428 // See: http://code.google.com/p/chromium/issues/detail?id=47141 1429 // Instead of sending mouse events to the input method first, we now just 1430 // simply confirm all ongoing composition here. 1431 if (type == NSLeftMouseDown || type == NSRightMouseDown || 1432 type == NSOtherMouseDown) { 1433 [self confirmComposition]; 1434 } 1435 1436 const WebMouseEvent& event = 1437 WebInputEventFactory::mouseEvent(theEvent, self); 1438 1439 if (renderWidgetHostView_->render_widget_host_) 1440 renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(event); 1441} 1442 1443- (BOOL)performKeyEquivalent:(NSEvent*)theEvent { 1444 // |performKeyEquivalent:| is sent to all views of a window, not only down the 1445 // responder chain (cf. "Handling Key Equivalents" in 1446 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html 1447 // ). We only want to handle key equivalents if we're first responder. 1448 if ([[self window] firstResponder] != self) 1449 return NO; 1450 1451 // If we return |NO| from this function, cocoa will send the key event to 1452 // the menu and only if the menu does not process the event to |keyDown:|. We 1453 // want to send the event to a renderer _before_ sending it to the menu, so 1454 // we need to return |YES| for all events that might be swallowed by the menu. 1455 // We do not return |YES| for every keypress because we don't get |keyDown:| 1456 // events for keys that we handle this way. 1457 NSUInteger modifierFlags = [theEvent modifierFlags]; 1458 if ((modifierFlags & NSCommandKeyMask) == 0) { 1459 // Make sure the menu does not contain key equivalents that don't 1460 // contain cmd. 1461 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]); 1462 return NO; 1463 } 1464 1465 // Command key combinations are sent via performKeyEquivalent rather than 1466 // keyDown:. We just forward this on and if WebCore doesn't want to handle 1467 // it, we let the TabContentsView figure out how to reinject it. 1468 [self keyEvent:theEvent wasKeyEquivalent:YES]; 1469 return YES; 1470} 1471 1472- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event { 1473 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:| 1474 // returned NO. If this function returns |YES|, Cocoa sends the event to 1475 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent 1476 // to us instead of doing key view loop control, ctrl-left/right get handled 1477 // correctly, etc. 1478 // (However, there are still some keys that Cocoa swallows, e.g. the key 1479 // equivalent that Cocoa uses for toggling the input langauge. In this case, 1480 // that's actually a good thing, though -- see http://crbug.com/26115 .) 1481 return YES; 1482} 1483 1484- (void)keyEvent:(NSEvent*)theEvent { 1485 [self keyEvent:theEvent wasKeyEquivalent:NO]; 1486} 1487 1488- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv { 1489 DCHECK([theEvent type] != NSKeyDown || 1490 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask)); 1491 1492 if ([theEvent type] == NSFlagsChanged) { 1493 // Ignore NSFlagsChanged events from the NumLock and Fn keys as 1494 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm"). 1495 int keyCode = [theEvent keyCode]; 1496 if (!keyCode || keyCode == 10 || keyCode == 63) 1497 return; 1498 } 1499 1500 // Don't cancel child popups; the key events are probably what's triggering 1501 // the popup in the first place. 1502 1503 RenderWidgetHost* widgetHost = renderWidgetHostView_->render_widget_host_; 1504 DCHECK(widgetHost); 1505 1506 NativeWebKeyboardEvent event(theEvent); 1507 1508 // We only handle key down events and just simply forward other events. 1509 if ([theEvent type] != NSKeyDown) { 1510 widgetHost->ForwardKeyboardEvent(event); 1511 1512 // Possibly autohide the cursor. 1513 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent]) 1514 [NSCursor setHiddenUntilMouseMoves:YES]; 1515 1516 return; 1517 } 1518 1519 scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]); 1520 1521 // Records the current marked text state, so that we can know if the marked 1522 // text was deleted or not after handling the key down event. 1523 BOOL oldHasMarkedText = hasMarkedText_; 1524 1525 // This method should not be called recursively. 1526 DCHECK(!handlingKeyDown_); 1527 1528 // Tells insertText: and doCommandBySelector: that we are handling a key 1529 // down event. 1530 handlingKeyDown_ = YES; 1531 1532 // These variables might be set when handling the keyboard event. 1533 // Clear them here so that we can know whether they have changed afterwards. 1534 textToBeInserted_.clear(); 1535 markedText_.clear(); 1536 underlines_.clear(); 1537 unmarkTextCalled_ = NO; 1538 hasEditCommands_ = NO; 1539 editCommands_.clear(); 1540 1541 // Before doing anything with a key down, check to see if plugin IME has been 1542 // cancelled, since the plugin host needs to be informed of that before 1543 // receiving the keydown. 1544 if ([theEvent type] == NSKeyDown) 1545 [self checkForPluginImeCancellation]; 1546 1547 // Sends key down events to input method first, then we can decide what should 1548 // be done according to input method's feedback. 1549 // If a plugin is active, bypass this step since events are forwarded directly 1550 // to the plugin IME. 1551 if (focusedPluginIdentifier_ == -1) 1552 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; 1553 1554 handlingKeyDown_ = NO; 1555 1556 // Indicates if we should send the key event and corresponding editor commands 1557 // after processing the input method result. 1558 BOOL delayEventUntilAfterImeCompostion = NO; 1559 1560 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY 1561 // while an input method is composing or inserting a text. 1562 // Gmail checks this code in its onkeydown handler to stop auto-completing 1563 // e-mail addresses while composing a CJK text. 1564 // If the text to be inserted has only one character, then we don't need this 1565 // trick, because we'll send the text as a key press event instead. 1566 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) { 1567 NativeWebKeyboardEvent fakeEvent = event; 1568 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY 1569 fakeEvent.setKeyIdentifierFromWindowsKeyCode(); 1570 fakeEvent.skip_in_browser = true; 1571 widgetHost->ForwardKeyboardEvent(fakeEvent); 1572 // If this key event was handled by the input method, but 1573 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above) 1574 // enqueued edit commands, then in order to let webkit handle them 1575 // correctly, we need to send the real key event and corresponding edit 1576 // commands after processing the input method result. 1577 // We shouldn't do this if a new marked text was set by the input method, 1578 // otherwise the new marked text might be cancelled by webkit. 1579 if (hasEditCommands_ && !hasMarkedText_) 1580 delayEventUntilAfterImeCompostion = YES; 1581 } else { 1582 if (!editCommands_.empty()) 1583 widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands_); 1584 widgetHost->ForwardKeyboardEvent(event); 1585 } 1586 1587 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the 1588 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will 1589 // be set to NULL. So we check it here and return immediately if it's NULL. 1590 if (!renderWidgetHostView_->render_widget_host_) 1591 return; 1592 1593 // Then send keypress and/or composition related events. 1594 // If there was a marked text or the text to be inserted is longer than 1 1595 // character, then we send the text by calling ConfirmComposition(). 1596 // Otherwise, if the text to be inserted only contains 1 character, then we 1597 // can just send a keypress event which is fabricated by changing the type of 1598 // the keydown event, so that we can retain all necessary informations, such 1599 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to 1600 // prevent the browser from handling it again. 1601 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only 1602 // handle BMP characters here, as we can always insert non-BMP characters as 1603 // text. 1604 BOOL textInserted = NO; 1605 if (textToBeInserted_.length() > 1606 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) { 1607 widgetHost->ImeConfirmComposition(textToBeInserted_); 1608 textInserted = YES; 1609 } 1610 1611 // Updates or cancels the composition. If some text has been inserted, then 1612 // we don't need to cancel the composition explicitly. 1613 if (hasMarkedText_ && markedText_.length()) { 1614 // Sends the updated marked text to the renderer so it can update the 1615 // composition node in WebKit. 1616 // When marked text is available, |selectedRange_| will be the range being 1617 // selected inside the marked text. 1618 widgetHost->ImeSetComposition(markedText_, underlines_, 1619 selectedRange_.location, 1620 NSMaxRange(selectedRange_)); 1621 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) { 1622 if (unmarkTextCalled_) 1623 widgetHost->ImeConfirmComposition(); 1624 else 1625 widgetHost->ImeCancelComposition(); 1626 } 1627 1628 // If the key event was handled by the input method but it also generated some 1629 // edit commands, then we need to send the real key event and corresponding 1630 // edit commands here. This usually occurs when the input method wants to 1631 // finish current composition session but still wants the application to 1632 // handle the key event. See http://crbug.com/48161 for reference. 1633 if (delayEventUntilAfterImeCompostion) { 1634 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event 1635 // with windowsKeyCode == 0xE5 has already been sent to webkit. 1636 // So before sending the real key down event, we need to send a fake key up 1637 // event to balance it. 1638 NativeWebKeyboardEvent fakeEvent = event; 1639 fakeEvent.type = WebKit::WebInputEvent::KeyUp; 1640 fakeEvent.skip_in_browser = true; 1641 widgetHost->ForwardKeyboardEvent(fakeEvent); 1642 // Not checking |renderWidgetHostView_->render_widget_host_| here because 1643 // a key event with |skip_in_browser| == true won't be handled by browser, 1644 // thus it won't destroy the widget. 1645 1646 if (!editCommands_.empty()) 1647 widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands_); 1648 widgetHost->ForwardKeyboardEvent(event); 1649 1650 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the 1651 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will 1652 // be set to NULL. So we check it here and return immediately if it's NULL. 1653 if (!renderWidgetHostView_->render_widget_host_) 1654 return; 1655 } 1656 1657 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask; 1658 // Only send a corresponding key press event if there is no marked text. 1659 if (!hasMarkedText_) { 1660 if (!textInserted && textToBeInserted_.length() == 1) { 1661 // If a single character was inserted, then we just send it as a keypress 1662 // event. 1663 event.type = WebKit::WebInputEvent::Char; 1664 event.text[0] = textToBeInserted_[0]; 1665 event.text[1] = 0; 1666 event.skip_in_browser = true; 1667 widgetHost->ForwardKeyboardEvent(event); 1668 } else if ((!textInserted || delayEventUntilAfterImeCompostion) && 1669 [[theEvent characters] length] > 0 && 1670 (([theEvent modifierFlags] & kCtrlCmdKeyMask) || 1671 (hasEditCommands_ && editCommands_.empty()))) { 1672 // We don't get insertText: calls if ctrl or cmd is down, or the key event 1673 // generates an insert command. So synthesize a keypress event for these 1674 // cases, unless the key event generated any other command. 1675 event.type = WebKit::WebInputEvent::Char; 1676 event.skip_in_browser = true; 1677 widgetHost->ForwardKeyboardEvent(event); 1678 } 1679 } 1680 1681 // Possibly autohide the cursor. 1682 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent]) 1683 [NSCursor setHiddenUntilMouseMoves:YES]; 1684} 1685 1686- (void)scrollWheel:(NSEvent *)theEvent { 1687 [self cancelChildPopups]; 1688 1689 const WebMouseWheelEvent& event = 1690 WebInputEventFactory::mouseWheelEvent(theEvent, self); 1691 if (renderWidgetHostView_->render_widget_host_) 1692 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(event); 1693} 1694 1695// See the comment in RenderWidgetHostViewMac::Destroy() about cancellation 1696// events. On the Mac we must kill popups on outside events, thus this lovely 1697// case of filicide caused by events on parent views. 1698- (void)cancelChildPopups { 1699 // If this view can be the key view, it is not a popup. Therefore, if it has 1700 // any children, they are popups that need to be canceled. 1701 if (canBeKeyView_) { 1702 for (NSView* subview in [self subviews]) { 1703 if (![subview isKindOfClass:[RenderWidgetHostViewCocoa class]]) 1704 continue; // Skip plugin views. 1705 1706 [static_cast<RenderWidgetHostViewCocoa*>(subview) 1707 renderWidgetHostViewMac]->KillSelf(); 1708 } 1709 } 1710} 1711 1712- (void)setFrameSize:(NSSize)newSize { 1713 [super setFrameSize:newSize]; 1714 if (renderWidgetHostView_->render_widget_host_) 1715 renderWidgetHostView_->render_widget_host_->WasResized(); 1716} 1717 1718- (void)setFrame:(NSRect)frameRect { 1719 [super setFrame:frameRect]; 1720 if (renderWidgetHostView_->render_widget_host_) 1721 renderWidgetHostView_->render_widget_host_->WasResized(); 1722} 1723 1724- (void)setFrameWithDeferredUpdate:(NSRect)frameRect { 1725 [super setFrame:frameRect]; 1726 [self performSelector:@selector(renderWidgetHostWasResized) 1727 withObject:nil 1728 afterDelay:0]; 1729} 1730 1731- (void)renderWidgetHostWasResized { 1732 if (renderWidgetHostView_->render_widget_host_) 1733 renderWidgetHostView_->render_widget_host_->WasResized(); 1734} 1735 1736- (void)callSetNeedsDisplayInRect { 1737 DCHECK([NSThread isMainThread]); 1738 DCHECK(renderWidgetHostView_->call_set_needs_display_in_rect_pending_); 1739 [self setNeedsDisplayInRect:renderWidgetHostView_->invalid_rect_]; 1740 renderWidgetHostView_->call_set_needs_display_in_rect_pending_ = false; 1741 renderWidgetHostView_->invalid_rect_ = NSZeroRect; 1742 1743 renderWidgetHostView_->HandleDelayedGpuViewHiding(); 1744} 1745 1746// Fills with white the parts of the area to the right and bottom for |rect| 1747// that intersect |damagedRect|. 1748- (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect 1749 dirtyRect:(gfx::Rect)damagedRect { 1750 if (damagedRect.right() > rect.right()) { 1751 int x = std::max(rect.right(), damagedRect.x()); 1752 int y = std::min(rect.bottom(), damagedRect.bottom()); 1753 int width = damagedRect.right() - x; 1754 int height = damagedRect.y() - y; 1755 1756 // Extra fun to get around the fact that gfx::Rects can't have 1757 // negative sizes. 1758 if (width < 0) { 1759 x += width; 1760 width = -width; 1761 } 1762 if (height < 0) { 1763 y += height; 1764 height = -height; 1765 } 1766 1767 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)]; 1768 [[NSColor whiteColor] set]; 1769 NSRectFill(r); 1770 } 1771 if (damagedRect.bottom() > rect.bottom()) { 1772 int x = damagedRect.x(); 1773 int y = damagedRect.bottom(); 1774 int width = damagedRect.right() - x; 1775 int height = std::max(rect.bottom(), damagedRect.y()) - y; 1776 1777 // Extra fun to get around the fact that gfx::Rects can't have 1778 // negative sizes. 1779 if (width < 0) { 1780 x += width; 1781 width = -width; 1782 } 1783 if (height < 0) { 1784 y += height; 1785 height = -height; 1786 } 1787 1788 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)]; 1789 [[NSColor whiteColor] set]; 1790 NSRectFill(r); 1791 } 1792} 1793 1794- (void)drawRect:(NSRect)dirtyRect { 1795 if (!renderWidgetHostView_->render_widget_host_) { 1796 // TODO(shess): Consider using something more noticable? 1797 [[NSColor whiteColor] set]; 1798 NSRectFill(dirtyRect); 1799 return; 1800 } 1801 1802 const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]); 1803 1804 if (renderWidgetHostView_->render_widget_host_-> 1805 is_accelerated_compositing_active()) { 1806 gfx::Rect gpuRect; 1807 1808 gfx::PluginWindowHandle root_handle = 1809 renderWidgetHostView_->plugin_container_manager_.root_container_handle(); 1810 if (root_handle != gfx::kNullPluginWindow) { 1811 AcceleratedPluginView* view = 1812 renderWidgetHostView_->ViewForPluginWindowHandle(root_handle); 1813 DCHECK(view); 1814 if (view && ![view isHidden]) { 1815 NSRect frame = [view frame]; 1816 frame.size = [view cachedSize]; 1817 gpuRect = [self flipNSRectToRect:frame]; 1818 } 1819 } 1820 1821 [self fillBottomRightRemainderOfRect:gpuRect dirtyRect:damagedRect]; 1822 return; 1823 } 1824 1825 DCHECK(!renderWidgetHostView_->about_to_validate_and_paint_); 1826 1827 renderWidgetHostView_->about_to_validate_and_paint_ = true; 1828 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>( 1829 renderWidgetHostView_->render_widget_host_->GetBackingStore(true)); 1830 renderWidgetHostView_->about_to_validate_and_paint_ = false; 1831 1832 if (backingStore) { 1833 gfx::Rect bitmapRect(0, 0, 1834 backingStore->size().width(), 1835 backingStore->size().height()); 1836 1837 // Specify the proper y offset to ensure that the view is rooted to the 1838 // upper left corner. This can be negative, if the window was resized 1839 // smaller and the renderer hasn't yet repainted. 1840 int yOffset = NSHeight([self bounds]) - backingStore->size().height(); 1841 1842 gfx::Rect paintRect = bitmapRect.Intersect(damagedRect); 1843 if (!paintRect.IsEmpty()) { 1844 // if we have a CGLayer, draw that into the window 1845 if (backingStore->cg_layer()) { 1846 CGContextRef context = static_cast<CGContextRef>( 1847 [[NSGraphicsContext currentContext] graphicsPort]); 1848 1849 // TODO: add clipping to dirtyRect if it improves drawing performance. 1850 CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset), 1851 backingStore->cg_layer()); 1852 } else { 1853 // if we haven't created a layer yet, draw the cached bitmap into 1854 // the window. The CGLayer will be created the next time the renderer 1855 // paints. 1856 CGContextRef context = static_cast<CGContextRef>( 1857 [[NSGraphicsContext currentContext] graphicsPort]); 1858 base::mac::ScopedCFTypeRef<CGImageRef> image( 1859 CGBitmapContextCreateImage(backingStore->cg_bitmap())); 1860 CGRect imageRect = bitmapRect.ToCGRect(); 1861 imageRect.origin.y = yOffset; 1862 CGContextDrawImage(context, imageRect, image); 1863 } 1864 } 1865 1866 // Fill the remaining portion of the damagedRect with white 1867 [self fillBottomRightRemainderOfRect:bitmapRect dirtyRect:damagedRect]; 1868 1869 if (!renderWidgetHostView_->whiteout_start_time_.is_null()) { 1870 base::TimeDelta whiteout_duration = base::TimeTicks::Now() - 1871 renderWidgetHostView_->whiteout_start_time_; 1872 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration); 1873 1874 // Reset the start time to 0 so that we start recording again the next 1875 // time the backing store is NULL... 1876 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks(); 1877 } 1878 if (!renderWidgetHostView_->tab_switch_paint_time_.is_null()) { 1879 base::TimeDelta tab_switch_paint_duration = base::TimeTicks::Now() - 1880 renderWidgetHostView_->tab_switch_paint_time_; 1881 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration", 1882 tab_switch_paint_duration); 1883 // Reset tab_switch_paint_time_ to 0 so future tab selections are 1884 // recorded. 1885 renderWidgetHostView_->tab_switch_paint_time_ = base::TimeTicks(); 1886 } 1887 } else { 1888 [[NSColor whiteColor] set]; 1889 NSRectFill(dirtyRect); 1890 if (renderWidgetHostView_->whiteout_start_time_.is_null()) 1891 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now(); 1892 } 1893} 1894 1895- (BOOL)canBecomeKeyView { 1896 if (!renderWidgetHostView_->render_widget_host_) 1897 return NO; 1898 1899 return canBeKeyView_; 1900} 1901 1902- (BOOL)acceptsFirstResponder { 1903 if (!renderWidgetHostView_->render_widget_host_) 1904 return NO; 1905 1906 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_; 1907} 1908 1909- (BOOL)becomeFirstResponder { 1910 if (!renderWidgetHostView_->render_widget_host_) 1911 return NO; 1912 1913 renderWidgetHostView_->render_widget_host_->Focus(); 1914 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true); 1915 renderWidgetHostView_->SetTextInputActive(true); 1916 1917 // Cancel any onging composition text which was left before we lost focus. 1918 // TODO(suzhe): We should do it in -resignFirstResponder: method, but 1919 // somehow that method won't be called when switching among different tabs. 1920 // See http://crbug.com/47209 1921 [self cancelComposition]; 1922 1923 NSNumber* direction = [NSNumber numberWithUnsignedInteger: 1924 [[self window] keyViewSelectionDirection]]; 1925 NSDictionary* userInfo = 1926 [NSDictionary dictionaryWithObject:direction 1927 forKey:kSelectionDirection]; 1928 [[NSNotificationCenter defaultCenter] 1929 postNotificationName:kViewDidBecomeFirstResponder 1930 object:self 1931 userInfo:userInfo]; 1932 1933 return YES; 1934} 1935 1936- (BOOL)resignFirstResponder { 1937 renderWidgetHostView_->SetTextInputActive(false); 1938 if (!renderWidgetHostView_->render_widget_host_) 1939 return YES; 1940 1941 if (closeOnDeactivate_) 1942 renderWidgetHostView_->KillSelf(); 1943 1944 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false); 1945 renderWidgetHostView_->render_widget_host_->Blur(); 1946 1947 // We should cancel any onging composition whenever RWH's Blur() method gets 1948 // called, because in this case, webkit will confirm the ongoing composition 1949 // internally. 1950 [self cancelComposition]; 1951 1952 return YES; 1953} 1954 1955- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { 1956 SEL action = [item action]; 1957 1958 // For now, these actions are always enabled for render view, 1959 // this is sub-optimal. 1960 // TODO(suzhe): Plumb the "can*" methods up from WebCore. 1961 if (action == @selector(undo:) || 1962 action == @selector(redo:) || 1963 action == @selector(cut:) || 1964 action == @selector(copy:) || 1965 action == @selector(copyToFindPboard:) || 1966 action == @selector(paste:) || 1967 action == @selector(pasteAsPlainText:) || 1968 action == @selector(checkSpelling:)) { 1969 return renderWidgetHostView_->render_widget_host_->IsRenderView(); 1970 } 1971 1972 if (action == @selector(toggleContinuousSpellChecking:)) { 1973 RenderViewHost::CommandState state; 1974 state.is_enabled = false; 1975 state.checked_state = RENDER_VIEW_COMMAND_CHECKED_STATE_UNCHECKED; 1976 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 1977 state = static_cast<RenderViewHost*>( 1978 renderWidgetHostView_->render_widget_host_)-> 1979 GetStateForCommand(RENDER_VIEW_COMMAND_TOGGLE_SPELL_CHECK); 1980 } 1981 if ([(id)item respondsToSelector:@selector(setState:)]) { 1982 NSCellStateValue checked_state = 1983 RENDER_VIEW_COMMAND_CHECKED_STATE_UNCHECKED; 1984 switch (state.checked_state) { 1985 case RENDER_VIEW_COMMAND_CHECKED_STATE_UNCHECKED: 1986 checked_state = NSOffState; 1987 break; 1988 case RENDER_VIEW_COMMAND_CHECKED_STATE_CHECKED: 1989 checked_state = NSOnState; 1990 break; 1991 case RENDER_VIEW_COMMAND_CHECKED_STATE_MIXED: 1992 checked_state = NSMixedState; 1993 break; 1994 } 1995 [(id)item setState:checked_state]; 1996 } 1997 return state.is_enabled; 1998 } 1999 2000 return editCommand_helper_->IsMenuItemEnabled(action, self); 2001} 2002 2003- (RenderWidgetHostViewMac*)renderWidgetHostViewMac { 2004 return renderWidgetHostView_.get(); 2005} 2006 2007// Determine whether we should autohide the cursor (i.e., hide it until mouse 2008// move) for the given event. Customize here to be more selective about which 2009// key presses to autohide on. 2010+ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event { 2011 return ([event type] == NSKeyDown) ? YES : NO; 2012} 2013 2014- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute 2015 index:(NSUInteger)index 2016 maxCount:(NSUInteger)maxCount { 2017 NSArray* fullArray = [self accessibilityAttributeValue:attribute]; 2018 NSUInteger totalLength = [fullArray count]; 2019 if (index >= totalLength) 2020 return nil; 2021 NSUInteger length = MIN(totalLength - index, maxCount); 2022 return [fullArray subarrayWithRange:NSMakeRange(index, length)]; 2023} 2024 2025- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute { 2026 NSArray* fullArray = [self accessibilityAttributeValue:attribute]; 2027 return [fullArray count]; 2028} 2029 2030- (id)accessibilityAttributeValue:(NSString *)attribute { 2031 BrowserAccessibilityManager* manager = 2032 renderWidgetHostView_->browser_accessibility_manager_.get(); 2033 2034 // Contents specifies document view of RenderWidgetHostViewCocoa provided by 2035 // BrowserAccessibilityManager. Children includes all subviews in addition to 2036 // contents. Currently we do not have subviews besides the document view. 2037 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] || 2038 [attribute isEqualToString:NSAccessibilityContentsAttribute]) && 2039 manager) { 2040 return [NSArray arrayWithObjects:manager-> 2041 GetRoot()->toBrowserAccessibilityCocoa(), nil]; 2042 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 2043 return NSAccessibilityScrollAreaRole; 2044 } 2045 id ret = [super accessibilityAttributeValue:attribute]; 2046 return ret; 2047} 2048 2049- (NSArray*)accessibilityAttributeNames { 2050 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; 2051 [ret addObject:NSAccessibilityContentsAttribute]; 2052 [ret addObjectsFromArray:[super accessibilityAttributeNames]]; 2053 return ret; 2054} 2055 2056- (id)accessibilityHitTest:(NSPoint)point { 2057 if (!renderWidgetHostView_->browser_accessibility_manager_.get()) 2058 return self; 2059 NSPoint pointInWindow = [[self window] convertScreenToBase:point]; 2060 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil]; 2061 localPoint.y = NSHeight([self bounds]) - localPoint.y; 2062 BrowserAccessibilityCocoa* root = renderWidgetHostView_-> 2063 browser_accessibility_manager_-> 2064 GetRoot()->toBrowserAccessibilityCocoa(); 2065 id obj = [root accessibilityHitTest:localPoint]; 2066 return obj; 2067} 2068 2069- (BOOL)accessibilityIsIgnored { 2070 return NO; 2071} 2072 2073- (NSUInteger)accessibilityIndexOfChild:(id)child { 2074 BrowserAccessibilityManager* manager = 2075 renderWidgetHostView_->browser_accessibility_manager_.get(); 2076 // Only child is root. 2077 if (manager && 2078 manager->GetRoot()->toBrowserAccessibilityCocoa() == child) { 2079 return 0; 2080 } else { 2081 return NSNotFound; 2082 } 2083} 2084 2085- (id)accessibilityFocusedUIElement { 2086 BrowserAccessibilityManager* manager = 2087 renderWidgetHostView_->browser_accessibility_manager_.get(); 2088 if (manager) { 2089 BrowserAccessibility* focused_item = manager->GetFocus(NULL); 2090 DCHECK(focused_item); 2091 if (focused_item) { 2092 BrowserAccessibilityCocoa* focused_item_cocoa = 2093 focused_item->toBrowserAccessibilityCocoa(); 2094 DCHECK(focused_item_cocoa); 2095 if (focused_item_cocoa) 2096 return focused_item_cocoa; 2097 } 2098 } 2099 return [super accessibilityFocusedUIElement]; 2100} 2101 2102- (void)doDefaultAction:(int32)accessibilityObjectId { 2103 renderWidgetHostView_->render_widget_host_-> 2104 AccessibilityDoDefaultAction(accessibilityObjectId); 2105} 2106 2107// Convert a web accessibility's location in web coordinates into a cocoa 2108// screen coordinate. 2109- (NSPoint)accessibilityPointInScreen: 2110 (BrowserAccessibilityCocoa*)accessibility { 2111 NSPoint origin = [accessibility origin]; 2112 NSSize size = [accessibility size]; 2113 origin.y = NSHeight([self bounds]) - origin.y; 2114 NSPoint originInWindow = [self convertPoint:origin toView:nil]; 2115 NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow]; 2116 originInScreen.y = originInScreen.y - size.height; 2117 return originInScreen; 2118} 2119 2120- (void)setAccessibilityFocus:(BOOL)focus 2121 accessibilityId:(int32)accessibilityObjectId { 2122 if (focus) { 2123 renderWidgetHostView_->render_widget_host_-> 2124 SetAccessibilityFocus(accessibilityObjectId); 2125 } 2126} 2127 2128// Spellchecking methods 2129// The next three methods are implemented here since this class is the first 2130// responder for anything in the browser. 2131 2132// This message is sent whenever the user specifies that a word should be 2133// changed from the spellChecker. 2134- (void)changeSpelling:(id)sender { 2135 // Grab the currently selected word from the spell panel, as this is the word 2136 // that we want to replace the selected word in the text with. 2137 NSString* newWord = [[sender selectedCell] stringValue]; 2138 if (newWord != nil) { 2139 RenderWidgetHostViewMac* thisHostView = [self renderWidgetHostViewMac]; 2140 thisHostView->GetRenderWidgetHost()->Replace( 2141 base::SysNSStringToUTF16(newWord)); 2142 } 2143} 2144 2145// This message is sent by NSSpellChecker whenever the next word should be 2146// advanced to, either after a correction or clicking the "Find Next" button. 2147// This isn't documented anywhere useful, like in NSSpellProtocol.h with the 2148// other spelling panel methods. This is probably because Apple assumes that the 2149// the spelling panel will be used with an NSText, which will automatically 2150// catch this and advance to the next word for you. Thanks Apple. 2151// This is also called from the Edit -> Spelling -> Check Spelling menu item. 2152- (void)checkSpelling:(id)sender { 2153 RenderWidgetHostViewMac* thisHostView = [self renderWidgetHostViewMac]; 2154 thisHostView->GetRenderWidgetHost()->AdvanceToNextMisspelling(); 2155} 2156 2157// This message is sent by the spelling panel whenever a word is ignored. 2158- (void)ignoreSpelling:(id)sender { 2159 // Ideally, we would ask the current RenderView for its tag, but that would 2160 // mean making a blocking IPC call from the browser. Instead, 2161 // SpellCheckerPlatform::CheckSpelling remembers the last tag and 2162 // SpellCheckerPlatform::IgnoreWord assumes that is the correct tag. 2163 NSString* wordToIgnore = [sender stringValue]; 2164 if (wordToIgnore != nil) 2165 SpellCheckerPlatform::IgnoreWord(base::SysNSStringToUTF16(wordToIgnore)); 2166} 2167 2168- (void)showGuessPanel:(id)sender { 2169 RenderWidgetHostViewMac* thisHostView = [self renderWidgetHostViewMac]; 2170 thisHostView->GetRenderWidgetHost()->ToggleSpellPanel( 2171 SpellCheckerPlatform::SpellingPanelVisible()); 2172} 2173 2174- (void)toggleContinuousSpellChecking:(id)sender { 2175 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2176 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2177 ToggleSpellCheck(); 2178 } 2179} 2180 2181// END Spellchecking methods 2182 2183// Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm 2184// with minor modifications for code style and commenting. 2185// 2186// The 'public' interface is -setToolTipAtMousePoint:. This differs from 2187// -setToolTip: in that the updated tooltip takes effect immediately, 2188// without the user's having to move the mouse out of and back into the view. 2189// 2190// Unfortunately, doing this requires sending fake mouseEnter/Exit events to 2191// the view, which in turn requires overriding some internal tracking-rect 2192// methods (to keep track of its owner & userdata, which need to be filled out 2193// in the fake events.) --snej 7/6/09 2194 2195 2196/* 2197 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 2198 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com) 2199 * 2200 * Redistribution and use in source and binary forms, with or without 2201 * modification, are permitted provided that the following conditions 2202 * are met: 2203 * 2204 * 1. Redistributions of source code must retain the above copyright 2205 * notice, this list of conditions and the following disclaimer. 2206 * 2. Redistributions in binary form must reproduce the above copyright 2207 * notice, this list of conditions and the following disclaimer in the 2208 * documentation and/or other materials provided with the distribution. 2209 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 2210 * its contributors may be used to endorse or promote products derived 2211 * from this software without specific prior written permission. 2212 * 2213 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 2214 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 2215 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 2216 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 2217 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 2218 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 2219 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 2220 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2221 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2222 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2223 */ 2224 2225// Any non-zero value will do, but using something recognizable might help us 2226// debug some day. 2227static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE; 2228 2229// Override of a public NSView method, replacing the inherited functionality. 2230// See above for rationale. 2231- (NSTrackingRectTag)addTrackingRect:(NSRect)rect 2232 owner:(id)owner 2233 userData:(void *)data 2234 assumeInside:(BOOL)assumeInside { 2235 DCHECK(trackingRectOwner_ == nil); 2236 trackingRectOwner_ = owner; 2237 trackingRectUserData_ = data; 2238 return kTrackingRectTag; 2239} 2240 2241// Override of (apparently) a private NSView method(!) See above for rationale. 2242- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect 2243 owner:(id)owner 2244 userData:(void *)data 2245 assumeInside:(BOOL)assumeInside 2246 useTrackingNum:(int)tag { 2247 DCHECK(tag == 0 || tag == kTrackingRectTag); 2248 DCHECK(trackingRectOwner_ == nil); 2249 trackingRectOwner_ = owner; 2250 trackingRectUserData_ = data; 2251 return kTrackingRectTag; 2252} 2253 2254// Override of (apparently) a private NSView method(!) See above for rationale. 2255- (void)_addTrackingRects:(NSRect *)rects 2256 owner:(id)owner 2257 userDataList:(void **)userDataList 2258 assumeInsideList:(BOOL *)assumeInsideList 2259 trackingNums:(NSTrackingRectTag *)trackingNums 2260 count:(int)count { 2261 DCHECK(count == 1); 2262 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag); 2263 DCHECK(trackingRectOwner_ == nil); 2264 trackingRectOwner_ = owner; 2265 trackingRectUserData_ = userDataList[0]; 2266 trackingNums[0] = kTrackingRectTag; 2267} 2268 2269// Override of a public NSView method, replacing the inherited functionality. 2270// See above for rationale. 2271- (void)removeTrackingRect:(NSTrackingRectTag)tag { 2272 if (tag == 0) 2273 return; 2274 2275 if (tag == kTrackingRectTag) { 2276 trackingRectOwner_ = nil; 2277 return; 2278 } 2279 2280 if (tag == lastToolTipTag_) { 2281 [super removeTrackingRect:tag]; 2282 lastToolTipTag_ = 0; 2283 return; 2284 } 2285 2286 // If any other tracking rect is being removed, we don't know how it was 2287 // created and it's possible there's a leak involved (see Radar 3500217). 2288 NOTREACHED(); 2289} 2290 2291// Override of (apparently) a private NSView method(!) 2292- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count { 2293 for (int i = 0; i < count; ++i) { 2294 int tag = tags[i]; 2295 if (tag == 0) 2296 continue; 2297 DCHECK(tag == kTrackingRectTag); 2298 trackingRectOwner_ = nil; 2299 } 2300} 2301 2302// Sends a fake NSMouseExited event to the view for its current tracking rect. 2303- (void)_sendToolTipMouseExited { 2304 // Nothing matters except window, trackingNumber, and userData. 2305 int windowNumber = [[self window] windowNumber]; 2306 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited 2307 location:NSMakePoint(0, 0) 2308 modifierFlags:0 2309 timestamp:0 2310 windowNumber:windowNumber 2311 context:NULL 2312 eventNumber:0 2313 trackingNumber:kTrackingRectTag 2314 userData:trackingRectUserData_]; 2315 [trackingRectOwner_ mouseExited:fakeEvent]; 2316} 2317 2318// Sends a fake NSMouseEntered event to the view for its current tracking rect. 2319- (void)_sendToolTipMouseEntered { 2320 // Nothing matters except window, trackingNumber, and userData. 2321 int windowNumber = [[self window] windowNumber]; 2322 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered 2323 location:NSMakePoint(0, 0) 2324 modifierFlags:0 2325 timestamp:0 2326 windowNumber:windowNumber 2327 context:NULL 2328 eventNumber:0 2329 trackingNumber:kTrackingRectTag 2330 userData:trackingRectUserData_]; 2331 [trackingRectOwner_ mouseEntered:fakeEvent]; 2332} 2333 2334// Sets the view's current tooltip, to be displayed at the current mouse 2335// location. (This does not make the tooltip appear -- as usual, it only 2336// appears after a delay.) Pass null to remove the tooltip. 2337- (void)setToolTipAtMousePoint:(NSString *)string { 2338 NSString *toolTip = [string length] == 0 ? nil : string; 2339 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) || 2340 (!toolTip && !toolTip_)) { 2341 return; 2342 } 2343 2344 if (toolTip_) { 2345 [self _sendToolTipMouseExited]; 2346 } 2347 2348 toolTip_.reset([toolTip copy]); 2349 2350 if (toolTip) { 2351 // See radar 3500217 for why we remove all tooltips 2352 // rather than just the single one we created. 2353 [self removeAllToolTips]; 2354 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000); 2355 lastToolTipTag_ = [self addToolTipRect:wideOpenRect 2356 owner:self 2357 userData:NULL]; 2358 [self _sendToolTipMouseEntered]; 2359 } 2360} 2361 2362// NSView calls this to get the text when displaying the tooltip. 2363- (NSString *)view:(NSView *)view 2364 stringForToolTip:(NSToolTipTag)tag 2365 point:(NSPoint)point 2366 userData:(void *)data { 2367 return [[toolTip_ copy] autorelease]; 2368} 2369 2370// Below is our NSTextInput implementation. 2371// 2372// When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following 2373// functions to process this event. 2374// 2375// [WebHTMLView keyDown] -> 2376// EventHandler::keyEvent() -> 2377// ... 2378// [WebEditorClient handleKeyboardEvent] -> 2379// [WebHTMLView _interceptEditingKeyEvent] -> 2380// [NSResponder interpretKeyEvents] -> 2381// [WebHTMLView insertText] -> 2382// Editor::insertText() 2383// 2384// Unfortunately, it is hard for Chromium to use this implementation because 2385// it causes key-typing jank. 2386// RenderWidgetHostViewMac is running in a browser process. On the other 2387// hand, Editor and EventHandler are running in a renderer process. 2388// So, if we used this implementation, a NSKeyDown event is dispatched to 2389// the following functions of Chromium. 2390// 2391// [RenderWidgetHostViewMac keyEvent] (browser) -> 2392// |Sync IPC (KeyDown)| (*1) -> 2393// EventHandler::keyEvent() (renderer) -> 2394// ... 2395// EditorClientImpl::handleKeyboardEvent() (renderer) -> 2396// |Sync IPC| (*2) -> 2397// [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) -> 2398// [self interpretKeyEvents] -> 2399// [RenderWidgetHostViewMac insertText] (browser) -> 2400// |Async IPC| -> 2401// Editor::insertText() (renderer) 2402// 2403// (*1) we need to wait until this call finishes since WebHTMLView uses the 2404// result of EventHandler::keyEvent(). 2405// (*2) we need to wait until this call finishes since WebEditorClient uses 2406// the result of [WebHTMLView _interceptEditingKeyEvent]. 2407// 2408// This needs many sync IPC messages sent between a browser and a renderer for 2409// each key event, which would probably result in key-typing jank. 2410// To avoid this problem, this implementation processes key events (and input 2411// method events) totally in a browser process and sends asynchronous input 2412// events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a 2413// renderer process. 2414// 2415// [RenderWidgetHostViewMac keyEvent] (browser) -> 2416// |Async IPC (RawKeyDown)| -> 2417// [self interpretKeyEvents] -> 2418// [RenderWidgetHostViewMac insertText] (browser) -> 2419// |Async IPC (Char)| -> 2420// Editor::insertText() (renderer) 2421// 2422// Since this implementation doesn't have to wait any IPC calls, this doesn't 2423// make any key-typing jank. --hbono 7/23/09 2424// 2425extern "C" { 2426extern NSString *NSTextInputReplacementRangeAttributeName; 2427} 2428 2429- (NSArray *)validAttributesForMarkedText { 2430 // This code is just copied from WebKit except renaming variables. 2431 if (!validAttributesForMarkedText_) { 2432 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects: 2433 NSUnderlineStyleAttributeName, 2434 NSUnderlineColorAttributeName, 2435 NSMarkedClauseSegmentAttributeName, 2436 NSTextInputReplacementRangeAttributeName, 2437 nil]); 2438 } 2439 return validAttributesForMarkedText_.get(); 2440} 2441 2442- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { 2443 NOTIMPLEMENTED(); 2444 return NSNotFound; 2445} 2446 2447- (NSRect)firstRectForCharacterRange:(NSRange)theRange { 2448 // An input method requests a cursor rectangle to display its candidate 2449 // window. 2450 // Calculate the screen coordinate of the cursor rectangle saved in 2451 // RenderWidgetHostViewMac::ImeUpdateTextInputState() and send it to the 2452 // input method. 2453 // Since this window may be moved since we receive the cursor rectangle last 2454 // time we sent the cursor rectangle to the input method, so we should map 2455 // from the view coordinate to the screen coordinate every time when an input 2456 // method need it. 2457 NSRect resultRect = [self convertRect:caretRect_ toView:nil]; 2458 NSWindow* window = [self window]; 2459 if (window) 2460 resultRect.origin = [window convertBaseToScreen:resultRect.origin]; 2461 2462 return resultRect; 2463} 2464 2465- (NSRange)selectedRange { 2466 // Return the selected range saved in the setMarkedText method. 2467 return hasMarkedText_ ? selectedRange_ : NSMakeRange(NSNotFound, 0); 2468} 2469 2470- (NSRange)markedRange { 2471 // An input method calls this method to check if an application really has 2472 // a text being composed when hasMarkedText call returns true. 2473 // Returns the range saved in the setMarkedText method so the input method 2474 // calls the setMarkedText method and we can update the composition node 2475 // there. (When this method returns an empty range, the input method doesn't 2476 // call the setMarkedText method.) 2477 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0); 2478} 2479 2480- (NSAttributedString *)attributedSubstringFromRange:(NSRange)range { 2481 // TODO(hbono): Even though many input method works without implementing 2482 // this method, we need to save a copy of the string in the setMarkedText 2483 // method and create a NSAttributedString with the given range. 2484 // http://crbug.com/37715 2485 return nil; 2486} 2487 2488- (NSInteger)conversationIdentifier { 2489 return reinterpret_cast<NSInteger>(self); 2490} 2491 2492// Each RenderWidgetHostViewCocoa has its own input context, but we return 2493// nil when the caret is in non-editable content or password box to avoid 2494// making input methods do their work. 2495- (NSTextInputContext *)inputContext { 2496 if (focusedPluginIdentifier_ != -1) 2497 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext]; 2498 2499 switch(renderWidgetHostView_->text_input_type_) { 2500 case WebKit::WebTextInputTypeNone: 2501 case WebKit::WebTextInputTypePassword: 2502 return nil; 2503 default: 2504 return [super inputContext]; 2505 } 2506} 2507 2508- (BOOL)hasMarkedText { 2509 // An input method calls this function to figure out whether or not an 2510 // application is really composing a text. If it is composing, it calls 2511 // the markedRange method, and maybe calls the setMarkedText method. 2512 // It seems an input method usually calls this function when it is about to 2513 // cancel an ongoing composition. If an application has a non-empty marked 2514 // range, it calls the setMarkedText method to delete the range. 2515 return hasMarkedText_; 2516} 2517 2518- (void)unmarkText { 2519 // Delete the composition node of the renderer and finish an ongoing 2520 // composition. 2521 // It seems an input method calls the setMarkedText method and set an empty 2522 // text when it cancels an ongoing composition, i.e. I have never seen an 2523 // input method calls this method. 2524 hasMarkedText_ = NO; 2525 markedText_.clear(); 2526 underlines_.clear(); 2527 2528 // If we are handling a key down event, then ConfirmComposition() will be 2529 // called in keyEvent: method. 2530 if (!handlingKeyDown_) 2531 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(); 2532 else 2533 unmarkTextCalled_ = YES; 2534} 2535 2536- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange { 2537 // An input method updates the composition string. 2538 // We send the given text and range to the renderer so it can update the 2539 // composition node of WebKit. 2540 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; 2541 NSString* im_text = isAttributedString ? [string string] : string; 2542 int length = [im_text length]; 2543 2544 markedRange_ = NSMakeRange(0, length); 2545 selectedRange_ = newSelRange; 2546 markedText_ = base::SysNSStringToUTF16(im_text); 2547 hasMarkedText_ = (length > 0); 2548 2549 underlines_.clear(); 2550 if (isAttributedString) { 2551 ExtractUnderlines(string, &underlines_); 2552 } else { 2553 // Use a thin black underline by default. 2554 underlines_.push_back( 2555 WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false)); 2556 } 2557 2558 // If we are handling a key down event, then SetComposition() will be 2559 // called in keyEvent: method. 2560 // Input methods of Mac use setMarkedText calls with an empty text to cancel 2561 // an ongoing composition. So, we should check whether or not the given text 2562 // is empty to update the input method state. (Our input method backend can 2563 // automatically cancels an ongoing composition when we send an empty text. 2564 // So, it is OK to send an empty text to the renderer.) 2565 if (!handlingKeyDown_) { 2566 renderWidgetHostView_->render_widget_host_->ImeSetComposition( 2567 markedText_, underlines_, 2568 newSelRange.location, NSMaxRange(newSelRange)); 2569 } 2570} 2571 2572- (void)doCommandBySelector:(SEL)selector { 2573 // An input method calls this function to dispatch an editing command to be 2574 // handled by this view. 2575 if (selector == @selector(noop:)) 2576 return; 2577 2578 std::string command( 2579 [RWHVMEditCommandHelper::CommandNameForSelector(selector) UTF8String]); 2580 2581 // If this method is called when handling a key down event, then we need to 2582 // handle the command in the key event handler. Otherwise we can just handle 2583 // it here. 2584 if (handlingKeyDown_) { 2585 hasEditCommands_ = YES; 2586 // We ignore commands that insert characters, because this was causing 2587 // strange behavior (e.g. tab always inserted a tab rather than moving to 2588 // the next field on the page). 2589 if (!StartsWithASCII(command, "insert", false)) 2590 editCommands_.push_back(EditCommand(command, "")); 2591 } else { 2592 renderWidgetHostView_->render_widget_host_->ForwardEditCommand(command, ""); 2593 } 2594} 2595 2596- (void)insertText:(id)string { 2597 // An input method has characters to be inserted. 2598 // Same as Linux, Mac calls this method not only: 2599 // * when an input method finishs composing text, but also; 2600 // * when we type an ASCII character (without using input methods). 2601 // When we aren't using input methods, we should send the given character as 2602 // a Char event so it is dispatched to an onkeypress() event handler of 2603 // JavaScript. 2604 // On the other hand, when we are using input methods, we should send the 2605 // given characters as an input method event and prevent the characters from 2606 // being dispatched to onkeypress() event handlers. 2607 // Text inserting might be initiated by other source instead of keyboard 2608 // events, such as the Characters dialog. In this case the text should be 2609 // sent as an input method event as well. 2610 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; 2611 NSString* im_text = isAttributedString ? [string string] : string; 2612 if (handlingKeyDown_) { 2613 textToBeInserted_.append(base::SysNSStringToUTF16(im_text)); 2614 } else { 2615 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition( 2616 base::SysNSStringToUTF16(im_text)); 2617 } 2618 2619 // Inserting text will delete all marked text automatically. 2620 hasMarkedText_ = NO; 2621} 2622 2623- (void)viewDidMoveToWindow { 2624 if (canBeKeyView_) { 2625 NSWindow* newWindow = [self window]; 2626 // Pointer comparison only, since we don't know if lastWindow_ is still 2627 // valid. 2628 if (newWindow) { 2629 // If we move into a new window, refresh the frame information. We 2630 // don't need to do it if it was the same window as it used to be in, 2631 // since that case is covered by DidBecomeSelected. We only want to 2632 // do this for real browser views, not popups. 2633 if (newWindow != lastWindow_) { 2634 lastWindow_ = newWindow; 2635 renderWidgetHostView_->WindowFrameChanged(); 2636 } 2637 renderWidgetHostView_->ForceTextureReload(); 2638 } 2639 } 2640 2641 // If we switch windows (or are removed from the view hierarchy), cancel any 2642 // open mouse-downs. 2643 if (hasOpenMouseDown_) { 2644 WebMouseEvent event; 2645 event.type = WebInputEvent::MouseUp; 2646 event.button = WebMouseEvent::ButtonLeft; 2647 if (renderWidgetHostView_->render_widget_host_) 2648 renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(event); 2649 2650 hasOpenMouseDown_ = NO; 2651 } 2652} 2653 2654- (void)undo:(id)sender { 2655 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2656 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2657 Undo(); 2658 } 2659} 2660 2661- (void)redo:(id)sender { 2662 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2663 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2664 Redo(); 2665 } 2666} 2667 2668- (void)cut:(id)sender { 2669 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2670 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2671 Cut(); 2672 } 2673} 2674 2675- (void)copy:(id)sender { 2676 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2677 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2678 Copy(); 2679 } 2680} 2681 2682- (void)copyToFindPboard:(id)sender { 2683 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2684 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2685 CopyToFindPboard(); 2686 } 2687} 2688 2689- (void)paste:(id)sender { 2690 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2691 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2692 Paste(); 2693 } 2694} 2695 2696- (void)pasteAsPlainText:(id)sender { 2697 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2698 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2699 ForwardEditCommand("PasteAndMatchStyle", ""); 2700 } 2701} 2702 2703- (void)cancelComposition { 2704 if (!hasMarkedText_) 2705 return; 2706 2707 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:] 2708 // doesn't call any NSTextInput functions, such as setMarkedText or 2709 // insertText. So, we need to send an IPC message to a renderer so it can 2710 // delete the composition node. 2711 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; 2712 [currentInputManager markedTextAbandoned:self]; 2713 2714 hasMarkedText_ = NO; 2715 // Should not call [self unmarkText] here, because it'll send unnecessary 2716 // cancel composition IPC message to the renderer. 2717} 2718 2719- (void)confirmComposition { 2720 if (!hasMarkedText_) 2721 return; 2722 2723 if (renderWidgetHostView_->render_widget_host_) 2724 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(); 2725 2726 [self cancelComposition]; 2727} 2728 2729- (void)setPluginImeActive:(BOOL)active { 2730 if (active == pluginImeActive_) 2731 return; 2732 2733 pluginImeActive_ = active; 2734 if (!active) { 2735 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition]; 2736 renderWidgetHostView_->PluginImeCompositionCompleted( 2737 string16(), focusedPluginIdentifier_); 2738 } 2739} 2740 2741- (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId { 2742 if (focused) 2743 focusedPluginIdentifier_ = pluginId; 2744 else if (focusedPluginIdentifier_ == pluginId) 2745 focusedPluginIdentifier_ = -1; 2746 2747 // Whenever plugin focus changes, plugin IME resets. 2748 [self setPluginImeActive:NO]; 2749} 2750 2751- (BOOL)postProcessEventForPluginIme:(NSEvent*)event { 2752 if (!pluginImeActive_) 2753 return false; 2754 2755 ComplexTextInputPanel* inputPanel = 2756 [ComplexTextInputPanel sharedComplexTextInputPanel]; 2757 NSString* composited_string = nil; 2758 BOOL handled = [inputPanel interpretKeyEvent:event 2759 string:&composited_string]; 2760 if (composited_string) { 2761 renderWidgetHostView_->PluginImeCompositionCompleted( 2762 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_); 2763 pluginImeActive_ = NO; 2764 } 2765 return handled; 2766} 2767 2768- (void)checkForPluginImeCancellation { 2769 if (pluginImeActive_ && 2770 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) { 2771 renderWidgetHostView_->PluginImeCompositionCompleted( 2772 string16(), focusedPluginIdentifier_); 2773 pluginImeActive_ = NO; 2774 } 2775} 2776 2777- (ViewID)viewID { 2778 return VIEW_ID_TAB_CONTAINER_FOCUS_VIEW; 2779} 2780 2781// Overriding a NSResponder method to support application services. 2782 2783- (id)validRequestorForSendType:(NSString*)sendType 2784 returnType:(NSString*)returnType { 2785 id requestor = nil; 2786 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType]; 2787 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType]; 2788 BOOL hasText = !renderWidgetHostView_->selected_text().empty(); 2789 BOOL takesText = 2790 renderWidgetHostView_->text_input_type_ != WebKit::WebTextInputTypeNone; 2791 2792 if (sendTypeIsString && hasText && !returnType) { 2793 requestor = self; 2794 } else if (!sendType && returnTypeIsString && takesText) { 2795 requestor = self; 2796 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) { 2797 requestor = self; 2798 } else { 2799 requestor = [super validRequestorForSendType:sendType 2800 returnType:returnType]; 2801 } 2802 return requestor; 2803} 2804 2805@end 2806 2807// 2808// Supporting application services 2809// 2810@implementation RenderWidgetHostViewCocoa(NSServicesRequests) 2811 2812- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard 2813 types:(NSArray*)types { 2814 const std::string& str = renderWidgetHostView_->selected_text(); 2815 if (![types containsObject:NSStringPboardType] || str.empty()) return NO; 2816 2817 scoped_nsobject<NSString> text([[NSString alloc] 2818 initWithUTF8String:str.c_str()]); 2819 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType]; 2820 [pboard declareTypes:toDeclare owner:nil]; 2821 return [pboard setString:text forType:NSStringPboardType]; 2822} 2823 2824- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard { 2825 NSString *string = [pboard stringForType:NSStringPboardType]; 2826 if (!string) return NO; 2827 2828 // If the user is currently using an IME, confirm the IME input, 2829 // and then insert the text from the service, the same as TextEdit and Safari. 2830 [self confirmComposition]; 2831 [self insertText:string]; 2832 return YES; 2833} 2834 2835@end 2836