1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/browser/renderer_host/render_widget_host_view_mac.h" 6 7#import <objc/runtime.h> 8#include <OpenGL/gl.h> 9#include <QuartzCore/QuartzCore.h> 10 11#include "base/basictypes.h" 12#include "base/bind.h" 13#include "base/callback_helpers.h" 14#include "base/command_line.h" 15#include "base/debug/crash_logging.h" 16#include "base/debug/trace_event.h" 17#include "base/logging.h" 18#include "base/mac/mac_util.h" 19#include "base/mac/scoped_cftyperef.h" 20#import "base/mac/scoped_nsobject.h" 21#include "base/mac/sdk_forward_declarations.h" 22#include "base/message_loop/message_loop.h" 23#include "base/metrics/histogram.h" 24#include "base/strings/string_util.h" 25#include "base/strings/stringprintf.h" 26#include "base/strings/sys_string_conversions.h" 27#include "base/strings/utf_string_conversions.h" 28#include "base/sys_info.h" 29#import "content/browser/accessibility/browser_accessibility_cocoa.h" 30#include "content/browser/accessibility/browser_accessibility_manager_mac.h" 31#import "content/browser/cocoa/system_hotkey_helper_mac.h" 32#import "content/browser/cocoa/system_hotkey_map.h" 33#include "content/browser/compositor/io_surface_layer_mac.h" 34#include "content/browser/compositor/resize_lock.h" 35#include "content/browser/compositor/software_layer_mac.h" 36#include "content/browser/frame_host/frame_tree.h" 37#include "content/browser/frame_host/frame_tree_node.h" 38#include "content/browser/frame_host/render_frame_host_impl.h" 39#include "content/browser/gpu/compositor_util.h" 40#include "content/browser/renderer_host/render_widget_helper.h" 41#include "content/browser/renderer_host/render_view_host_impl.h" 42#import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h" 43#import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h" 44#import "content/browser/renderer_host/text_input_client_mac.h" 45#include "content/common/accessibility_messages.h" 46#include "content/common/edit_command.h" 47#include "content/common/gpu/gpu_messages.h" 48#include "content/common/gpu/surface_handle_types_mac.h" 49#include "content/common/input_messages.h" 50#include "content/common/view_messages.h" 51#include "content/common/webplugin_geometry.h" 52#include "content/public/browser/browser_thread.h" 53#include "content/public/browser/native_web_keyboard_event.h" 54#include "content/public/browser/notification_service.h" 55#include "content/public/browser/notification_types.h" 56#include "content/public/browser/render_widget_host_view_frame_subscriber.h" 57#import "content/public/browser/render_widget_host_view_mac_delegate.h" 58#include "content/public/browser/user_metrics.h" 59#include "content/public/browser/web_contents.h" 60#include "skia/ext/platform_canvas.h" 61#include "third_party/WebKit/public/platform/WebScreenInfo.h" 62#include "third_party/WebKit/public/web/WebInputEvent.h" 63#include "third_party/WebKit/public/web/mac/WebInputEventFactory.h" 64#import "third_party/mozilla/ComplexTextInputPanel.h" 65#include "ui/base/cocoa/animation_utils.h" 66#import "ui/base/cocoa/fullscreen_window_manager.h" 67#import "ui/base/cocoa/underlay_opengl_hosting_window.h" 68#include "ui/events/keycodes/keyboard_codes.h" 69#include "ui/base/layout.h" 70#include "ui/compositor/compositor.h" 71#include "ui/compositor/layer.h" 72#include "ui/gfx/display.h" 73#include "ui/gfx/frame_time.h" 74#include "ui/gfx/point.h" 75#include "ui/gfx/rect_conversions.h" 76#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 77#include "ui/gfx/screen.h" 78#include "ui/gfx/size_conversions.h" 79#include "ui/gl/gl_switches.h" 80 81using content::BrowserAccessibility; 82using content::BrowserAccessibilityManager; 83using content::EditCommand; 84using content::FrameTreeNode; 85using content::NativeWebKeyboardEvent; 86using content::RenderFrameHost; 87using content::RenderViewHost; 88using content::RenderViewHostImpl; 89using content::RenderWidgetHostImpl; 90using content::RenderWidgetHostViewMac; 91using content::RenderWidgetHostViewMacEditCommandHelper; 92using content::TextInputClientMac; 93using content::WebContents; 94using blink::WebInputEvent; 95using blink::WebInputEventFactory; 96using blink::WebMouseEvent; 97using blink::WebMouseWheelEvent; 98using blink::WebGestureEvent; 99 100namespace { 101 102// Whether a keyboard event has been reserved by OSX. 103BOOL EventIsReservedBySystem(NSEvent* event) { 104 content::SystemHotkeyHelperMac* helper = 105 content::SystemHotkeyHelperMac::GetInstance(); 106 return helper->map()->IsEventReserved(event); 107} 108 109} // namespace 110 111// These are not documented, so use only after checking -respondsToSelector:. 112@interface NSApplication (UndocumentedSpeechMethods) 113- (void)speakString:(NSString*)string; 114- (void)stopSpeaking:(id)sender; 115- (BOOL)isSpeaking; 116@end 117 118// Declare things that are part of the 10.7 SDK. 119#if !defined(MAC_OS_X_VERSION_10_7) || \ 120 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 121 122static NSString* const NSWindowDidChangeBackingPropertiesNotification = 123 @"NSWindowDidChangeBackingPropertiesNotification"; 124 125#endif // 10.7 126 127// This method will return YES for OS X versions 10.7.3 and later, and NO 128// otherwise. 129// Used to prevent a crash when building with the 10.7 SDK and accessing the 130// notification below. See: http://crbug.com/260595. 131static BOOL SupportsBackingPropertiesChangedNotification() { 132 // windowDidChangeBackingProperties: method has been added to the 133 // NSWindowDelegate protocol in 10.7.3, at the same time as the 134 // NSWindowDidChangeBackingPropertiesNotification notification was added. 135 // If the protocol contains this method description, the notification should 136 // be supported as well. 137 Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate"); 138 struct objc_method_description methodDescription = 139 protocol_getMethodDescription( 140 windowDelegateProtocol, 141 @selector(windowDidChangeBackingProperties:), 142 NO, 143 YES); 144 145 // If the protocol does not contain the method, the returned method 146 // description is {NULL, NULL} 147 return methodDescription.name != NULL || methodDescription.types != NULL; 148} 149 150// Private methods: 151@interface RenderWidgetHostViewCocoa () 152@property(nonatomic, assign) NSRange selectedRange; 153@property(nonatomic, assign) NSRange markedRange; 154 155+ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event; 156- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r; 157- (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event 158 consumed:(BOOL)consumed; 159 160- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv; 161- (void)windowDidChangeBackingProperties:(NSNotification*)notification; 162- (void)windowChangedGlobalFrame:(NSNotification*)notification; 163- (void)checkForPluginImeCancellation; 164- (void)updateScreenProperties; 165- (void)setResponderDelegate: 166 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate; 167@end 168 169// A window subclass that allows the fullscreen window to become main and gain 170// keyboard focus. This is only used for pepper flash. Normal fullscreen is 171// handled by the browser. 172@interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow 173@end 174 175@implementation PepperFlashFullscreenWindow 176 177- (BOOL)canBecomeKeyWindow { 178 return YES; 179} 180 181- (BOOL)canBecomeMainWindow { 182 return YES; 183} 184 185@end 186 187@interface RenderWidgetPopupWindow : NSWindow { 188 // The event tap that allows monitoring of all events, to properly close with 189 // a click outside the bounds of the window. 190 id clickEventTap_; 191} 192@end 193 194@implementation RenderWidgetPopupWindow 195 196- (id)initWithContentRect:(NSRect)contentRect 197 styleMask:(NSUInteger)windowStyle 198 backing:(NSBackingStoreType)bufferingType 199 defer:(BOOL)deferCreation { 200 if (self = [super initWithContentRect:contentRect 201 styleMask:windowStyle 202 backing:bufferingType 203 defer:deferCreation]) { 204 [self setOpaque:NO]; 205 [self setBackgroundColor:[NSColor clearColor]]; 206 [self startObservingClicks]; 207 } 208 return self; 209} 210 211- (void)close { 212 [self stopObservingClicks]; 213 [super close]; 214} 215 216// Gets called when the menubar is clicked. 217// Needed because the local event monitor doesn't see the click on the menubar. 218- (void)beganTracking:(NSNotification*)notification { 219 [self close]; 220} 221 222// Install the callback. 223- (void)startObservingClicks { 224 clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask 225 handler:^NSEvent* (NSEvent* event) { 226 if ([event window] == self) 227 return event; 228 NSEventType eventType = [event type]; 229 if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown) 230 [self close]; 231 return event; 232 }]; 233 234 NSNotificationCenter* notificationCenter = 235 [NSNotificationCenter defaultCenter]; 236 [notificationCenter addObserver:self 237 selector:@selector(beganTracking:) 238 name:NSMenuDidBeginTrackingNotification 239 object:[NSApp mainMenu]]; 240} 241 242// Remove the callback. 243- (void)stopObservingClicks { 244 if (!clickEventTap_) 245 return; 246 247 [NSEvent removeMonitor:clickEventTap_]; 248 clickEventTap_ = nil; 249 250 NSNotificationCenter* notificationCenter = 251 [NSNotificationCenter defaultCenter]; 252 [notificationCenter removeObserver:self 253 name:NSMenuDidBeginTrackingNotification 254 object:[NSApp mainMenu]]; 255} 256 257@end 258 259namespace { 260 261// Maximum number of characters we allow in a tooltip. 262const size_t kMaxTooltipLength = 1024; 263 264// TODO(suzhe): Upstream this function. 265blink::WebColor WebColorFromNSColor(NSColor *color) { 266 CGFloat r, g, b, a; 267 [color getRed:&r green:&g blue:&b alpha:&a]; 268 269 return 270 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 | 271 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 | 272 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 | 273 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255)); 274} 275 276// Extract underline information from an attributed string. Mostly copied from 277// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm 278void ExtractUnderlines( 279 NSAttributedString* string, 280 std::vector<blink::WebCompositionUnderline>* underlines) { 281 int length = [[string string] length]; 282 int i = 0; 283 while (i < length) { 284 NSRange range; 285 NSDictionary* attrs = [string attributesAtIndex:i 286 longestEffectiveRange:&range 287 inRange:NSMakeRange(i, length - i)]; 288 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { 289 blink::WebColor color = SK_ColorBLACK; 290 if (NSColor *colorAttr = 291 [attrs objectForKey:NSUnderlineColorAttributeName]) { 292 color = WebColorFromNSColor( 293 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); 294 } 295 underlines->push_back( 296 blink::WebCompositionUnderline(range.location, 297 NSMaxRange(range), 298 color, 299 [style intValue] > 1, 300 SK_ColorTRANSPARENT)); 301 } 302 i = range.location + range.length; 303 } 304} 305 306// EnablePasswordInput() and DisablePasswordInput() are copied from 307// enableSecureTextInput() and disableSecureTextInput() functions in 308// third_party/WebKit/WebCore/platform/SecureTextInput.cpp 309// But we don't call EnableSecureEventInput() and DisableSecureEventInput() 310// here, because they are already called in webkit and they are system wide 311// functions. 312void EnablePasswordInput() { 313 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); 314 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag, 315 sizeof(CFArrayRef), &inputSources); 316 CFRelease(inputSources); 317} 318 319void DisablePasswordInput() { 320 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag); 321} 322 323// Calls to [NSScreen screens], required by FlipYFromRectToScreen and 324// FlipNSRectToRectScreen, can take several milliseconds. Only re-compute this 325// value when screen info changes. 326// TODO(ccameron): An observer on every RWHVCocoa will set this to false 327// on NSApplicationDidChangeScreenParametersNotification. Only one observer 328// is necessary. 329bool g_screen_info_up_to_date = false; 330 331float FlipYFromRectToScreen(float y, float rect_height) { 332 TRACE_EVENT0("browser", "FlipYFromRectToScreen"); 333 static CGFloat screen_zero_height = 0; 334 if (!g_screen_info_up_to_date) { 335 if ([[NSScreen screens] count] > 0) { 336 screen_zero_height = 337 [[[NSScreen screens] objectAtIndex:0] frame].size.height; 338 g_screen_info_up_to_date = true; 339 } else { 340 return y; 341 } 342 } 343 return screen_zero_height - y - rect_height; 344} 345 346// Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper 347// left of the primary screen (Carbon coordinates), and stuffs it into a 348// gfx::Rect. 349gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) { 350 gfx::Rect new_rect(NSRectToCGRect(rect)); 351 new_rect.set_y(FlipYFromRectToScreen(new_rect.y(), new_rect.height())); 352 return new_rect; 353} 354 355// Returns the window that visually contains the given view. This is different 356// from [view window] in the case of tab dragging, where the view's owning 357// window is a floating panel attached to the actual browser window that the tab 358// is visually part of. 359NSWindow* ApparentWindowForView(NSView* view) { 360 // TODO(shess): In case of !window, the view has been removed from 361 // the view hierarchy because the tab isn't main. Could retrieve 362 // the information from the main tab for our window. 363 NSWindow* enclosing_window = [view window]; 364 365 // See if this is a tab drag window. The width check is to distinguish that 366 // case from extension popup windows. 367 NSWindow* ancestor_window = [enclosing_window parentWindow]; 368 if (ancestor_window && (NSWidth([enclosing_window frame]) == 369 NSWidth([ancestor_window frame]))) { 370 enclosing_window = ancestor_window; 371 } 372 373 return enclosing_window; 374} 375 376blink::WebScreenInfo GetWebScreenInfo(NSView* view) { 377 gfx::Display display = 378 gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view); 379 380 NSScreen* screen = [NSScreen deepestScreen]; 381 382 blink::WebScreenInfo results; 383 384 results.deviceScaleFactor = static_cast<int>(display.device_scale_factor()); 385 results.depth = NSBitsPerPixelFromDepth([screen depth]); 386 results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]); 387 results.isMonochrome = 388 [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel; 389 results.rect = display.bounds(); 390 results.availableRect = display.work_area(); 391 results.orientationAngle = display.RotationAsDegree(); 392 results.orientationType = 393 content::RenderWidgetHostViewBase::GetOrientationTypeForDesktop(display); 394 395 return results; 396} 397 398} // namespace 399 400namespace content { 401 402//////////////////////////////////////////////////////////////////////////////// 403// DelegatedFrameHost, public: 404 405ui::Compositor* RenderWidgetHostViewMac::GetCompositor() const { 406 if (browser_compositor_view_) 407 return browser_compositor_view_->GetCompositor(); 408 return NULL; 409} 410 411ui::Layer* RenderWidgetHostViewMac::GetLayer() { 412 return root_layer_.get(); 413} 414 415RenderWidgetHostImpl* RenderWidgetHostViewMac::GetHost() { 416 return render_widget_host_; 417} 418 419bool RenderWidgetHostViewMac::IsVisible() { 420 return !render_widget_host_->is_hidden(); 421} 422 423gfx::Size RenderWidgetHostViewMac::DesiredFrameSize() { 424 return GetViewBounds().size(); 425} 426 427float RenderWidgetHostViewMac::CurrentDeviceScaleFactor() { 428 return ViewScaleFactor(); 429} 430 431gfx::Size RenderWidgetHostViewMac::ConvertViewSizeToPixel( 432 const gfx::Size& size) { 433 return gfx::ToEnclosingRect(gfx::ScaleRect(gfx::Rect(size), 434 ViewScaleFactor())).size(); 435} 436 437scoped_ptr<ResizeLock> RenderWidgetHostViewMac::CreateResizeLock( 438 bool defer_compositor_lock) { 439 NOTREACHED(); 440 ResizeLock* lock = NULL; 441 return scoped_ptr<ResizeLock>(lock); 442} 443 444DelegatedFrameHost* RenderWidgetHostViewMac::GetDelegatedFrameHost() const { 445 return delegated_frame_host_.get(); 446} 447 448//////////////////////////////////////////////////////////////////////////////// 449// BrowserCompositorViewMacClient, public: 450 451bool RenderWidgetHostViewMac::BrowserCompositorViewShouldAckImmediately() 452 const { 453 // If vsync is disabled, then always draw and ack frames immediately. 454 static bool is_vsync_disabled = 455 base::CommandLine::ForCurrentProcess()->HasSwitch( 456 switches::kDisableGpuVsync); 457 if (is_vsync_disabled) 458 return true; 459 460 // If the window is occluded, then this frame's display call may be severely 461 // throttled. This is a good thing, unless tab capture may be active, because 462 // the broadcast will be inappropriately throttled. 463 // http://crbug.com/350410 464 465 // If tab capture isn't active then only ack frames when we draw them. 466 if (delegated_frame_host_ && !delegated_frame_host_->HasFrameSubscriber()) 467 return false; 468 469 NSWindow* window = [cocoa_view_ window]; 470 // If the view isn't even in the heirarchy then frames will never be drawn, 471 // so ack them immediately. 472 if (!window) 473 return true; 474 475 // Check the window occlusion API. 476 if ([window respondsToSelector:@selector(occlusionState)]) { 477 if ([window occlusionState] & NSWindowOcclusionStateVisible) { 478 // If the window is visible then it is safe to wait until frames are 479 // drawn to ack them. 480 return false; 481 } else { 482 // If the window is occluded then frames may never be drawn, so ack them 483 // immediately. 484 return true; 485 } 486 } 487 488 // If the window occlusion API is not present then ack frames when we draw 489 // them. 490 return false; 491} 492 493void RenderWidgetHostViewMac::BrowserCompositorViewFrameSwapped( 494 const std::vector<ui::LatencyInfo>& all_latency_info) { 495 if (!render_widget_host_) 496 return; 497 for (auto latency_info : all_latency_info) { 498 latency_info.AddLatencyNumber( 499 ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0); 500 render_widget_host_->FrameSwapped(latency_info); 501 } 502} 503 504NSView* RenderWidgetHostViewMac::BrowserCompositorSuperview() { 505 return cocoa_view_; 506} 507 508ui::Layer* RenderWidgetHostViewMac::BrowserCompositorRootLayer() { 509 return root_layer_.get(); 510} 511 512/////////////////////////////////////////////////////////////////////////////// 513// RenderWidgetHostViewBase, public: 514 515// static 516void RenderWidgetHostViewBase::GetDefaultScreenInfo( 517 blink::WebScreenInfo* results) { 518 *results = GetWebScreenInfo(NULL); 519} 520 521/////////////////////////////////////////////////////////////////////////////// 522// RenderWidgetHostViewMac, public: 523 524RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget) 525 : render_widget_host_(RenderWidgetHostImpl::From(widget)), 526 text_input_type_(ui::TEXT_INPUT_TYPE_NONE), 527 can_compose_inline_(true), 528 browser_compositor_view_placeholder_( 529 new BrowserCompositorViewPlaceholderMac), 530 is_loading_(false), 531 allow_pause_for_resize_or_repaint_(true), 532 weak_factory_(this), 533 fullscreen_parent_host_view_(NULL) { 534 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_| 535 // goes away. Since we autorelease it, our caller must put 536 // |GetNativeView()| into the view hierarchy right after calling us. 537 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc] 538 initWithRenderWidgetHostViewMac:this] autorelease]; 539 540 // Make this view host a solid white layer when there is no content ready to 541 // draw. 542 background_layer_.reset([[CALayer alloc] init]); 543 [background_layer_ 544 setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)]; 545 [cocoa_view_ setLayer:background_layer_]; 546 [cocoa_view_ setWantsLayer:YES]; 547 548 if (IsDelegatedRendererEnabled()) { 549 root_layer_.reset(new ui::Layer(ui::LAYER_TEXTURED)); 550 delegated_frame_host_.reset(new DelegatedFrameHost(this)); 551 } 552 553 gfx::Screen::GetScreenFor(cocoa_view_)->AddObserver(this); 554 555 render_widget_host_->SetView(this); 556} 557 558RenderWidgetHostViewMac::~RenderWidgetHostViewMac() { 559 gfx::Screen::GetScreenFor(cocoa_view_)->RemoveObserver(this); 560 561 // This is being called from |cocoa_view_|'s destructor, so invalidate the 562 // pointer. 563 cocoa_view_ = nil; 564 565 UnlockMouse(); 566 567 // Ensure that the browser compositor is destroyed in a safe order. 568 ShutdownBrowserCompositor(); 569 570 // We are owned by RenderWidgetHostViewCocoa, so if we go away before the 571 // RenderWidgetHost does we need to tell it not to hold a stale pointer to 572 // us. 573 if (render_widget_host_) 574 render_widget_host_->SetView(NULL); 575} 576 577void RenderWidgetHostViewMac::SetDelegate( 578 NSObject<RenderWidgetHostViewMacDelegate>* delegate) { 579 [cocoa_view_ setResponderDelegate:delegate]; 580} 581 582void RenderWidgetHostViewMac::SetAllowPauseForResizeOrRepaint(bool allow) { 583 allow_pause_for_resize_or_repaint_ = allow; 584} 585 586/////////////////////////////////////////////////////////////////////////////// 587// RenderWidgetHostViewMac, RenderWidgetHostView implementation: 588 589void RenderWidgetHostViewMac::EnsureBrowserCompositorView() { 590 if (browser_compositor_view_) 591 return; 592 593 TRACE_EVENT0("browser", 594 "RenderWidgetHostViewMac::EnsureBrowserCompositorView"); 595 596 browser_compositor_view_.reset(new BrowserCompositorViewMac(this)); 597 delegated_frame_host_->AddedToWindow(); 598 delegated_frame_host_->WasShown(ui::LatencyInfo()); 599} 600 601void RenderWidgetHostViewMac::DestroyBrowserCompositorView() { 602 TRACE_EVENT0("browser", 603 "RenderWidgetHostViewMac::DestroyBrowserCompositorView"); 604 if (!browser_compositor_view_) 605 return; 606 607 // Marking the DelegatedFrameHost as removed from the window hierarchy is 608 // necessary to remove all connections to its old ui::Compositor. 609 delegated_frame_host_->WasHidden(); 610 delegated_frame_host_->RemovingFromWindow(); 611 browser_compositor_view_.reset(); 612} 613 614bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) { 615 bool handled = true; 616 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message) 617 IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged) 618 IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme) 619 IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedTextCompleted, 620 OnGetRenderedTextCompleted) 621 IPC_MESSAGE_UNHANDLED(handled = false) 622 IPC_END_MESSAGE_MAP() 623 return handled; 624} 625 626void RenderWidgetHostViewMac::InitAsChild( 627 gfx::NativeView parent_view) { 628} 629 630void RenderWidgetHostViewMac::InitAsPopup( 631 RenderWidgetHostView* parent_host_view, 632 const gfx::Rect& pos) { 633 bool activatable = popup_type_ == blink::WebPopupTypeNone; 634 [cocoa_view_ setCloseOnDeactivate:YES]; 635 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO]; 636 637 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint()); 638 origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height()); 639 640 popup_window_.reset([[RenderWidgetPopupWindow alloc] 641 initWithContentRect:NSMakeRect(origin_global.x, origin_global.y, 642 pos.width(), pos.height()) 643 styleMask:NSBorderlessWindowMask 644 backing:NSBackingStoreBuffered 645 defer:NO]); 646 [popup_window_ setLevel:NSPopUpMenuWindowLevel]; 647 [popup_window_ setReleasedWhenClosed:NO]; 648 [popup_window_ makeKeyAndOrderFront:nil]; 649 [[popup_window_ contentView] addSubview:cocoa_view_]; 650 [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]]; 651 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 652 [[NSNotificationCenter defaultCenter] 653 addObserver:cocoa_view_ 654 selector:@selector(popupWindowWillClose:) 655 name:NSWindowWillCloseNotification 656 object:popup_window_]; 657} 658 659// This function creates the fullscreen window and hides the dock and menubar if 660// necessary. Note, this codepath is only used for pepper flash when 661// pp::FlashFullScreen::SetFullscreen() is called. If 662// pp::FullScreen::SetFullscreen() is called then the entire browser window 663// will enter fullscreen instead. 664void RenderWidgetHostViewMac::InitAsFullscreen( 665 RenderWidgetHostView* reference_host_view) { 666 fullscreen_parent_host_view_ = 667 static_cast<RenderWidgetHostViewMac*>(reference_host_view); 668 NSWindow* parent_window = nil; 669 if (reference_host_view) 670 parent_window = [reference_host_view->GetNativeView() window]; 671 NSScreen* screen = [parent_window screen]; 672 if (!screen) 673 screen = [NSScreen mainScreen]; 674 675 pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc] 676 initWithContentRect:[screen frame] 677 styleMask:NSBorderlessWindowMask 678 backing:NSBackingStoreBuffered 679 defer:NO]); 680 [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel]; 681 [pepper_fullscreen_window_ setReleasedWhenClosed:NO]; 682 [cocoa_view_ setCanBeKeyView:YES]; 683 [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]]; 684 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 685 // If the pepper fullscreen window isn't opaque then there are performance 686 // issues when it's on the discrete GPU and the Chrome window is being drawn 687 // to. http://crbug.com/171911 688 [pepper_fullscreen_window_ setOpaque:YES]; 689 690 // Note that this forms a reference cycle between the fullscreen window and 691 // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_, 692 // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable. 693 // This cycle is normally broken when -keyEvent: receives an <esc> key, which 694 // explicitly calls Shutdown on the render_widget_host_, which calls 695 // Destroy() on RWHVMac, which drops the reference to 696 // pepper_fullscreen_window_. 697 [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_]; 698 699 // Note that this keeps another reference to pepper_fullscreen_window_. 700 fullscreen_window_manager_.reset([[FullscreenWindowManager alloc] 701 initWithWindow:pepper_fullscreen_window_.get() 702 desiredScreen:screen]); 703 [fullscreen_window_manager_ enterFullscreenMode]; 704 [pepper_fullscreen_window_ makeKeyAndOrderFront:nil]; 705} 706 707void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() { 708 // See comment in InitAsFullscreen(): There is a reference cycle between 709 // rwhvmac and fullscreen window, which is usually broken by hitting <esc>. 710 // Tests that test pepper fullscreen mode without sending an <esc> event 711 // need to call this method to break the reference cycle. 712 [fullscreen_window_manager_ exitFullscreenMode]; 713 fullscreen_window_manager_.reset(); 714 [pepper_fullscreen_window_ close]; 715 pepper_fullscreen_window_.reset(); 716} 717 718int RenderWidgetHostViewMac::window_number() const { 719 NSWindow* window = [cocoa_view_ window]; 720 if (!window) 721 return -1; 722 return [window windowNumber]; 723} 724 725float RenderWidgetHostViewMac::ViewScaleFactor() const { 726 return ui::GetScaleFactorForNativeView(cocoa_view_); 727} 728 729void RenderWidgetHostViewMac::UpdateDisplayLink() { 730 static bool is_vsync_disabled = 731 base::CommandLine::ForCurrentProcess()->HasSwitch( 732 switches::kDisableGpuVsync); 733 if (is_vsync_disabled) 734 return; 735 736 NSScreen* screen = [[cocoa_view_ window] screen]; 737 NSDictionary* screen_description = [screen deviceDescription]; 738 NSNumber* screen_number = [screen_description objectForKey:@"NSScreenNumber"]; 739 CGDirectDisplayID display_id = [screen_number unsignedIntValue]; 740 741 display_link_ = DisplayLinkMac::GetForDisplay(display_id); 742 if (!display_link_.get()) { 743 // Note that on some headless systems, the display link will fail to be 744 // created, so this should not be a fatal error. 745 LOG(ERROR) << "Failed to create display link."; 746 } 747} 748 749void RenderWidgetHostViewMac::SendVSyncParametersToRenderer() { 750 if (!render_widget_host_ || !display_link_.get()) 751 return; 752 753 if (!display_link_->GetVSyncParameters(&vsync_timebase_, &vsync_interval_)) { 754 vsync_timebase_ = base::TimeTicks(); 755 vsync_interval_ = base::TimeDelta(); 756 return; 757 } 758 759 render_widget_host_->UpdateVSyncParameters(vsync_timebase_, vsync_interval_); 760} 761 762void RenderWidgetHostViewMac::SpeakText(const std::string& text) { 763 [NSApp speakString:base::SysUTF8ToNSString(text)]; 764} 765 766void RenderWidgetHostViewMac::UpdateBackingStoreScaleFactor() { 767 if (!render_widget_host_) 768 return; 769 render_widget_host_->NotifyScreenInfoChanged(); 770} 771 772RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const { 773 return render_widget_host_; 774} 775 776void RenderWidgetHostViewMac::WasShown() { 777 if (!render_widget_host_->is_hidden()) 778 return; 779 780 ui::LatencyInfo renderer_latency_info; 781 renderer_latency_info.AddLatencyNumber( 782 ui::TAB_SHOW_COMPONENT, 783 render_widget_host_->GetLatencyComponentId(), 784 0); 785 render_widget_host_->WasShown(renderer_latency_info); 786 787 // If there is not a frame being currently drawn, kick one, so that the below 788 // pause will have a frame to wait on. 789 render_widget_host_->ScheduleComposite(); 790 PauseForPendingResizeOrRepaintsAndDraw(); 791} 792 793void RenderWidgetHostViewMac::WasHidden() { 794 if (render_widget_host_->is_hidden()) 795 return; 796 797 DestroyBrowserCompositorView(); 798 799 // If we have a renderer, then inform it that we are being hidden so it can 800 // reduce its resource utilization. 801 render_widget_host_->WasHidden(); 802} 803 804void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) { 805 gfx::Rect rect = GetViewBounds(); 806 rect.set_size(size); 807 SetBounds(rect); 808} 809 810void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) { 811 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates, 812 // TODO(thakis): fix, http://crbug.com/73362 813 if (render_widget_host_->is_hidden()) 814 return; 815 816 // During the initial creation of the RenderWidgetHostView in 817 // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with 818 // an empty size. In the Windows code flow, it is not ignored because 819 // subsequent sizing calls from the OS flow through TCVW::WasSized which calls 820 // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer 821 // flags to keep things sized properly. On the other hand, if the size is not 822 // empty then this is a valid request for a pop-up. 823 if (rect.size().IsEmpty()) 824 return; 825 826 // Ignore the position of |rect| for non-popup rwhvs. This is because 827 // background tabs do not have a window, but the window is required for the 828 // coordinate conversions. Popups are always for a visible tab. 829 // 830 // Note: If |cocoa_view_| has been removed from the view hierarchy, it's still 831 // valid for resizing to be requested (e.g., during tab capture, to size the 832 // view to screen-capture resolution). In this case, simply treat the view as 833 // relative to the screen. 834 BOOL isRelativeToScreen = IsPopup() || 835 ![[cocoa_view_ superview] isKindOfClass:[BaseView class]]; 836 if (isRelativeToScreen) { 837 // The position of |rect| is screen coordinate system and we have to 838 // consider Cocoa coordinate system is upside-down and also multi-screen. 839 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint()); 840 NSSize size = NSMakeSize(rect.width(), rect.height()); 841 size = [cocoa_view_ convertSize:size toView:nil]; 842 origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height); 843 NSRect frame = NSMakeRect(origin_global.x, origin_global.y, 844 size.width, size.height); 845 if (IsPopup()) 846 [popup_window_ setFrame:frame display:YES]; 847 else 848 [cocoa_view_ setFrame:frame]; 849 } else { 850 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]); 851 gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]]; 852 rect2.set_width(rect.width()); 853 rect2.set_height(rect.height()); 854 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]]; 855 } 856} 857 858gfx::Vector2dF RenderWidgetHostViewMac::GetLastScrollOffset() const { 859 return last_scroll_offset_; 860} 861 862gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const { 863 return cocoa_view_; 864} 865 866gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const { 867 return reinterpret_cast<gfx::NativeViewId>(GetNativeView()); 868} 869 870gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() { 871 NOTIMPLEMENTED(); 872 return static_cast<gfx::NativeViewAccessible>(NULL); 873} 874 875void RenderWidgetHostViewMac::MovePluginWindows( 876 const std::vector<WebPluginGeometry>& moves) { 877 // Must be overridden, but unused on this platform. Core Animation 878 // plugins are drawn by the GPU process (through the compositor), 879 // and Core Graphics plugins are drawn by the renderer process. 880 DCHECK_CURRENTLY_ON(BrowserThread::UI); 881} 882 883void RenderWidgetHostViewMac::Focus() { 884 [[cocoa_view_ window] makeFirstResponder:cocoa_view_]; 885} 886 887void RenderWidgetHostViewMac::Blur() { 888 UnlockMouse(); 889 [[cocoa_view_ window] makeFirstResponder:nil]; 890} 891 892bool RenderWidgetHostViewMac::HasFocus() const { 893 return [[cocoa_view_ window] firstResponder] == cocoa_view_; 894} 895 896bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const { 897 if (delegated_frame_host_) 898 return delegated_frame_host_->CanCopyToBitmap(); 899 return false; 900} 901 902void RenderWidgetHostViewMac::Show() { 903 [cocoa_view_ setHidden:NO]; 904 905 WasShown(); 906} 907 908void RenderWidgetHostViewMac::Hide() { 909 [cocoa_view_ setHidden:YES]; 910 911 WasHidden(); 912} 913 914bool RenderWidgetHostViewMac::IsShowing() { 915 return ![cocoa_view_ isHidden]; 916} 917 918gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const { 919 NSRect bounds = [cocoa_view_ bounds]; 920 // TODO(shess): In case of !window, the view has been removed from 921 // the view hierarchy because the tab isn't main. Could retrieve 922 // the information from the main tab for our window. 923 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); 924 if (!enclosing_window) 925 return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds))); 926 927 bounds = [cocoa_view_ convertRect:bounds toView:nil]; 928 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin]; 929 return FlipNSRectToRectScreen(bounds); 930} 931 932void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) { 933 WebCursor web_cursor = cursor; 934 [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()]; 935} 936 937void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) { 938 is_loading_ = is_loading; 939 // If we ever decide to show the waiting cursor while the page is loading 940 // like Chrome does on Windows, call |UpdateCursor()| here. 941} 942 943void RenderWidgetHostViewMac::TextInputTypeChanged( 944 ui::TextInputType type, 945 ui::TextInputMode input_mode, 946 bool can_compose_inline) { 947 if (text_input_type_ != type 948 || can_compose_inline_ != can_compose_inline) { 949 text_input_type_ = type; 950 can_compose_inline_ = can_compose_inline; 951 if (HasFocus()) { 952 SetTextInputActive(true); 953 954 // Let AppKit cache the new input context to make IMEs happy. 955 // See http://crbug.com/73039. 956 [NSApp updateWindows]; 957 958#ifndef __LP64__ 959 UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_); 960#endif 961 } 962 } 963} 964 965void RenderWidgetHostViewMac::ImeCancelComposition() { 966 [cocoa_view_ cancelComposition]; 967} 968 969void RenderWidgetHostViewMac::ImeCompositionRangeChanged( 970 const gfx::Range& range, 971 const std::vector<gfx::Rect>& character_bounds) { 972 // The RangeChanged message is only sent with valid values. The current 973 // caret position (start == end) will be sent if there is no IME range. 974 [cocoa_view_ setMarkedRange:range.ToNSRange()]; 975 composition_range_ = range; 976 composition_bounds_ = character_bounds; 977} 978 979void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status, 980 int error_code) { 981 Destroy(); 982} 983 984void RenderWidgetHostViewMac::RenderWidgetHostGone() { 985 // Destroy the DelegatedFrameHost, to prevent crashes when Destroy is never 986 // called on the view. 987 // http://crbug.com/404828 988 ShutdownBrowserCompositor(); 989} 990 991void RenderWidgetHostViewMac::Destroy() { 992 [[NSNotificationCenter defaultCenter] 993 removeObserver:cocoa_view_ 994 name:NSWindowWillCloseNotification 995 object:popup_window_]; 996 997 // We've been told to destroy. 998 [cocoa_view_ retain]; 999 [cocoa_view_ removeFromSuperview]; 1000 [cocoa_view_ autorelease]; 1001 1002 [popup_window_ close]; 1003 popup_window_.autorelease(); 1004 1005 [fullscreen_window_manager_ exitFullscreenMode]; 1006 fullscreen_window_manager_.reset(); 1007 [pepper_fullscreen_window_ close]; 1008 1009 // This can be called as part of processing the window's responder 1010 // chain, for instance |-performKeyEquivalent:|. In that case the 1011 // object needs to survive until the stack unwinds. 1012 pepper_fullscreen_window_.autorelease(); 1013 1014 // Delete the delegated frame state, which will reach back into 1015 // render_widget_host_. 1016 ShutdownBrowserCompositor(); 1017 1018 // We get this call just before |render_widget_host_| deletes 1019 // itself. But we are owned by |cocoa_view_|, which may be retained 1020 // by some other code. Examples are WebContentsViewMac's 1021 // |latent_focus_view_| and TabWindowController's 1022 // |cachedContentView_|. 1023 render_widget_host_ = NULL; 1024} 1025 1026// Called from the renderer to tell us what the tooltip text should be. It 1027// calls us frequently so we need to cache the value to prevent doing a lot 1028// of repeat work. 1029void RenderWidgetHostViewMac::SetTooltipText( 1030 const base::string16& tooltip_text) { 1031 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) { 1032 tooltip_text_ = tooltip_text; 1033 1034 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on 1035 // Windows; we're just trying to be polite. Don't persist the trimmed 1036 // string, as then the comparison above will always fail and we'll try to 1037 // set it again every single time the mouse moves. 1038 base::string16 display_text = tooltip_text_; 1039 if (tooltip_text_.length() > kMaxTooltipLength) 1040 display_text = tooltip_text_.substr(0, kMaxTooltipLength); 1041 1042 NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text); 1043 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring]; 1044 } 1045} 1046 1047bool RenderWidgetHostViewMac::SupportsSpeech() const { 1048 return [NSApp respondsToSelector:@selector(speakString:)] && 1049 [NSApp respondsToSelector:@selector(stopSpeaking:)]; 1050} 1051 1052void RenderWidgetHostViewMac::SpeakSelection() { 1053 if (![NSApp respondsToSelector:@selector(speakString:)]) 1054 return; 1055 1056 if (selected_text_.empty() && render_widget_host_) { 1057 // If there's no selection, speak all text. Send an asynchronous IPC 1058 // request for fetching all the text for a webcontent. 1059 // ViewMsg_GetRenderedTextCompleted is sent back to IPC Message receiver. 1060 render_widget_host_->Send(new ViewMsg_GetRenderedText( 1061 render_widget_host_->GetRoutingID())); 1062 return; 1063 } 1064 1065 SpeakText(selected_text_); 1066} 1067 1068bool RenderWidgetHostViewMac::IsSpeaking() const { 1069 return [NSApp respondsToSelector:@selector(isSpeaking)] && 1070 [NSApp isSpeaking]; 1071} 1072 1073void RenderWidgetHostViewMac::StopSpeaking() { 1074 if ([NSApp respondsToSelector:@selector(stopSpeaking:)]) 1075 [NSApp stopSpeaking:cocoa_view_]; 1076} 1077 1078// 1079// RenderWidgetHostViewCocoa uses the stored selection text, 1080// which implements NSServicesRequests protocol. 1081// 1082void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text, 1083 size_t offset, 1084 const gfx::Range& range) { 1085 if (range.is_empty() || text.empty()) { 1086 selected_text_.clear(); 1087 } else { 1088 size_t pos = range.GetMin() - offset; 1089 size_t n = range.length(); 1090 1091 DCHECK(pos + n <= text.length()) << "The text can not fully cover range."; 1092 if (pos >= text.length()) { 1093 DCHECK(false) << "The text can not cover range."; 1094 return; 1095 } 1096 selected_text_ = base::UTF16ToUTF8(text.substr(pos, n)); 1097 } 1098 1099 [cocoa_view_ setSelectedRange:range.ToNSRange()]; 1100 // Updates markedRange when there is no marked text so that retrieving 1101 // markedRange immediately after calling setMarkdText: returns the current 1102 // caret position. 1103 if (![cocoa_view_ hasMarkedText]) { 1104 [cocoa_view_ setMarkedRange:range.ToNSRange()]; 1105 } 1106 1107 RenderWidgetHostViewBase::SelectionChanged(text, offset, range); 1108} 1109 1110void RenderWidgetHostViewMac::SelectionBoundsChanged( 1111 const ViewHostMsg_SelectionBounds_Params& params) { 1112 if (params.anchor_rect == params.focus_rect) 1113 caret_rect_ = params.anchor_rect; 1114} 1115 1116void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) { 1117 RenderWidgetHostViewBase::SetShowingContextMenu(showing); 1118 1119 // Create a fake mouse event to inform the render widget that the mouse 1120 // left or entered. 1121 NSWindow* window = [cocoa_view_ window]; 1122 // TODO(asvitkine): If the location outside of the event stream doesn't 1123 // correspond to the current event (due to delayed event processing), then 1124 // this may result in a cursor flicker if there are later mouse move events 1125 // in the pipeline. Find a way to use the mouse location from the event that 1126 // dismissed the context menu. 1127 NSPoint location = [window mouseLocationOutsideOfEventStream]; 1128 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved 1129 location:location 1130 modifierFlags:0 1131 timestamp:0 1132 windowNumber:window_number() 1133 context:nil 1134 eventNumber:0 1135 clickCount:0 1136 pressure:0]; 1137 WebMouseEvent web_event = 1138 WebInputEventFactory::mouseEvent(event, cocoa_view_); 1139 if (showing) 1140 web_event.type = WebInputEvent::MouseLeave; 1141 ForwardMouseEvent(web_event); 1142} 1143 1144bool RenderWidgetHostViewMac::IsPopup() const { 1145 return popup_type_ != blink::WebPopupTypeNone; 1146} 1147 1148void RenderWidgetHostViewMac::CopyFromCompositingSurface( 1149 const gfx::Rect& src_subrect, 1150 const gfx::Size& dst_size, 1151 const base::Callback<void(bool, const SkBitmap&)>& callback, 1152 const SkColorType color_type) { 1153 if (delegated_frame_host_) { 1154 delegated_frame_host_->CopyFromCompositingSurface( 1155 src_subrect, dst_size, callback, color_type); 1156 } 1157} 1158 1159void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame( 1160 const gfx::Rect& src_subrect, 1161 const scoped_refptr<media::VideoFrame>& target, 1162 const base::Callback<void(bool)>& callback) { 1163 if (delegated_frame_host_) { 1164 delegated_frame_host_->CopyFromCompositingSurfaceToVideoFrame( 1165 src_subrect, target, callback); 1166 } 1167} 1168 1169bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const { 1170 if (delegated_frame_host_) 1171 return delegated_frame_host_->CanCopyToVideoFrame(); 1172 return false; 1173} 1174 1175bool RenderWidgetHostViewMac::CanSubscribeFrame() const { 1176 if (delegated_frame_host_) 1177 return delegated_frame_host_->CanSubscribeFrame(); 1178 return false; 1179} 1180 1181void RenderWidgetHostViewMac::BeginFrameSubscription( 1182 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) { 1183 if (delegated_frame_host_) 1184 delegated_frame_host_->BeginFrameSubscription(subscriber.Pass()); 1185} 1186 1187void RenderWidgetHostViewMac::EndFrameSubscription() { 1188 if (delegated_frame_host_) 1189 delegated_frame_host_->EndFrameSubscription(); 1190} 1191 1192// Sets whether or not to accept first responder status. 1193void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) { 1194 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag]; 1195} 1196 1197void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) { 1198 if (render_widget_host_) 1199 render_widget_host_->ForwardMouseEvent(event); 1200 1201 if (event.type == WebInputEvent::MouseLeave) { 1202 [cocoa_view_ setToolTipAtMousePoint:nil]; 1203 tooltip_text_.clear(); 1204 } 1205} 1206 1207void RenderWidgetHostViewMac::KillSelf() { 1208 if (!weak_factory_.HasWeakPtrs()) { 1209 [cocoa_view_ setHidden:YES]; 1210 base::MessageLoop::current()->PostTask(FROM_HERE, 1211 base::Bind(&RenderWidgetHostViewMac::ShutdownHost, 1212 weak_factory_.GetWeakPtr())); 1213 } 1214} 1215 1216bool RenderWidgetHostViewMac::PostProcessEventForPluginIme( 1217 const NativeWebKeyboardEvent& event) { 1218 // Check WebInputEvent type since multiple types of events can be sent into 1219 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is 1220 // necessary to avoid double processing. 1221 // Also check the native type, since NSFlagsChanged is considered a key event 1222 // for WebKit purposes, but isn't considered a key event by the OS. 1223 if (event.type == WebInputEvent::RawKeyDown && 1224 [event.os_event type] == NSKeyDown) 1225 return [cocoa_view_ postProcessEventForPluginIme:event.os_event]; 1226 return false; 1227} 1228 1229void RenderWidgetHostViewMac::PluginImeCompositionCompleted( 1230 const base::string16& text, int plugin_id) { 1231 if (render_widget_host_) { 1232 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted( 1233 render_widget_host_->GetRoutingID(), text, plugin_id)); 1234 } 1235} 1236 1237bool RenderWidgetHostViewMac::GetLineBreakIndex( 1238 const std::vector<gfx::Rect>& bounds, 1239 const gfx::Range& range, 1240 size_t* line_break_point) { 1241 DCHECK(line_break_point); 1242 if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty()) 1243 return false; 1244 1245 // We can't check line breaking completely from only rectangle array. Thus we 1246 // assume the line breaking as the next character's y offset is larger than 1247 // a threshold. Currently the threshold is determined as minimum y offset plus 1248 // 75% of maximum height. 1249 // TODO(nona): Check the threshold is reliable or not. 1250 // TODO(nona): Bidi support. 1251 const size_t loop_end_idx = std::min(bounds.size(), range.end()); 1252 int max_height = 0; 1253 int min_y_offset = kint32max; 1254 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) { 1255 max_height = std::max(max_height, bounds[idx].height()); 1256 min_y_offset = std::min(min_y_offset, bounds[idx].y()); 1257 } 1258 int line_break_threshold = min_y_offset + (max_height * 3 / 4); 1259 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) { 1260 if (bounds[idx].y() > line_break_threshold) { 1261 *line_break_point = idx; 1262 return true; 1263 } 1264 } 1265 return false; 1266} 1267 1268gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange( 1269 const gfx::Range& range, 1270 gfx::Range* actual_range) { 1271 DCHECK(actual_range); 1272 DCHECK(!composition_bounds_.empty()); 1273 DCHECK(range.start() <= composition_bounds_.size()); 1274 DCHECK(range.end() <= composition_bounds_.size()); 1275 1276 if (range.is_empty()) { 1277 *actual_range = range; 1278 if (range.start() == composition_bounds_.size()) { 1279 return gfx::Rect(composition_bounds_[range.start() - 1].right(), 1280 composition_bounds_[range.start() - 1].y(), 1281 0, 1282 composition_bounds_[range.start() - 1].height()); 1283 } else { 1284 return gfx::Rect(composition_bounds_[range.start()].x(), 1285 composition_bounds_[range.start()].y(), 1286 0, 1287 composition_bounds_[range.start()].height()); 1288 } 1289 } 1290 1291 size_t end_idx; 1292 if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) { 1293 end_idx = range.end(); 1294 } 1295 *actual_range = gfx::Range(range.start(), end_idx); 1296 gfx::Rect rect = composition_bounds_[range.start()]; 1297 for (size_t i = range.start() + 1; i < end_idx; ++i) { 1298 rect.Union(composition_bounds_[i]); 1299 } 1300 return rect; 1301} 1302 1303gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange( 1304 const gfx::Range& request_range) { 1305 if (composition_range_.is_empty()) 1306 return gfx::Range::InvalidRange(); 1307 1308 if (request_range.is_reversed()) 1309 return gfx::Range::InvalidRange(); 1310 1311 if (request_range.start() < composition_range_.start() || 1312 request_range.start() > composition_range_.end() || 1313 request_range.end() > composition_range_.end()) { 1314 return gfx::Range::InvalidRange(); 1315 } 1316 1317 return gfx::Range( 1318 request_range.start() - composition_range_.start(), 1319 request_range.end() - composition_range_.start()); 1320} 1321 1322WebContents* RenderWidgetHostViewMac::GetWebContents() { 1323 if (!render_widget_host_->IsRenderView()) 1324 return NULL; 1325 1326 return WebContents::FromRenderViewHost( 1327 RenderViewHost::From(render_widget_host_)); 1328} 1329 1330bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange( 1331 NSRange range, 1332 NSRect* rect, 1333 NSRange* actual_range) { 1334 DCHECK(rect); 1335 // This exists to make IMEs more responsive, see http://crbug.com/115920 1336 TRACE_EVENT0("browser", 1337 "RenderWidgetHostViewMac::GetFirstRectForCharacterRange"); 1338 1339 // If requested range is same as caret location, we can just return it. 1340 if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) { 1341 if (actual_range) 1342 *actual_range = range; 1343 *rect = NSRectFromCGRect(caret_rect_.ToCGRect()); 1344 return true; 1345 } 1346 1347 const gfx::Range request_range_in_composition = 1348 ConvertCharacterRangeToCompositionRange(gfx::Range(range)); 1349 if (request_range_in_composition == gfx::Range::InvalidRange()) 1350 return false; 1351 1352 // If firstRectForCharacterRange in WebFrame is failed in renderer, 1353 // ImeCompositionRangeChanged will be sent with empty vector. 1354 if (composition_bounds_.empty()) 1355 return false; 1356 DCHECK_EQ(composition_bounds_.size(), composition_range_.length()); 1357 1358 gfx::Range ui_actual_range; 1359 *rect = NSRectFromCGRect(GetFirstRectForCompositionRange( 1360 request_range_in_composition, 1361 &ui_actual_range).ToCGRect()); 1362 if (actual_range) { 1363 *actual_range = gfx::Range( 1364 composition_range_.start() + ui_actual_range.start(), 1365 composition_range_.start() + ui_actual_range.end()).ToNSRange(); 1366 } 1367 return true; 1368} 1369 1370void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped( 1371 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params, 1372 int gpu_host_id) { 1373} 1374 1375void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer( 1376 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params, 1377 int gpu_host_id) { 1378} 1379 1380void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() { 1381} 1382 1383void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() { 1384} 1385 1386bool RenderWidgetHostViewMac::HasAcceleratedSurface( 1387 const gfx::Size& desired_size) { 1388 if (browser_compositor_view_) 1389 return browser_compositor_view_->HasFrameOfSize(desired_size); 1390 return false; 1391} 1392 1393void RenderWidgetHostViewMac::OnSwapCompositorFrame( 1394 uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) { 1395 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::OnSwapCompositorFrame"); 1396 1397 last_scroll_offset_ = frame->metadata.root_scroll_offset; 1398 if (frame->delegated_frame_data) { 1399 float scale_factor = frame->metadata.device_scale_factor; 1400 1401 // Compute the frame size based on the root render pass rect size. 1402 cc::RenderPass* root_pass = 1403 frame->delegated_frame_data->render_pass_list.back(); 1404 gfx::Size pixel_size = root_pass->output_rect.size(); 1405 gfx::Size dip_size = 1406 ConvertSizeToDIP(scale_factor, pixel_size); 1407 1408 root_layer_->SetBounds(gfx::Rect(dip_size)); 1409 if (!render_widget_host_->is_hidden()) { 1410 EnsureBrowserCompositorView(); 1411 browser_compositor_view_->GetCompositor()->SetScaleAndSize( 1412 scale_factor, pixel_size); 1413 } 1414 1415 SendVSyncParametersToRenderer(); 1416 1417 delegated_frame_host_->SwapDelegatedFrame( 1418 output_surface_id, 1419 frame->delegated_frame_data.Pass(), 1420 frame->metadata.device_scale_factor, 1421 frame->metadata.latency_info); 1422 } else { 1423 DLOG(ERROR) << "Received unexpected frame type."; 1424 RecordAction( 1425 base::UserMetricsAction("BadMessageTerminate_UnexpectedFrameType")); 1426 render_widget_host_->GetProcess()->ReceivedBadMessage(); 1427 } 1428} 1429 1430void RenderWidgetHostViewMac::AcceleratedSurfaceInitialized(int host_id, 1431 int route_id) { 1432} 1433 1434void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) { 1435 *results = GetWebScreenInfo(GetNativeView()); 1436} 1437 1438gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() { 1439 // TODO(shess): In case of !window, the view has been removed from 1440 // the view hierarchy because the tab isn't main. Could retrieve 1441 // the information from the main tab for our window. 1442 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); 1443 if (!enclosing_window) 1444 return gfx::Rect(); 1445 1446 NSRect bounds = [enclosing_window frame]; 1447 return FlipNSRectToRectScreen(bounds); 1448} 1449 1450gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() { 1451 // TODO(kbr): may be able to eliminate PluginWindowHandle argument 1452 // completely on Mac OS. 1453 return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT); 1454} 1455 1456bool RenderWidgetHostViewMac::LockMouse() { 1457 if (mouse_locked_) 1458 return true; 1459 1460 mouse_locked_ = true; 1461 1462 // Lock position of mouse cursor and hide it. 1463 CGAssociateMouseAndMouseCursorPosition(NO); 1464 [NSCursor hide]; 1465 1466 // Clear the tooltip window. 1467 SetTooltipText(base::string16()); 1468 1469 return true; 1470} 1471 1472void RenderWidgetHostViewMac::UnlockMouse() { 1473 if (!mouse_locked_) 1474 return; 1475 mouse_locked_ = false; 1476 1477 // Unlock position of mouse cursor and unhide it. 1478 CGAssociateMouseAndMouseCursorPosition(YES); 1479 [NSCursor unhide]; 1480 1481 if (render_widget_host_) 1482 render_widget_host_->LostMouseLock(); 1483} 1484 1485void RenderWidgetHostViewMac::WheelEventAck( 1486 const blink::WebMouseWheelEvent& event, 1487 InputEventAckState ack_result) { 1488 bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED; 1489 // Only record a wheel event as unhandled if JavaScript handlers got a chance 1490 // to see it (no-op wheel events are ignored by the event dispatcher) 1491 if (event.deltaX || event.deltaY) 1492 [cocoa_view_ processedWheelEvent:event consumed:consumed]; 1493} 1494 1495bool RenderWidgetHostViewMac::Send(IPC::Message* message) { 1496 if (render_widget_host_) 1497 return render_widget_host_->Send(message); 1498 delete message; 1499 return false; 1500} 1501 1502void RenderWidgetHostViewMac::ShutdownHost() { 1503 weak_factory_.InvalidateWeakPtrs(); 1504 render_widget_host_->Shutdown(); 1505 // Do not touch any members at this point, |this| has been deleted. 1506} 1507 1508void RenderWidgetHostViewMac::ShutdownBrowserCompositor() { 1509 DestroyBrowserCompositorView(); 1510 delegated_frame_host_.reset(); 1511 root_layer_.reset(); 1512 browser_compositor_view_placeholder_.reset(); 1513} 1514 1515void RenderWidgetHostViewMac::SetActive(bool active) { 1516 if (render_widget_host_) { 1517 render_widget_host_->SetActive(active); 1518 if (active) { 1519 if (HasFocus()) 1520 render_widget_host_->Focus(); 1521 } else { 1522 render_widget_host_->Blur(); 1523 } 1524 } 1525 if (HasFocus()) 1526 SetTextInputActive(active); 1527 if (!active) { 1528 [cocoa_view_ setPluginImeActive:NO]; 1529 UnlockMouse(); 1530 } 1531} 1532 1533void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) { 1534 if (render_widget_host_) { 1535 render_widget_host_->Send(new ViewMsg_SetWindowVisibility( 1536 render_widget_host_->GetRoutingID(), visible)); 1537 } 1538} 1539 1540void RenderWidgetHostViewMac::WindowFrameChanged() { 1541 if (render_widget_host_) { 1542 render_widget_host_->Send(new ViewMsg_WindowFrameChanged( 1543 render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(), 1544 GetViewBounds())); 1545 } 1546} 1547 1548void RenderWidgetHostViewMac::ShowDefinitionForSelection() { 1549 RenderWidgetHostViewMacDictionaryHelper helper(this); 1550 helper.ShowDefinitionForSelection(); 1551} 1552 1553void RenderWidgetHostViewMac::SetBackgroundOpaque(bool opaque) { 1554 RenderWidgetHostViewBase::SetBackgroundOpaque(opaque); 1555 if (render_widget_host_) 1556 render_widget_host_->SetBackgroundOpaque(opaque); 1557} 1558 1559BrowserAccessibilityManager* 1560 RenderWidgetHostViewMac::CreateBrowserAccessibilityManager( 1561 BrowserAccessibilityDelegate* delegate) { 1562 return new BrowserAccessibilityManagerMac( 1563 cocoa_view_, 1564 BrowserAccessibilityManagerMac::GetEmptyDocument(), 1565 delegate); 1566} 1567 1568gfx::Point RenderWidgetHostViewMac::AccessibilityOriginInScreen( 1569 const gfx::Rect& bounds) { 1570 NSPoint origin = NSMakePoint(bounds.x(), bounds.y()); 1571 NSSize size = NSMakeSize(bounds.width(), bounds.height()); 1572 origin.y = NSHeight([cocoa_view_ bounds]) - origin.y; 1573 NSPoint originInWindow = [cocoa_view_ convertPoint:origin toView:nil]; 1574 NSPoint originInScreen = 1575 [[cocoa_view_ window] convertBaseToScreen:originInWindow]; 1576 originInScreen.y = originInScreen.y - size.height; 1577 return gfx::Point(originInScreen.x, originInScreen.y); 1578} 1579 1580void RenderWidgetHostViewMac::AccessibilityShowMenu(const gfx::Point& point) { 1581 NSPoint location = NSMakePoint(point.x(), point.y()); 1582 location = [[cocoa_view_ window] convertScreenToBase:location]; 1583 NSEvent* fakeRightClick = [NSEvent 1584 mouseEventWithType:NSRightMouseDown 1585 location:location 1586 modifierFlags:0 1587 timestamp:0 1588 windowNumber:[[cocoa_view_ window] windowNumber] 1589 context:[NSGraphicsContext currentContext] 1590 eventNumber:0 1591 clickCount:1 1592 pressure:0]; 1593 1594 [cocoa_view_ mouseEvent:fakeRightClick]; 1595} 1596 1597void RenderWidgetHostViewMac::SetTextInputActive(bool active) { 1598 if (active) { 1599 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) 1600 EnablePasswordInput(); 1601 else 1602 DisablePasswordInput(); 1603 } else { 1604 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) 1605 DisablePasswordInput(); 1606 } 1607} 1608 1609void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused, 1610 int plugin_id) { 1611 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id]; 1612} 1613 1614void RenderWidgetHostViewMac::OnStartPluginIme() { 1615 [cocoa_view_ setPluginImeActive:YES]; 1616} 1617 1618void RenderWidgetHostViewMac::OnGetRenderedTextCompleted( 1619 const std::string& text) { 1620 SpeakText(text); 1621} 1622 1623void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() { 1624 if (!render_widget_host_ || render_widget_host_->is_hidden()) 1625 return; 1626 1627 // Pausing for one view prevents others from receiving frames. 1628 // This may lead to large delays, causing overlaps. See crbug.com/352020. 1629 if (!allow_pause_for_resize_or_repaint_) 1630 return; 1631 1632 // Wait for a frame of the right size to come in. 1633 if (browser_compositor_view_) 1634 browser_compositor_view_->BeginPumpingFrames(); 1635 render_widget_host_->PauseForPendingResizeOrRepaints(); 1636 if (browser_compositor_view_) 1637 browser_compositor_view_->EndPumpingFrames(); 1638} 1639 1640SkColorType RenderWidgetHostViewMac::PreferredReadbackFormat() { 1641 return kN32_SkColorType; 1642} 1643 1644//////////////////////////////////////////////////////////////////////////////// 1645// gfx::DisplayObserver, public: 1646 1647void RenderWidgetHostViewMac::OnDisplayAdded(const gfx::Display& display) { 1648} 1649 1650void RenderWidgetHostViewMac::OnDisplayRemoved(const gfx::Display& display) { 1651} 1652 1653void RenderWidgetHostViewMac::OnDisplayMetricsChanged( 1654 const gfx::Display& display, uint32_t metrics) { 1655 gfx::Screen* screen = gfx::Screen::GetScreenFor(cocoa_view_); 1656 if (display.id() != screen->GetDisplayNearestWindow(cocoa_view_).id()) 1657 return; 1658 1659 UpdateScreenInfo(cocoa_view_); 1660} 1661 1662} // namespace content 1663 1664// RenderWidgetHostViewCocoa --------------------------------------------------- 1665 1666@implementation RenderWidgetHostViewCocoa 1667@synthesize selectedRange = selectedRange_; 1668@synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_; 1669@synthesize markedRange = markedRange_; 1670 1671- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r { 1672 self = [super initWithFrame:NSZeroRect]; 1673 if (self) { 1674 self.acceptsTouchEvents = YES; 1675 editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper); 1676 editCommand_helper_->AddEditingSelectorsToClass([self class]); 1677 1678 renderWidgetHostView_.reset(r); 1679 canBeKeyView_ = YES; 1680 focusedPluginIdentifier_ = -1; 1681 1682 // OpenGL support: 1683 if ([self respondsToSelector: 1684 @selector(setWantsBestResolutionOpenGLSurface:)]) { 1685 [self setWantsBestResolutionOpenGLSurface:YES]; 1686 } 1687 [[NSNotificationCenter defaultCenter] 1688 addObserver:self 1689 selector:@selector(didChangeScreenParameters:) 1690 name:NSApplicationDidChangeScreenParametersNotification 1691 object:nil]; 1692 } 1693 return self; 1694} 1695 1696- (void)dealloc { 1697 // Unbind the GL context from this view. If this is not done before super's 1698 // dealloc is called then the GL context will crash when it reaches into 1699 // the view in its destructor. 1700 // http://crbug.com/255608 1701 if (renderWidgetHostView_) 1702 renderWidgetHostView_->AcceleratedSurfaceRelease(); 1703 1704 if (responderDelegate_ && 1705 [responderDelegate_ respondsToSelector:@selector(viewGone:)]) 1706 [responderDelegate_ viewGone:self]; 1707 responderDelegate_.reset(); 1708 1709 [[NSNotificationCenter defaultCenter] removeObserver:self]; 1710 1711 [super dealloc]; 1712} 1713 1714- (void)didChangeScreenParameters:(NSNotification*)notify { 1715 g_screen_info_up_to_date = false; 1716} 1717 1718- (void)setResponderDelegate: 1719 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate { 1720 DCHECK(!responderDelegate_); 1721 responderDelegate_.reset([delegate retain]); 1722} 1723 1724- (void)resetCursorRects { 1725 if (currentCursor_) { 1726 [self addCursorRect:[self visibleRect] cursor:currentCursor_]; 1727 [currentCursor_ setOnMouseEntered:YES]; 1728 } 1729} 1730 1731- (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event 1732 consumed:(BOOL)consumed { 1733 [responderDelegate_ rendererHandledWheelEvent:event consumed:consumed]; 1734} 1735 1736- (BOOL)respondsToSelector:(SEL)selector { 1737 // Trickiness: this doesn't mean "does this object's superclass respond to 1738 // this selector" but rather "does the -respondsToSelector impl from the 1739 // superclass say that this class responds to the selector". 1740 if ([super respondsToSelector:selector]) 1741 return YES; 1742 1743 if (responderDelegate_) 1744 return [responderDelegate_ respondsToSelector:selector]; 1745 1746 return NO; 1747} 1748 1749- (id)forwardingTargetForSelector:(SEL)selector { 1750 if ([responderDelegate_ respondsToSelector:selector]) 1751 return responderDelegate_.get(); 1752 1753 return [super forwardingTargetForSelector:selector]; 1754} 1755 1756- (void)setCanBeKeyView:(BOOL)can { 1757 canBeKeyView_ = can; 1758} 1759 1760- (BOOL)acceptsMouseEventsWhenInactive { 1761 // Some types of windows (balloons, always-on-top panels) want to accept mouse 1762 // clicks w/o the first click being treated as 'activation'. Same applies to 1763 // mouse move events. 1764 return [[self window] level] > NSNormalWindowLevel; 1765} 1766 1767- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent { 1768 return [self acceptsMouseEventsWhenInactive]; 1769} 1770 1771- (void)setTakesFocusOnlyOnMouseDown:(BOOL)b { 1772 takesFocusOnlyOnMouseDown_ = b; 1773} 1774 1775- (void)setCloseOnDeactivate:(BOOL)b { 1776 closeOnDeactivate_ = b; 1777} 1778 1779- (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent { 1780 NSWindow* window = [self window]; 1781 // If this is a background window, don't handle mouse movement events. This 1782 // is the expected behavior on the Mac as evidenced by other applications. 1783 if ([theEvent type] == NSMouseMoved && 1784 ![self acceptsMouseEventsWhenInactive] && 1785 ![window isKeyWindow]) { 1786 return YES; 1787 } 1788 1789 // Use hitTest to check whether the mouse is over a nonWebContentView - in 1790 // which case the mouse event should not be handled by the render host. 1791 const SEL nonWebContentViewSelector = @selector(nonWebContentView); 1792 NSView* contentView = [window contentView]; 1793 NSView* view = [contentView hitTest:[theEvent locationInWindow]]; 1794 // Traverse the superview hierarchy as the hitTest will return the frontmost 1795 // view, such as an NSTextView, while nonWebContentView may be specified by 1796 // its parent view. 1797 while (view) { 1798 if ([view respondsToSelector:nonWebContentViewSelector] && 1799 [view performSelector:nonWebContentViewSelector]) { 1800 // The cursor is over a nonWebContentView - ignore this mouse event. 1801 return YES; 1802 } 1803 if ([view isKindOfClass:[self class]] && ![view isEqual:self] && 1804 !hasOpenMouseDown_) { 1805 // The cursor is over an overlapping render widget. This check is done by 1806 // both views so the one that's returned by -hitTest: will end up 1807 // processing the event. 1808 // Note that while dragging, we only get events for the render view where 1809 // drag started, even if mouse is actually over another view or outside 1810 // the window. Cocoa does this for us. We should handle these events and 1811 // not ignore (since there is no other render view to handle them). Thus 1812 // the |!hasOpenMouseDown_| check above. 1813 return YES; 1814 } 1815 view = [view superview]; 1816 } 1817 return NO; 1818} 1819 1820- (void)mouseEvent:(NSEvent*)theEvent { 1821 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent"); 1822 if (responderDelegate_ && 1823 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) { 1824 BOOL handled = [responderDelegate_ handleEvent:theEvent]; 1825 if (handled) 1826 return; 1827 } 1828 1829 if ([self shouldIgnoreMouseEvent:theEvent]) { 1830 // If this is the first such event, send a mouse exit to the host view. 1831 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) { 1832 WebMouseEvent exitEvent = 1833 WebInputEventFactory::mouseEvent(theEvent, self); 1834 exitEvent.type = WebInputEvent::MouseLeave; 1835 exitEvent.button = WebMouseEvent::ButtonNone; 1836 renderWidgetHostView_->ForwardMouseEvent(exitEvent); 1837 } 1838 mouseEventWasIgnored_ = YES; 1839 return; 1840 } 1841 1842 if (mouseEventWasIgnored_) { 1843 // If this is the first mouse event after a previous event that was ignored 1844 // due to the hitTest, send a mouse enter event to the host view. 1845 if (renderWidgetHostView_->render_widget_host_) { 1846 WebMouseEvent enterEvent = 1847 WebInputEventFactory::mouseEvent(theEvent, self); 1848 enterEvent.type = WebInputEvent::MouseMove; 1849 enterEvent.button = WebMouseEvent::ButtonNone; 1850 renderWidgetHostView_->ForwardMouseEvent(enterEvent); 1851 } 1852 } 1853 mouseEventWasIgnored_ = NO; 1854 1855 // TODO(rohitrao): Probably need to handle other mouse down events here. 1856 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) { 1857 if (renderWidgetHostView_->render_widget_host_) 1858 renderWidgetHostView_->render_widget_host_->OnPointerEventActivate(); 1859 1860 // Manually take focus after the click but before forwarding it to the 1861 // renderer. 1862 [[self window] makeFirstResponder:self]; 1863 } 1864 1865 // Don't cancel child popups; killing them on a mouse click would prevent the 1866 // user from positioning the insertion point in the text field spawning the 1867 // popup. A click outside the text field would cause the text field to drop 1868 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel 1869 // the popup anyway, so we're OK. 1870 1871 NSEventType type = [theEvent type]; 1872 if (type == NSLeftMouseDown) 1873 hasOpenMouseDown_ = YES; 1874 else if (type == NSLeftMouseUp) 1875 hasOpenMouseDown_ = NO; 1876 1877 // TODO(suzhe): We should send mouse events to the input method first if it 1878 // wants to handle them. But it won't work without implementing method 1879 // - (NSUInteger)characterIndexForPoint:. 1880 // See: http://code.google.com/p/chromium/issues/detail?id=47141 1881 // Instead of sending mouse events to the input method first, we now just 1882 // simply confirm all ongoing composition here. 1883 if (type == NSLeftMouseDown || type == NSRightMouseDown || 1884 type == NSOtherMouseDown) { 1885 [self confirmComposition]; 1886 } 1887 1888 const WebMouseEvent event = 1889 WebInputEventFactory::mouseEvent(theEvent, self); 1890 renderWidgetHostView_->ForwardMouseEvent(event); 1891} 1892 1893- (BOOL)performKeyEquivalent:(NSEvent*)theEvent { 1894 // |performKeyEquivalent:| is sent to all views of a window, not only down the 1895 // responder chain (cf. "Handling Key Equivalents" in 1896 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html 1897 // ). We only want to handle key equivalents if we're first responder. 1898 if ([[self window] firstResponder] != self) 1899 return NO; 1900 1901 // If the event is reserved by the system, then do not pass it to web content. 1902 if (EventIsReservedBySystem(theEvent)) 1903 return NO; 1904 1905 // If we return |NO| from this function, cocoa will send the key event to 1906 // the menu and only if the menu does not process the event to |keyDown:|. We 1907 // want to send the event to a renderer _before_ sending it to the menu, so 1908 // we need to return |YES| for all events that might be swallowed by the menu. 1909 // We do not return |YES| for every keypress because we don't get |keyDown:| 1910 // events for keys that we handle this way. 1911 NSUInteger modifierFlags = [theEvent modifierFlags]; 1912 if ((modifierFlags & NSCommandKeyMask) == 0) { 1913 // Make sure the menu does not contain key equivalents that don't 1914 // contain cmd. 1915 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]); 1916 return NO; 1917 } 1918 1919 // Command key combinations are sent via performKeyEquivalent rather than 1920 // keyDown:. We just forward this on and if WebCore doesn't want to handle 1921 // it, we let the WebContentsView figure out how to reinject it. 1922 [self keyEvent:theEvent wasKeyEquivalent:YES]; 1923 return YES; 1924} 1925 1926- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event { 1927 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:| 1928 // returned NO. If this function returns |YES|, Cocoa sends the event to 1929 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent 1930 // to us instead of doing key view loop control, ctrl-left/right get handled 1931 // correctly, etc. 1932 // (However, there are still some keys that Cocoa swallows, e.g. the key 1933 // equivalent that Cocoa uses for toggling the input language. In this case, 1934 // that's actually a good thing, though -- see http://crbug.com/26115 .) 1935 return YES; 1936} 1937 1938- (EventHandled)keyEvent:(NSEvent*)theEvent { 1939 if (responderDelegate_ && 1940 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) { 1941 BOOL handled = [responderDelegate_ handleEvent:theEvent]; 1942 if (handled) 1943 return kEventHandled; 1944 } 1945 1946 [self keyEvent:theEvent wasKeyEquivalent:NO]; 1947 return kEventHandled; 1948} 1949 1950- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv { 1951 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent"); 1952 1953 // If the user changes the system hotkey mapping after Chrome has been 1954 // launched, then it is possible that a formerly reserved system hotkey is no 1955 // longer reserved. The hotkey would have skipped the renderer, but would 1956 // also have not been handled by the system. If this is the case, immediately 1957 // return. 1958 // TODO(erikchen): SystemHotkeyHelperMac should use the File System Events 1959 // api to monitor changes to system hotkeys. This logic will have to be 1960 // updated. 1961 // http://crbug.com/383558. 1962 if (EventIsReservedBySystem(theEvent)) 1963 return; 1964 1965 DCHECK([theEvent type] != NSKeyDown || 1966 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask)); 1967 1968 if ([theEvent type] == NSFlagsChanged) { 1969 // Ignore NSFlagsChanged events from the NumLock and Fn keys as 1970 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm"). 1971 int keyCode = [theEvent keyCode]; 1972 if (!keyCode || keyCode == 10 || keyCode == 63) 1973 return; 1974 } 1975 1976 // Don't cancel child popups; the key events are probably what's triggering 1977 // the popup in the first place. 1978 1979 RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_; 1980 DCHECK(widgetHost); 1981 1982 NativeWebKeyboardEvent event(theEvent); 1983 1984 // Force fullscreen windows to close on Escape so they won't keep the keyboard 1985 // grabbed or be stuck onscreen if the renderer is hanging. 1986 if (event.type == NativeWebKeyboardEvent::RawKeyDown && 1987 event.windowsKeyCode == ui::VKEY_ESCAPE && 1988 renderWidgetHostView_->pepper_fullscreen_window()) { 1989 RenderWidgetHostViewMac* parent = 1990 renderWidgetHostView_->fullscreen_parent_host_view(); 1991 if (parent) 1992 parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES; 1993 widgetHost->Shutdown(); 1994 return; 1995 } 1996 1997 // Suppress the escape key up event if necessary. 1998 if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) { 1999 if (event.type == NativeWebKeyboardEvent::KeyUp) 2000 suppressNextEscapeKeyUp_ = NO; 2001 return; 2002 } 2003 2004 // We only handle key down events and just simply forward other events. 2005 if ([theEvent type] != NSKeyDown) { 2006 widgetHost->ForwardKeyboardEvent(event); 2007 2008 // Possibly autohide the cursor. 2009 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent]) 2010 [NSCursor setHiddenUntilMouseMoves:YES]; 2011 2012 return; 2013 } 2014 2015 base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]); 2016 2017 // Records the current marked text state, so that we can know if the marked 2018 // text was deleted or not after handling the key down event. 2019 BOOL oldHasMarkedText = hasMarkedText_; 2020 2021 // This method should not be called recursively. 2022 DCHECK(!handlingKeyDown_); 2023 2024 // Tells insertText: and doCommandBySelector: that we are handling a key 2025 // down event. 2026 handlingKeyDown_ = YES; 2027 2028 // These variables might be set when handling the keyboard event. 2029 // Clear them here so that we can know whether they have changed afterwards. 2030 textToBeInserted_.clear(); 2031 markedText_.clear(); 2032 underlines_.clear(); 2033 unmarkTextCalled_ = NO; 2034 hasEditCommands_ = NO; 2035 editCommands_.clear(); 2036 2037 // Before doing anything with a key down, check to see if plugin IME has been 2038 // cancelled, since the plugin host needs to be informed of that before 2039 // receiving the keydown. 2040 if ([theEvent type] == NSKeyDown) 2041 [self checkForPluginImeCancellation]; 2042 2043 // Sends key down events to input method first, then we can decide what should 2044 // be done according to input method's feedback. 2045 // If a plugin is active, bypass this step since events are forwarded directly 2046 // to the plugin IME. 2047 if (focusedPluginIdentifier_ == -1) 2048 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; 2049 2050 handlingKeyDown_ = NO; 2051 2052 // Indicates if we should send the key event and corresponding editor commands 2053 // after processing the input method result. 2054 BOOL delayEventUntilAfterImeCompostion = NO; 2055 2056 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY 2057 // while an input method is composing or inserting a text. 2058 // Gmail checks this code in its onkeydown handler to stop auto-completing 2059 // e-mail addresses while composing a CJK text. 2060 // If the text to be inserted has only one character, then we don't need this 2061 // trick, because we'll send the text as a key press event instead. 2062 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) { 2063 NativeWebKeyboardEvent fakeEvent = event; 2064 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY 2065 fakeEvent.setKeyIdentifierFromWindowsKeyCode(); 2066 fakeEvent.skip_in_browser = true; 2067 widgetHost->ForwardKeyboardEvent(fakeEvent); 2068 // If this key event was handled by the input method, but 2069 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above) 2070 // enqueued edit commands, then in order to let webkit handle them 2071 // correctly, we need to send the real key event and corresponding edit 2072 // commands after processing the input method result. 2073 // We shouldn't do this if a new marked text was set by the input method, 2074 // otherwise the new marked text might be cancelled by webkit. 2075 if (hasEditCommands_ && !hasMarkedText_) 2076 delayEventUntilAfterImeCompostion = YES; 2077 } else { 2078 if (!editCommands_.empty()) { 2079 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent( 2080 widgetHost->GetRoutingID(), editCommands_)); 2081 } 2082 widgetHost->ForwardKeyboardEvent(event); 2083 } 2084 2085 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the 2086 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will 2087 // be set to NULL. So we check it here and return immediately if it's NULL. 2088 if (!renderWidgetHostView_->render_widget_host_) 2089 return; 2090 2091 // Then send keypress and/or composition related events. 2092 // If there was a marked text or the text to be inserted is longer than 1 2093 // character, then we send the text by calling ConfirmComposition(). 2094 // Otherwise, if the text to be inserted only contains 1 character, then we 2095 // can just send a keypress event which is fabricated by changing the type of 2096 // the keydown event, so that we can retain all necessary informations, such 2097 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to 2098 // prevent the browser from handling it again. 2099 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only 2100 // handle BMP characters here, as we can always insert non-BMP characters as 2101 // text. 2102 BOOL textInserted = NO; 2103 if (textToBeInserted_.length() > 2104 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) { 2105 widgetHost->ImeConfirmComposition( 2106 textToBeInserted_, gfx::Range::InvalidRange(), false); 2107 textInserted = YES; 2108 } 2109 2110 // Updates or cancels the composition. If some text has been inserted, then 2111 // we don't need to cancel the composition explicitly. 2112 if (hasMarkedText_ && markedText_.length()) { 2113 // Sends the updated marked text to the renderer so it can update the 2114 // composition node in WebKit. 2115 // When marked text is available, |selectedRange_| will be the range being 2116 // selected inside the marked text. 2117 widgetHost->ImeSetComposition(markedText_, underlines_, 2118 selectedRange_.location, 2119 NSMaxRange(selectedRange_)); 2120 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) { 2121 if (unmarkTextCalled_) { 2122 widgetHost->ImeConfirmComposition( 2123 base::string16(), gfx::Range::InvalidRange(), false); 2124 } else { 2125 widgetHost->ImeCancelComposition(); 2126 } 2127 } 2128 2129 // If the key event was handled by the input method but it also generated some 2130 // edit commands, then we need to send the real key event and corresponding 2131 // edit commands here. This usually occurs when the input method wants to 2132 // finish current composition session but still wants the application to 2133 // handle the key event. See http://crbug.com/48161 for reference. 2134 if (delayEventUntilAfterImeCompostion) { 2135 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event 2136 // with windowsKeyCode == 0xE5 has already been sent to webkit. 2137 // So before sending the real key down event, we need to send a fake key up 2138 // event to balance it. 2139 NativeWebKeyboardEvent fakeEvent = event; 2140 fakeEvent.type = blink::WebInputEvent::KeyUp; 2141 fakeEvent.skip_in_browser = true; 2142 widgetHost->ForwardKeyboardEvent(fakeEvent); 2143 // Not checking |renderWidgetHostView_->render_widget_host_| here because 2144 // a key event with |skip_in_browser| == true won't be handled by browser, 2145 // thus it won't destroy the widget. 2146 2147 if (!editCommands_.empty()) { 2148 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent( 2149 widgetHost->GetRoutingID(), editCommands_)); 2150 } 2151 widgetHost->ForwardKeyboardEvent(event); 2152 2153 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the 2154 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will 2155 // be set to NULL. So we check it here and return immediately if it's NULL. 2156 if (!renderWidgetHostView_->render_widget_host_) 2157 return; 2158 } 2159 2160 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask; 2161 // Only send a corresponding key press event if there is no marked text. 2162 if (!hasMarkedText_) { 2163 if (!textInserted && textToBeInserted_.length() == 1) { 2164 // If a single character was inserted, then we just send it as a keypress 2165 // event. 2166 event.type = blink::WebInputEvent::Char; 2167 event.text[0] = textToBeInserted_[0]; 2168 event.text[1] = 0; 2169 event.skip_in_browser = true; 2170 widgetHost->ForwardKeyboardEvent(event); 2171 } else if ((!textInserted || delayEventUntilAfterImeCompostion) && 2172 [[theEvent characters] length] > 0 && 2173 (([theEvent modifierFlags] & kCtrlCmdKeyMask) || 2174 (hasEditCommands_ && editCommands_.empty()))) { 2175 // We don't get insertText: calls if ctrl or cmd is down, or the key event 2176 // generates an insert command. So synthesize a keypress event for these 2177 // cases, unless the key event generated any other command. 2178 event.type = blink::WebInputEvent::Char; 2179 event.skip_in_browser = true; 2180 widgetHost->ForwardKeyboardEvent(event); 2181 } 2182 } 2183 2184 // Possibly autohide the cursor. 2185 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent]) 2186 [NSCursor setHiddenUntilMouseMoves:YES]; 2187} 2188 2189- (void)shortCircuitScrollWheelEvent:(NSEvent*)event { 2190 DCHECK(base::mac::IsOSLionOrLater()); 2191 2192 if ([event phase] != NSEventPhaseEnded && 2193 [event phase] != NSEventPhaseCancelled) { 2194 return; 2195 } 2196 2197 if (renderWidgetHostView_->render_widget_host_) { 2198 // History-swiping is not possible if the logic reaches this point. 2199 // Allow rubber-banding in both directions. 2200 bool canRubberbandLeft = true; 2201 bool canRubberbandRight = true; 2202 const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent( 2203 event, self, canRubberbandLeft, canRubberbandRight); 2204 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent); 2205 } 2206 2207 if (endWheelMonitor_) { 2208 [NSEvent removeMonitor:endWheelMonitor_]; 2209 endWheelMonitor_ = nil; 2210 } 2211} 2212 2213- (void)beginGestureWithEvent:(NSEvent*)event { 2214 [responderDelegate_ beginGestureWithEvent:event]; 2215} 2216- (void)endGestureWithEvent:(NSEvent*)event { 2217 [responderDelegate_ endGestureWithEvent:event]; 2218} 2219- (void)touchesMovedWithEvent:(NSEvent*)event { 2220 [responderDelegate_ touchesMovedWithEvent:event]; 2221} 2222- (void)touchesBeganWithEvent:(NSEvent*)event { 2223 [responderDelegate_ touchesBeganWithEvent:event]; 2224} 2225- (void)touchesCancelledWithEvent:(NSEvent*)event { 2226 [responderDelegate_ touchesCancelledWithEvent:event]; 2227} 2228- (void)touchesEndedWithEvent:(NSEvent*)event { 2229 [responderDelegate_ touchesEndedWithEvent:event]; 2230} 2231 2232// This is invoked only on 10.8 or newer when the user taps a word using 2233// three fingers. 2234- (void)quickLookWithEvent:(NSEvent*)event { 2235 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; 2236 TextInputClientMac::GetInstance()->GetStringAtPoint( 2237 renderWidgetHostView_->render_widget_host_, 2238 gfx::Point(point.x, NSHeight([self frame]) - point.y), 2239 ^(NSAttributedString* string, NSPoint baselinePoint) { 2240 if (string && [string length] > 0) { 2241 dispatch_async(dispatch_get_main_queue(), ^{ 2242 [self showDefinitionForAttributedString:string 2243 atPoint:baselinePoint]; 2244 }); 2245 } 2246 } 2247 ); 2248} 2249 2250// This method handles 2 different types of hardware events. 2251// (Apple does not distinguish between them). 2252// a. Scrolling the middle wheel of a mouse. 2253// b. Swiping on the track pad. 2254// 2255// This method is responsible for 2 types of behavior: 2256// a. Scrolling the content of window. 2257// b. Navigating forwards/backwards in history. 2258// 2259// This is a brief description of the logic: 2260// 1. If the content can be scrolled, scroll the content. 2261// (This requires a roundtrip to blink to determine whether the content 2262// can be scrolled.) 2263// Once this logic is triggered, the navigate logic cannot be triggered 2264// until the gesture finishes. 2265// 2. If the user is making a horizontal swipe, start the navigate 2266// forward/backwards UI. 2267// Once this logic is triggered, the user can either cancel or complete 2268// the gesture. If the user completes the gesture, all remaining touches 2269// are swallowed, and not allowed to scroll the content. If the user 2270// cancels the gesture, all remaining touches are forwarded to the content 2271// scroll logic. The user cannot trigger the navigation logic again. 2272- (void)scrollWheel:(NSEvent*)event { 2273 if (responderDelegate_ && 2274 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) { 2275 BOOL handled = [responderDelegate_ handleEvent:event]; 2276 if (handled) 2277 return; 2278 } 2279 2280 // Use an NSEvent monitor to listen for the wheel-end end. This ensures that 2281 // the event is received even when the mouse cursor is no longer over the view 2282 // when the scrolling ends (e.g. if the tab was switched). This is necessary 2283 // for ending rubber-banding in such cases. 2284 if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan && 2285 !endWheelMonitor_) { 2286 endWheelMonitor_ = 2287 [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask 2288 handler:^(NSEvent* blockEvent) { 2289 [self shortCircuitScrollWheelEvent:blockEvent]; 2290 return blockEvent; 2291 }]; 2292 } 2293 2294 // This is responsible for content scrolling! 2295 if (renderWidgetHostView_->render_widget_host_) { 2296 BOOL canRubberbandLeft = [responderDelegate_ canRubberbandLeft:self]; 2297 BOOL canRubberbandRight = [responderDelegate_ canRubberbandRight:self]; 2298 const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent( 2299 event, self, canRubberbandLeft, canRubberbandRight); 2300 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent); 2301 } 2302} 2303 2304// Called repeatedly during a pinch gesture, with incremental change values. 2305- (void)magnifyWithEvent:(NSEvent*)event { 2306 if (renderWidgetHostView_->render_widget_host_) { 2307 // Send a GesturePinchUpdate event. 2308 // Note that we don't attempt to bracket these by GesturePinchBegin/End (or 2309 // GestureSrollBegin/End) as is done for touchscreen. Keeping track of when 2310 // a pinch is active would take a little more work here, and we don't need 2311 // it for anything yet. 2312 const WebGestureEvent& webEvent = 2313 WebInputEventFactory::gestureEvent(event, self); 2314 renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(webEvent); 2315 } 2316} 2317 2318- (void)viewWillMoveToWindow:(NSWindow*)newWindow { 2319 NSWindow* oldWindow = [self window]; 2320 2321 NSNotificationCenter* notificationCenter = 2322 [NSNotificationCenter defaultCenter]; 2323 2324 // Backing property notifications crash on 10.6 when building with the 10.7 2325 // SDK, see http://crbug.com/260595. 2326 static BOOL supportsBackingPropertiesNotification = 2327 SupportsBackingPropertiesChangedNotification(); 2328 2329 if (oldWindow) { 2330 if (supportsBackingPropertiesNotification) { 2331 [notificationCenter 2332 removeObserver:self 2333 name:NSWindowDidChangeBackingPropertiesNotification 2334 object:oldWindow]; 2335 } 2336 [notificationCenter 2337 removeObserver:self 2338 name:NSWindowDidMoveNotification 2339 object:oldWindow]; 2340 [notificationCenter 2341 removeObserver:self 2342 name:NSWindowDidEndLiveResizeNotification 2343 object:oldWindow]; 2344 } 2345 if (newWindow) { 2346 if (supportsBackingPropertiesNotification) { 2347 [notificationCenter 2348 addObserver:self 2349 selector:@selector(windowDidChangeBackingProperties:) 2350 name:NSWindowDidChangeBackingPropertiesNotification 2351 object:newWindow]; 2352 } 2353 [notificationCenter 2354 addObserver:self 2355 selector:@selector(windowChangedGlobalFrame:) 2356 name:NSWindowDidMoveNotification 2357 object:newWindow]; 2358 [notificationCenter 2359 addObserver:self 2360 selector:@selector(windowChangedGlobalFrame:) 2361 name:NSWindowDidEndLiveResizeNotification 2362 object:newWindow]; 2363 } 2364} 2365 2366- (void)updateScreenProperties{ 2367 renderWidgetHostView_->UpdateBackingStoreScaleFactor(); 2368 renderWidgetHostView_->UpdateDisplayLink(); 2369} 2370 2371// http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4 2372- (void)windowDidChangeBackingProperties:(NSNotification*)notification { 2373 // Background tabs check if their scale factor or vsync properties changed 2374 // when they are added to a window. 2375 2376 // Allocating a CGLayerRef with the current scale factor immediately from 2377 // this handler doesn't work. Schedule the backing store update on the 2378 // next runloop cycle, then things are read for CGLayerRef allocations to 2379 // work. 2380 [self performSelector:@selector(updateScreenProperties) 2381 withObject:nil 2382 afterDelay:0]; 2383} 2384 2385- (void)windowChangedGlobalFrame:(NSNotification*)notification { 2386 renderWidgetHostView_->UpdateScreenInfo( 2387 renderWidgetHostView_->GetNativeView()); 2388} 2389 2390- (void)setFrameSize:(NSSize)newSize { 2391 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize"); 2392 2393 // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding 2394 // -setFrame: isn't neccessary. 2395 [super setFrameSize:newSize]; 2396 2397 if (!renderWidgetHostView_->render_widget_host_) 2398 return; 2399 2400 renderWidgetHostView_->render_widget_host_->SendScreenRects(); 2401 renderWidgetHostView_->render_widget_host_->WasResized(); 2402 if (renderWidgetHostView_->delegated_frame_host_) 2403 renderWidgetHostView_->delegated_frame_host_->WasResized(); 2404 2405 // Wait for the frame that WasResize might have requested. If the view is 2406 // being made visible at a new size, then this call will have no effect 2407 // because the view widget is still hidden, and the pause call in WasShown 2408 // will have this effect for us. 2409 renderWidgetHostView_->PauseForPendingResizeOrRepaintsAndDraw(); 2410} 2411 2412- (BOOL)canBecomeKeyView { 2413 if (!renderWidgetHostView_->render_widget_host_) 2414 return NO; 2415 2416 return canBeKeyView_; 2417} 2418 2419- (BOOL)acceptsFirstResponder { 2420 if (!renderWidgetHostView_->render_widget_host_) 2421 return NO; 2422 2423 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_; 2424} 2425 2426- (BOOL)becomeFirstResponder { 2427 if (!renderWidgetHostView_->render_widget_host_) 2428 return NO; 2429 2430 renderWidgetHostView_->render_widget_host_->Focus(); 2431 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true); 2432 renderWidgetHostView_->SetTextInputActive(true); 2433 2434 // Cancel any onging composition text which was left before we lost focus. 2435 // TODO(suzhe): We should do it in -resignFirstResponder: method, but 2436 // somehow that method won't be called when switching among different tabs. 2437 // See http://crbug.com/47209 2438 [self cancelComposition]; 2439 2440 NSNumber* direction = [NSNumber numberWithUnsignedInteger: 2441 [[self window] keyViewSelectionDirection]]; 2442 NSDictionary* userInfo = 2443 [NSDictionary dictionaryWithObject:direction 2444 forKey:kSelectionDirection]; 2445 [[NSNotificationCenter defaultCenter] 2446 postNotificationName:kViewDidBecomeFirstResponder 2447 object:self 2448 userInfo:userInfo]; 2449 2450 return YES; 2451} 2452 2453- (BOOL)resignFirstResponder { 2454 renderWidgetHostView_->SetTextInputActive(false); 2455 if (!renderWidgetHostView_->render_widget_host_) 2456 return YES; 2457 2458 if (closeOnDeactivate_) 2459 renderWidgetHostView_->KillSelf(); 2460 2461 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false); 2462 renderWidgetHostView_->render_widget_host_->Blur(); 2463 2464 // We should cancel any onging composition whenever RWH's Blur() method gets 2465 // called, because in this case, webkit will confirm the ongoing composition 2466 // internally. 2467 [self cancelComposition]; 2468 2469 return YES; 2470} 2471 2472- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { 2473 if (responderDelegate_ && 2474 [responderDelegate_ 2475 respondsToSelector:@selector(validateUserInterfaceItem: 2476 isValidItem:)]) { 2477 BOOL valid; 2478 BOOL known = 2479 [responderDelegate_ validateUserInterfaceItem:item isValidItem:&valid]; 2480 if (known) 2481 return valid; 2482 } 2483 2484 SEL action = [item action]; 2485 2486 if (action == @selector(stopSpeaking:)) { 2487 return renderWidgetHostView_->render_widget_host_->IsRenderView() && 2488 renderWidgetHostView_->IsSpeaking(); 2489 } 2490 if (action == @selector(startSpeaking:)) { 2491 return renderWidgetHostView_->render_widget_host_->IsRenderView() && 2492 renderWidgetHostView_->SupportsSpeech(); 2493 } 2494 2495 // For now, these actions are always enabled for render view, 2496 // this is sub-optimal. 2497 // TODO(suzhe): Plumb the "can*" methods up from WebCore. 2498 if (action == @selector(undo:) || 2499 action == @selector(redo:) || 2500 action == @selector(cut:) || 2501 action == @selector(copy:) || 2502 action == @selector(copyToFindPboard:) || 2503 action == @selector(paste:) || 2504 action == @selector(pasteAndMatchStyle:)) { 2505 return renderWidgetHostView_->render_widget_host_->IsRenderView(); 2506 } 2507 2508 return editCommand_helper_->IsMenuItemEnabled(action, self); 2509} 2510 2511- (RenderWidgetHostViewMac*)renderWidgetHostViewMac { 2512 return renderWidgetHostView_.get(); 2513} 2514 2515// Determine whether we should autohide the cursor (i.e., hide it until mouse 2516// move) for the given event. Customize here to be more selective about which 2517// key presses to autohide on. 2518+ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event { 2519 return ([event type] == NSKeyDown && 2520 !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO; 2521} 2522 2523- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute 2524 index:(NSUInteger)index 2525 maxCount:(NSUInteger)maxCount { 2526 NSArray* fullArray = [self accessibilityAttributeValue:attribute]; 2527 NSUInteger totalLength = [fullArray count]; 2528 if (index >= totalLength) 2529 return nil; 2530 NSUInteger length = MIN(totalLength - index, maxCount); 2531 return [fullArray subarrayWithRange:NSMakeRange(index, length)]; 2532} 2533 2534- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute { 2535 NSArray* fullArray = [self accessibilityAttributeValue:attribute]; 2536 return [fullArray count]; 2537} 2538 2539- (id)accessibilityAttributeValue:(NSString *)attribute { 2540 BrowserAccessibilityManager* manager = 2541 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager(); 2542 2543 // Contents specifies document view of RenderWidgetHostViewCocoa provided by 2544 // BrowserAccessibilityManager. Children includes all subviews in addition to 2545 // contents. Currently we do not have subviews besides the document view. 2546 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] || 2547 [attribute isEqualToString:NSAccessibilityContentsAttribute]) && 2548 manager) { 2549 return [NSArray arrayWithObjects:manager-> 2550 GetRoot()->ToBrowserAccessibilityCocoa(), nil]; 2551 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 2552 return NSAccessibilityScrollAreaRole; 2553 } 2554 id ret = [super accessibilityAttributeValue:attribute]; 2555 return ret; 2556} 2557 2558- (NSArray*)accessibilityAttributeNames { 2559 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; 2560 [ret addObject:NSAccessibilityContentsAttribute]; 2561 [ret addObjectsFromArray:[super accessibilityAttributeNames]]; 2562 return ret; 2563} 2564 2565- (id)accessibilityHitTest:(NSPoint)point { 2566 BrowserAccessibilityManager* manager = 2567 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager(); 2568 if (!manager) 2569 return self; 2570 NSPoint pointInWindow = [[self window] convertScreenToBase:point]; 2571 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil]; 2572 localPoint.y = NSHeight([self bounds]) - localPoint.y; 2573 BrowserAccessibilityCocoa* root = 2574 manager->GetRoot()->ToBrowserAccessibilityCocoa(); 2575 id obj = [root accessibilityHitTest:localPoint]; 2576 return obj; 2577} 2578 2579- (BOOL)accessibilityIsIgnored { 2580 BrowserAccessibilityManager* manager = 2581 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager(); 2582 return !manager; 2583} 2584 2585- (NSUInteger)accessibilityGetIndexOf:(id)child { 2586 BrowserAccessibilityManager* manager = 2587 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager(); 2588 // Only child is root. 2589 if (manager && 2590 manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) { 2591 return 0; 2592 } else { 2593 return NSNotFound; 2594 } 2595} 2596 2597- (id)accessibilityFocusedUIElement { 2598 BrowserAccessibilityManager* manager = 2599 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager(); 2600 if (manager) { 2601 BrowserAccessibility* focused_item = manager->GetFocus(NULL); 2602 DCHECK(focused_item); 2603 if (focused_item) { 2604 BrowserAccessibilityCocoa* focused_item_cocoa = 2605 focused_item->ToBrowserAccessibilityCocoa(); 2606 DCHECK(focused_item_cocoa); 2607 if (focused_item_cocoa) 2608 return focused_item_cocoa; 2609 } 2610 } 2611 return [super accessibilityFocusedUIElement]; 2612} 2613 2614// Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm 2615// with minor modifications for code style and commenting. 2616// 2617// The 'public' interface is -setToolTipAtMousePoint:. This differs from 2618// -setToolTip: in that the updated tooltip takes effect immediately, 2619// without the user's having to move the mouse out of and back into the view. 2620// 2621// Unfortunately, doing this requires sending fake mouseEnter/Exit events to 2622// the view, which in turn requires overriding some internal tracking-rect 2623// methods (to keep track of its owner & userdata, which need to be filled out 2624// in the fake events.) --snej 7/6/09 2625 2626 2627/* 2628 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 2629 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com) 2630 * 2631 * Redistribution and use in source and binary forms, with or without 2632 * modification, are permitted provided that the following conditions 2633 * are met: 2634 * 2635 * 1. Redistributions of source code must retain the above copyright 2636 * notice, this list of conditions and the following disclaimer. 2637 * 2. Redistributions in binary form must reproduce the above copyright 2638 * notice, this list of conditions and the following disclaimer in the 2639 * documentation and/or other materials provided with the distribution. 2640 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 2641 * its contributors may be used to endorse or promote products derived 2642 * from this software without specific prior written permission. 2643 * 2644 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 2645 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 2646 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 2647 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 2648 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 2649 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 2650 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 2651 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2652 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2653 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2654 */ 2655 2656// Any non-zero value will do, but using something recognizable might help us 2657// debug some day. 2658static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE; 2659 2660// Override of a public NSView method, replacing the inherited functionality. 2661// See above for rationale. 2662- (NSTrackingRectTag)addTrackingRect:(NSRect)rect 2663 owner:(id)owner 2664 userData:(void *)data 2665 assumeInside:(BOOL)assumeInside { 2666 DCHECK(trackingRectOwner_ == nil); 2667 trackingRectOwner_ = owner; 2668 trackingRectUserData_ = data; 2669 return kTrackingRectTag; 2670} 2671 2672// Override of (apparently) a private NSView method(!) See above for rationale. 2673- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect 2674 owner:(id)owner 2675 userData:(void *)data 2676 assumeInside:(BOOL)assumeInside 2677 useTrackingNum:(int)tag { 2678 DCHECK(tag == 0 || tag == kTrackingRectTag); 2679 DCHECK(trackingRectOwner_ == nil); 2680 trackingRectOwner_ = owner; 2681 trackingRectUserData_ = data; 2682 return kTrackingRectTag; 2683} 2684 2685// Override of (apparently) a private NSView method(!) See above for rationale. 2686- (void)_addTrackingRects:(NSRect *)rects 2687 owner:(id)owner 2688 userDataList:(void **)userDataList 2689 assumeInsideList:(BOOL *)assumeInsideList 2690 trackingNums:(NSTrackingRectTag *)trackingNums 2691 count:(int)count { 2692 DCHECK(count == 1); 2693 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag); 2694 DCHECK(trackingRectOwner_ == nil); 2695 trackingRectOwner_ = owner; 2696 trackingRectUserData_ = userDataList[0]; 2697 trackingNums[0] = kTrackingRectTag; 2698} 2699 2700// Override of a public NSView method, replacing the inherited functionality. 2701// See above for rationale. 2702- (void)removeTrackingRect:(NSTrackingRectTag)tag { 2703 if (tag == 0) 2704 return; 2705 2706 if (tag == kTrackingRectTag) { 2707 trackingRectOwner_ = nil; 2708 return; 2709 } 2710 2711 if (tag == lastToolTipTag_) { 2712 [super removeTrackingRect:tag]; 2713 lastToolTipTag_ = 0; 2714 return; 2715 } 2716 2717 // If any other tracking rect is being removed, we don't know how it was 2718 // created and it's possible there's a leak involved (see Radar 3500217). 2719 NOTREACHED(); 2720} 2721 2722// Override of (apparently) a private NSView method(!) 2723- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count { 2724 for (int i = 0; i < count; ++i) { 2725 int tag = tags[i]; 2726 if (tag == 0) 2727 continue; 2728 DCHECK(tag == kTrackingRectTag); 2729 trackingRectOwner_ = nil; 2730 } 2731} 2732 2733// Sends a fake NSMouseExited event to the view for its current tracking rect. 2734- (void)_sendToolTipMouseExited { 2735 // Nothing matters except window, trackingNumber, and userData. 2736 int windowNumber = [[self window] windowNumber]; 2737 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited 2738 location:NSZeroPoint 2739 modifierFlags:0 2740 timestamp:0 2741 windowNumber:windowNumber 2742 context:NULL 2743 eventNumber:0 2744 trackingNumber:kTrackingRectTag 2745 userData:trackingRectUserData_]; 2746 [trackingRectOwner_ mouseExited:fakeEvent]; 2747} 2748 2749// Sends a fake NSMouseEntered event to the view for its current tracking rect. 2750- (void)_sendToolTipMouseEntered { 2751 // Nothing matters except window, trackingNumber, and userData. 2752 int windowNumber = [[self window] windowNumber]; 2753 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered 2754 location:NSZeroPoint 2755 modifierFlags:0 2756 timestamp:0 2757 windowNumber:windowNumber 2758 context:NULL 2759 eventNumber:0 2760 trackingNumber:kTrackingRectTag 2761 userData:trackingRectUserData_]; 2762 [trackingRectOwner_ mouseEntered:fakeEvent]; 2763} 2764 2765// Sets the view's current tooltip, to be displayed at the current mouse 2766// location. (This does not make the tooltip appear -- as usual, it only 2767// appears after a delay.) Pass null to remove the tooltip. 2768- (void)setToolTipAtMousePoint:(NSString *)string { 2769 NSString *toolTip = [string length] == 0 ? nil : string; 2770 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) || 2771 (!toolTip && !toolTip_)) { 2772 return; 2773 } 2774 2775 if (toolTip_) { 2776 [self _sendToolTipMouseExited]; 2777 } 2778 2779 toolTip_.reset([toolTip copy]); 2780 2781 if (toolTip) { 2782 // See radar 3500217 for why we remove all tooltips 2783 // rather than just the single one we created. 2784 [self removeAllToolTips]; 2785 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000); 2786 lastToolTipTag_ = [self addToolTipRect:wideOpenRect 2787 owner:self 2788 userData:NULL]; 2789 [self _sendToolTipMouseEntered]; 2790 } 2791} 2792 2793// NSView calls this to get the text when displaying the tooltip. 2794- (NSString *)view:(NSView *)view 2795 stringForToolTip:(NSToolTipTag)tag 2796 point:(NSPoint)point 2797 userData:(void *)data { 2798 return [[toolTip_ copy] autorelease]; 2799} 2800 2801// Below is our NSTextInputClient implementation. 2802// 2803// When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following 2804// functions to process this event. 2805// 2806// [WebHTMLView keyDown] -> 2807// EventHandler::keyEvent() -> 2808// ... 2809// [WebEditorClient handleKeyboardEvent] -> 2810// [WebHTMLView _interceptEditingKeyEvent] -> 2811// [NSResponder interpretKeyEvents] -> 2812// [WebHTMLView insertText] -> 2813// Editor::insertText() 2814// 2815// Unfortunately, it is hard for Chromium to use this implementation because 2816// it causes key-typing jank. 2817// RenderWidgetHostViewMac is running in a browser process. On the other 2818// hand, Editor and EventHandler are running in a renderer process. 2819// So, if we used this implementation, a NSKeyDown event is dispatched to 2820// the following functions of Chromium. 2821// 2822// [RenderWidgetHostViewMac keyEvent] (browser) -> 2823// |Sync IPC (KeyDown)| (*1) -> 2824// EventHandler::keyEvent() (renderer) -> 2825// ... 2826// EditorClientImpl::handleKeyboardEvent() (renderer) -> 2827// |Sync IPC| (*2) -> 2828// [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) -> 2829// [self interpretKeyEvents] -> 2830// [RenderWidgetHostViewMac insertText] (browser) -> 2831// |Async IPC| -> 2832// Editor::insertText() (renderer) 2833// 2834// (*1) we need to wait until this call finishes since WebHTMLView uses the 2835// result of EventHandler::keyEvent(). 2836// (*2) we need to wait until this call finishes since WebEditorClient uses 2837// the result of [WebHTMLView _interceptEditingKeyEvent]. 2838// 2839// This needs many sync IPC messages sent between a browser and a renderer for 2840// each key event, which would probably result in key-typing jank. 2841// To avoid this problem, this implementation processes key events (and input 2842// method events) totally in a browser process and sends asynchronous input 2843// events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a 2844// renderer process. 2845// 2846// [RenderWidgetHostViewMac keyEvent] (browser) -> 2847// |Async IPC (RawKeyDown)| -> 2848// [self interpretKeyEvents] -> 2849// [RenderWidgetHostViewMac insertText] (browser) -> 2850// |Async IPC (Char)| -> 2851// Editor::insertText() (renderer) 2852// 2853// Since this implementation doesn't have to wait any IPC calls, this doesn't 2854// make any key-typing jank. --hbono 7/23/09 2855// 2856extern "C" { 2857extern NSString *NSTextInputReplacementRangeAttributeName; 2858} 2859 2860- (NSArray *)validAttributesForMarkedText { 2861 // This code is just copied from WebKit except renaming variables. 2862 if (!validAttributesForMarkedText_) { 2863 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects: 2864 NSUnderlineStyleAttributeName, 2865 NSUnderlineColorAttributeName, 2866 NSMarkedClauseSegmentAttributeName, 2867 NSTextInputReplacementRangeAttributeName, 2868 nil]); 2869 } 2870 return validAttributesForMarkedText_.get(); 2871} 2872 2873- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { 2874 DCHECK([self window]); 2875 // |thePoint| is in screen coordinates, but needs to be converted to WebKit 2876 // coordinates (upper left origin). Scroll offsets will be taken care of in 2877 // the renderer. 2878 thePoint = [[self window] convertScreenToBase:thePoint]; 2879 thePoint = [self convertPoint:thePoint fromView:nil]; 2880 thePoint.y = NSHeight([self frame]) - thePoint.y; 2881 2882 NSUInteger index = 2883 TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint( 2884 renderWidgetHostView_->render_widget_host_, 2885 gfx::Point(thePoint.x, thePoint.y)); 2886 return index; 2887} 2888 2889- (NSRect)firstViewRectForCharacterRange:(NSRange)theRange 2890 actualRange:(NSRangePointer)actualRange { 2891 NSRect rect; 2892 if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange( 2893 theRange, 2894 &rect, 2895 actualRange)) { 2896 rect = TextInputClientMac::GetInstance()->GetFirstRectForRange( 2897 renderWidgetHostView_->render_widget_host_, theRange); 2898 2899 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery. 2900 if (actualRange) 2901 *actualRange = theRange; 2902 } 2903 2904 // The returned rectangle is in WebKit coordinates (upper left origin), so 2905 // flip the coordinate system. 2906 NSRect viewFrame = [self frame]; 2907 rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect); 2908 return rect; 2909} 2910 2911- (NSRect)firstRectForCharacterRange:(NSRange)theRange 2912 actualRange:(NSRangePointer)actualRange { 2913 NSRect rect = [self firstViewRectForCharacterRange:theRange 2914 actualRange:actualRange]; 2915 2916 // Convert into screen coordinates for return. 2917 rect = [self convertRect:rect toView:nil]; 2918 rect.origin = [[self window] convertBaseToScreen:rect.origin]; 2919 return rect; 2920} 2921 2922- (NSRange)markedRange { 2923 // An input method calls this method to check if an application really has 2924 // a text being composed when hasMarkedText call returns true. 2925 // Returns the range saved in the setMarkedText method so the input method 2926 // calls the setMarkedText method and we can update the composition node 2927 // there. (When this method returns an empty range, the input method doesn't 2928 // call the setMarkedText method.) 2929 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0); 2930} 2931 2932- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range 2933 actualRange:(NSRangePointer)actualRange { 2934 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery. 2935 if (actualRange) 2936 *actualRange = range; 2937 NSAttributedString* str = 2938 TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange( 2939 renderWidgetHostView_->render_widget_host_, range); 2940 return str; 2941} 2942 2943- (NSInteger)conversationIdentifier { 2944 return reinterpret_cast<NSInteger>(self); 2945} 2946 2947// Each RenderWidgetHostViewCocoa has its own input context, but we return 2948// nil when the caret is in non-editable content or password box to avoid 2949// making input methods do their work. 2950- (NSTextInputContext *)inputContext { 2951 if (focusedPluginIdentifier_ != -1) 2952 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext]; 2953 2954 switch(renderWidgetHostView_->text_input_type_) { 2955 case ui::TEXT_INPUT_TYPE_NONE: 2956 case ui::TEXT_INPUT_TYPE_PASSWORD: 2957 return nil; 2958 default: 2959 return [super inputContext]; 2960 } 2961} 2962 2963- (BOOL)hasMarkedText { 2964 // An input method calls this function to figure out whether or not an 2965 // application is really composing a text. If it is composing, it calls 2966 // the markedRange method, and maybe calls the setMarkedText method. 2967 // It seems an input method usually calls this function when it is about to 2968 // cancel an ongoing composition. If an application has a non-empty marked 2969 // range, it calls the setMarkedText method to delete the range. 2970 return hasMarkedText_; 2971} 2972 2973- (void)unmarkText { 2974 // Delete the composition node of the renderer and finish an ongoing 2975 // composition. 2976 // It seems an input method calls the setMarkedText method and set an empty 2977 // text when it cancels an ongoing composition, i.e. I have never seen an 2978 // input method calls this method. 2979 hasMarkedText_ = NO; 2980 markedText_.clear(); 2981 underlines_.clear(); 2982 2983 // If we are handling a key down event, then ConfirmComposition() will be 2984 // called in keyEvent: method. 2985 if (!handlingKeyDown_) { 2986 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition( 2987 base::string16(), gfx::Range::InvalidRange(), false); 2988 } else { 2989 unmarkTextCalled_ = YES; 2990 } 2991} 2992 2993- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange 2994 replacementRange:(NSRange)replacementRange { 2995 // An input method updates the composition string. 2996 // We send the given text and range to the renderer so it can update the 2997 // composition node of WebKit. 2998 // TODO(suzhe): It's hard for us to support replacementRange without accessing 2999 // the full web content. 3000 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; 3001 NSString* im_text = isAttributedString ? [string string] : string; 3002 int length = [im_text length]; 3003 3004 // |markedRange_| will get set on a callback from ImeSetComposition(). 3005 selectedRange_ = newSelRange; 3006 markedText_ = base::SysNSStringToUTF16(im_text); 3007 hasMarkedText_ = (length > 0); 3008 3009 underlines_.clear(); 3010 if (isAttributedString) { 3011 ExtractUnderlines(string, &underlines_); 3012 } else { 3013 // Use a thin black underline by default. 3014 underlines_.push_back(blink::WebCompositionUnderline( 3015 0, length, SK_ColorBLACK, false, SK_ColorTRANSPARENT)); 3016 } 3017 3018 // If we are handling a key down event, then SetComposition() will be 3019 // called in keyEvent: method. 3020 // Input methods of Mac use setMarkedText calls with an empty text to cancel 3021 // an ongoing composition. So, we should check whether or not the given text 3022 // is empty to update the input method state. (Our input method backend can 3023 // automatically cancels an ongoing composition when we send an empty text. 3024 // So, it is OK to send an empty text to the renderer.) 3025 if (!handlingKeyDown_) { 3026 renderWidgetHostView_->render_widget_host_->ImeSetComposition( 3027 markedText_, underlines_, 3028 newSelRange.location, NSMaxRange(newSelRange)); 3029 } 3030} 3031 3032- (void)doCommandBySelector:(SEL)selector { 3033 // An input method calls this function to dispatch an editing command to be 3034 // handled by this view. 3035 if (selector == @selector(noop:)) 3036 return; 3037 3038 std::string command( 3039 [RenderWidgetHostViewMacEditCommandHelper:: 3040 CommandNameForSelector(selector) UTF8String]); 3041 3042 // If this method is called when handling a key down event, then we need to 3043 // handle the command in the key event handler. Otherwise we can just handle 3044 // it here. 3045 if (handlingKeyDown_) { 3046 hasEditCommands_ = YES; 3047 // We ignore commands that insert characters, because this was causing 3048 // strange behavior (e.g. tab always inserted a tab rather than moving to 3049 // the next field on the page). 3050 if (!StartsWithASCII(command, "insert", false)) 3051 editCommands_.push_back(EditCommand(command, "")); 3052 } else { 3053 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_; 3054 rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(), 3055 command, "")); 3056 } 3057} 3058 3059- (void)insertText:(id)string replacementRange:(NSRange)replacementRange { 3060 // An input method has characters to be inserted. 3061 // Same as Linux, Mac calls this method not only: 3062 // * when an input method finishs composing text, but also; 3063 // * when we type an ASCII character (without using input methods). 3064 // When we aren't using input methods, we should send the given character as 3065 // a Char event so it is dispatched to an onkeypress() event handler of 3066 // JavaScript. 3067 // On the other hand, when we are using input methods, we should send the 3068 // given characters as an input method event and prevent the characters from 3069 // being dispatched to onkeypress() event handlers. 3070 // Text inserting might be initiated by other source instead of keyboard 3071 // events, such as the Characters dialog. In this case the text should be 3072 // sent as an input method event as well. 3073 // TODO(suzhe): It's hard for us to support replacementRange without accessing 3074 // the full web content. 3075 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; 3076 NSString* im_text = isAttributedString ? [string string] : string; 3077 if (handlingKeyDown_) { 3078 textToBeInserted_.append(base::SysNSStringToUTF16(im_text)); 3079 } else { 3080 gfx::Range replacement_range(replacementRange); 3081 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition( 3082 base::SysNSStringToUTF16(im_text), replacement_range, false); 3083 } 3084 3085 // Inserting text will delete all marked text automatically. 3086 hasMarkedText_ = NO; 3087} 3088 3089- (void)insertText:(id)string { 3090 [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)]; 3091} 3092 3093- (void)viewDidMoveToWindow { 3094 if ([self window]) 3095 [self updateScreenProperties]; 3096 3097 if (canBeKeyView_) { 3098 NSWindow* newWindow = [self window]; 3099 // Pointer comparison only, since we don't know if lastWindow_ is still 3100 // valid. 3101 if (newWindow) { 3102 // If we move into a new window, refresh the frame information. We 3103 // don't need to do it if it was the same window as it used to be in, 3104 // since that case is covered by WasShown(). We only want to do this for 3105 // real browser views, not popups. 3106 if (newWindow != lastWindow_) { 3107 lastWindow_ = newWindow; 3108 renderWidgetHostView_->WindowFrameChanged(); 3109 } 3110 } 3111 } 3112 3113 // If we switch windows (or are removed from the view hierarchy), cancel any 3114 // open mouse-downs. 3115 if (hasOpenMouseDown_) { 3116 WebMouseEvent event; 3117 event.type = WebInputEvent::MouseUp; 3118 event.button = WebMouseEvent::ButtonLeft; 3119 renderWidgetHostView_->ForwardMouseEvent(event); 3120 3121 hasOpenMouseDown_ = NO; 3122 } 3123} 3124 3125- (void)undo:(id)sender { 3126 WebContents* web_contents = renderWidgetHostView_->GetWebContents(); 3127 if (web_contents) 3128 web_contents->Undo(); 3129} 3130 3131- (void)redo:(id)sender { 3132 WebContents* web_contents = renderWidgetHostView_->GetWebContents(); 3133 if (web_contents) 3134 web_contents->Redo(); 3135} 3136 3137- (void)cut:(id)sender { 3138 WebContents* web_contents = renderWidgetHostView_->GetWebContents(); 3139 if (web_contents) 3140 web_contents->Cut(); 3141} 3142 3143- (void)copy:(id)sender { 3144 WebContents* web_contents = renderWidgetHostView_->GetWebContents(); 3145 if (web_contents) 3146 web_contents->Copy(); 3147} 3148 3149- (void)copyToFindPboard:(id)sender { 3150 WebContents* web_contents = renderWidgetHostView_->GetWebContents(); 3151 if (web_contents) 3152 web_contents->CopyToFindPboard(); 3153} 3154 3155- (void)paste:(id)sender { 3156 WebContents* web_contents = renderWidgetHostView_->GetWebContents(); 3157 if (web_contents) 3158 web_contents->Paste(); 3159} 3160 3161- (void)pasteAndMatchStyle:(id)sender { 3162 WebContents* web_contents = renderWidgetHostView_->GetWebContents(); 3163 if (web_contents) 3164 web_contents->PasteAndMatchStyle(); 3165} 3166 3167- (void)selectAll:(id)sender { 3168 // editCommand_helper_ adds implementations for most NSResponder methods 3169 // dynamically. But the renderer side only sends selection results back to 3170 // the browser if they were triggered by a keyboard event or went through 3171 // one of the Select methods on RWH. Since selectAll: is called from the 3172 // menu handler, neither is true. 3173 // Explicitly call SelectAll() here to make sure the renderer returns 3174 // selection results. 3175 WebContents* web_contents = renderWidgetHostView_->GetWebContents(); 3176 if (web_contents) 3177 web_contents->SelectAll(); 3178} 3179 3180- (void)startSpeaking:(id)sender { 3181 renderWidgetHostView_->SpeakSelection(); 3182} 3183 3184- (void)stopSpeaking:(id)sender { 3185 renderWidgetHostView_->StopSpeaking(); 3186} 3187 3188- (void)cancelComposition { 3189 if (!hasMarkedText_) 3190 return; 3191 3192 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:] 3193 // doesn't call any NSTextInput functions, such as setMarkedText or 3194 // insertText. So, we need to send an IPC message to a renderer so it can 3195 // delete the composition node. 3196 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; 3197 [currentInputManager markedTextAbandoned:self]; 3198 3199 hasMarkedText_ = NO; 3200 // Should not call [self unmarkText] here, because it'll send unnecessary 3201 // cancel composition IPC message to the renderer. 3202} 3203 3204- (void)confirmComposition { 3205 if (!hasMarkedText_) 3206 return; 3207 3208 if (renderWidgetHostView_->render_widget_host_) 3209 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition( 3210 base::string16(), gfx::Range::InvalidRange(), false); 3211 3212 [self cancelComposition]; 3213} 3214 3215- (void)setPluginImeActive:(BOOL)active { 3216 if (active == pluginImeActive_) 3217 return; 3218 3219 pluginImeActive_ = active; 3220 if (!active) { 3221 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition]; 3222 renderWidgetHostView_->PluginImeCompositionCompleted( 3223 base::string16(), focusedPluginIdentifier_); 3224 } 3225} 3226 3227- (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId { 3228 if (focused) 3229 focusedPluginIdentifier_ = pluginId; 3230 else if (focusedPluginIdentifier_ == pluginId) 3231 focusedPluginIdentifier_ = -1; 3232 3233 // Whenever plugin focus changes, plugin IME resets. 3234 [self setPluginImeActive:NO]; 3235} 3236 3237- (BOOL)postProcessEventForPluginIme:(NSEvent*)event { 3238 if (!pluginImeActive_) 3239 return false; 3240 3241 ComplexTextInputPanel* inputPanel = 3242 [ComplexTextInputPanel sharedComplexTextInputPanel]; 3243 NSString* composited_string = nil; 3244 BOOL handled = [inputPanel interpretKeyEvent:event 3245 string:&composited_string]; 3246 if (composited_string) { 3247 renderWidgetHostView_->PluginImeCompositionCompleted( 3248 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_); 3249 pluginImeActive_ = NO; 3250 } 3251 return handled; 3252} 3253 3254- (void)checkForPluginImeCancellation { 3255 if (pluginImeActive_ && 3256 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) { 3257 renderWidgetHostView_->PluginImeCompositionCompleted( 3258 base::string16(), focusedPluginIdentifier_); 3259 pluginImeActive_ = NO; 3260 } 3261} 3262 3263// Overriding a NSResponder method to support application services. 3264 3265- (id)validRequestorForSendType:(NSString*)sendType 3266 returnType:(NSString*)returnType { 3267 id requestor = nil; 3268 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType]; 3269 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType]; 3270 BOOL hasText = !renderWidgetHostView_->selected_text().empty(); 3271 BOOL takesText = 3272 renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE; 3273 3274 if (sendTypeIsString && hasText && !returnType) { 3275 requestor = self; 3276 } else if (!sendType && returnTypeIsString && takesText) { 3277 requestor = self; 3278 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) { 3279 requestor = self; 3280 } else { 3281 requestor = [super validRequestorForSendType:sendType 3282 returnType:returnType]; 3283 } 3284 return requestor; 3285} 3286 3287- (void)viewWillStartLiveResize { 3288 [super viewWillStartLiveResize]; 3289 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_; 3290 if (widget) 3291 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true)); 3292} 3293 3294- (void)viewDidEndLiveResize { 3295 [super viewDidEndLiveResize]; 3296 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_; 3297 if (widget) 3298 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false)); 3299} 3300 3301- (void)updateCursor:(NSCursor*)cursor { 3302 if (currentCursor_ == cursor) 3303 return; 3304 3305 currentCursor_.reset([cursor retain]); 3306 [[self window] invalidateCursorRectsForView:self]; 3307} 3308 3309- (void)popupWindowWillClose:(NSNotification *)notification { 3310 renderWidgetHostView_->KillSelf(); 3311} 3312 3313@end 3314 3315// 3316// Supporting application services 3317// 3318@implementation RenderWidgetHostViewCocoa(NSServicesRequests) 3319 3320- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard 3321 types:(NSArray*)types { 3322 const std::string& str = renderWidgetHostView_->selected_text(); 3323 if (![types containsObject:NSStringPboardType] || str.empty()) return NO; 3324 3325 base::scoped_nsobject<NSString> text( 3326 [[NSString alloc] initWithUTF8String:str.c_str()]); 3327 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType]; 3328 [pboard declareTypes:toDeclare owner:nil]; 3329 return [pboard setString:text forType:NSStringPboardType]; 3330} 3331 3332- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard { 3333 NSString *string = [pboard stringForType:NSStringPboardType]; 3334 if (!string) return NO; 3335 3336 // If the user is currently using an IME, confirm the IME input, 3337 // and then insert the text from the service, the same as TextEdit and Safari. 3338 [self confirmComposition]; 3339 [self insertText:string]; 3340 return YES; 3341} 3342 3343- (BOOL)isOpaque { 3344 return YES; 3345} 3346 3347// "-webkit-app-region: drag | no-drag" is implemented on Mac by excluding 3348// regions that are not draggable. (See ControlRegionView in 3349// native_app_window_cocoa.mm). This requires the render host view to be 3350// draggable by default. 3351- (BOOL)mouseDownCanMoveWindow { 3352 return YES; 3353} 3354 3355@end 3356