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.
5 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
7 #import <objc/runtime.h>
8 #include <QuartzCore/QuartzCore.h>
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/command_line.h"
14 #include "base/debug/crash_logging.h"
15 #include "base/debug/trace_event.h"
16 #include "base/logging.h"
17 #include "base/mac/mac_util.h"
18 #include "base/mac/scoped_cftyperef.h"
19 #import "base/mac/scoped_nsobject.h"
20 #include "base/mac/sdk_forward_declarations.h"
21 #include "base/message_loop/message_loop.h"
22 #include "base/metrics/histogram.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/strings/sys_string_conversions.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/sys_info.h"
28 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
29 #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
30 #include "content/browser/renderer_host/backing_store_mac.h"
31 #include "content/browser/renderer_host/backing_store_manager.h"
32 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
33 #include "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
34 #include "content/browser/renderer_host/compositing_iosurface_mac.h"
35 #include "content/browser/renderer_host/render_view_host_impl.h"
36 #import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
37 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
38 #import "content/browser/renderer_host/text_input_client_mac.h"
39 #include "content/common/accessibility_messages.h"
40 #include "content/common/edit_command.h"
41 #include "content/common/gpu/gpu_messages.h"
42 #include "content/common/input_messages.h"
43 #include "content/common/view_messages.h"
44 #include "content/common/webplugin_geometry.h"
45 #include "content/port/browser/render_widget_host_view_frame_subscriber.h"
46 #include "content/public/browser/browser_thread.h"
47 #include "content/public/browser/native_web_keyboard_event.h"
48 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
49 #include "content/public/browser/user_metrics.h"
50 #include "content/public/common/content_switches.h"
51 #include "skia/ext/platform_canvas.h"
52 #include "third_party/WebKit/public/web/WebInputEvent.h"
53 #include "third_party/WebKit/public/web/WebScreenInfo.h"
54 #include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
55 #import "third_party/mozilla/ComplexTextInputPanel.h"
56 #include "ui/base/cocoa/animation_utils.h"
57 #import "ui/base/cocoa/fullscreen_window_manager.h"
58 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
59 #include "ui/events/keycodes/keyboard_codes.h"
60 #include "ui/base/layout.h"
61 #include "ui/gfx/display.h"
62 #include "ui/gfx/point.h"
63 #include "ui/gfx/rect_conversions.h"
64 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
65 #include "ui/gfx/screen.h"
66 #include "ui/gfx/size_conversions.h"
67 #include "ui/gl/io_surface_support_mac.h"
69 using content::BackingStoreMac;
70 using content::BrowserAccessibility;
71 using content::BrowserAccessibilityManager;
72 using content::EditCommand;
73 using content::NativeWebKeyboardEvent;
74 using content::RenderViewHostImpl;
75 using content::RenderWidgetHostImpl;
76 using content::RenderWidgetHostViewMac;
77 using content::RenderWidgetHostViewMacEditCommandHelper;
78 using content::TextInputClientMac;
79 using WebKit::WebInputEvent;
80 using WebKit::WebInputEventFactory;
81 using WebKit::WebMouseEvent;
82 using WebKit::WebMouseWheelEvent;
84 enum CoreAnimationStatus {
85 CORE_ANIMATION_DISABLED,
86 CORE_ANIMATION_ENABLED_LAZY,
87 CORE_ANIMATION_ENABLED_ALWAYS,
90 static CoreAnimationStatus GetCoreAnimationStatus() {
91 // TODO(sail) Remove this.
92 if (!CommandLine::ForCurrentProcess()->HasSwitch(
93 switches::kUseCoreAnimation)) {
94 return CORE_ANIMATION_DISABLED;
96 if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
97 switches::kUseCoreAnimation) == "lazy") {
98 return CORE_ANIMATION_ENABLED_LAZY;
100 if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
101 switches::kUseCoreAnimation) == "disabled") {
102 return CORE_ANIMATION_DISABLED;
104 return CORE_ANIMATION_ENABLED_ALWAYS;
107 // These are not documented, so use only after checking -respondsToSelector:.
108 @interface NSApplication (UndocumentedSpeechMethods)
109 - (void)speakString:(NSString*)string;
110 - (void)stopSpeaking:(id)sender;
114 // Declare things that are part of the 10.7 SDK.
115 #if !defined(MAC_OS_X_VERSION_10_7) || \
116 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
117 @interface NSView (NSOpenGLSurfaceResolutionLionAPI)
118 - (void)setWantsBestResolutionOpenGLSurface:(BOOL)flag;
121 static NSString* const NSWindowDidChangeBackingPropertiesNotification =
122 @"NSWindowDidChangeBackingPropertiesNotification";
123 static NSString* const NSBackingPropertyOldScaleFactorKey =
124 @"NSBackingPropertyOldScaleFactorKey";
125 // Note: Apple's example code (linked from the comment above
126 // -windowDidChangeBackingProperties:) uses
127 // @"NSWindowBackingPropertiesChangeOldBackingScaleFactorKey", but that always
128 // returns an old scale of 0. @"NSBackingPropertyOldScaleFactorKey" seems to
129 // work in practice, and it's what's used in Apple's WebKit port
130 // (WebKit/mac/WebView/WebView.mm).
134 static inline int ToWebKitModifiers(NSUInteger flags) {
136 if (flags & NSControlKeyMask) modifiers |= WebInputEvent::ControlKey;
137 if (flags & NSShiftKeyMask) modifiers |= WebInputEvent::ShiftKey;
138 if (flags & NSAlternateKeyMask) modifiers |= WebInputEvent::AltKey;
139 if (flags & NSCommandKeyMask) modifiers |= WebInputEvent::MetaKey;
143 // This method will return YES for OS X versions 10.7.3 and later, and NO
145 // Used to prevent a crash when building with the 10.7 SDK and accessing the
146 // notification below. See: http://crbug.com/260595.
147 static BOOL SupportsBackingPropertiesChangedNotification() {
148 // windowDidChangeBackingProperties: method has been added to the
149 // NSWindowDelegate protocol in 10.7.3, at the same time as the
150 // NSWindowDidChangeBackingPropertiesNotification notification was added.
151 // If the protocol contains this method description, the notification should
152 // be supported as well.
153 Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
154 struct objc_method_description methodDescription =
155 protocol_getMethodDescription(
156 windowDelegateProtocol,
157 @selector(windowDidChangeBackingProperties:),
161 // If the protocol does not contain the method, the returned method
162 // description is {NULL, NULL}
163 return methodDescription.name != NULL || methodDescription.types != NULL;
166 static float ScaleFactor(NSView* view) {
167 return ui::GetImageScale(ui::GetScaleFactorForNativeView(view));
171 @interface RenderWidgetHostViewCocoa ()
172 @property(nonatomic, assign) NSRange selectedRange;
173 @property(nonatomic, assign) NSRange markedRange;
174 @property(nonatomic, assign)
175 NSObject<RenderWidgetHostViewMacDelegate>* delegate;
177 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
178 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
179 - (void)gotUnhandledWheelEvent;
180 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right;
181 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar;
182 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
183 - (void)windowDidChangeBackingProperties:(NSNotification*)notification;
184 - (void)windowChangedGlobalFrame:(NSNotification*)notification;
185 - (void)drawBackingStore:(BackingStoreMac*)backingStore
186 dirtyRect:(CGRect)dirtyRect
187 inContext:(CGContextRef)context;
188 - (void)updateSoftwareLayerScaleFactor;
189 - (void)checkForPluginImeCancellation;
190 - (void)updateTabBackingStoreScaleFactor;
193 // A window subclass that allows the fullscreen window to become main and gain
194 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
195 // handled by the browser.
196 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
199 @implementation PepperFlashFullscreenWindow
201 - (BOOL)canBecomeKeyWindow {
205 - (BOOL)canBecomeMainWindow {
211 @interface RenderWidgetPopupWindow : NSWindow {
212 // The event tap that allows monitoring of all events, to properly close with
213 // a click outside the bounds of the window.
218 @implementation RenderWidgetPopupWindow
220 - (id)initWithContentRect:(NSRect)contentRect
221 styleMask:(NSUInteger)windowStyle
222 backing:(NSBackingStoreType)bufferingType
223 defer:(BOOL)deferCreation {
224 if (self = [super initWithContentRect:contentRect
225 styleMask:windowStyle
226 backing:bufferingType
227 defer:deferCreation]) {
228 CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
230 [self setBackgroundColor:[NSColor clearColor]];
231 [self startObservingClicks];
237 [self stopObservingClicks];
241 // Gets called when the menubar is clicked.
242 // Needed because the local event monitor doesn't see the click on the menubar.
243 - (void)beganTracking:(NSNotification*)notification {
247 // Install the callback.
248 - (void)startObservingClicks {
249 clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
250 handler:^NSEvent* (NSEvent* event) {
251 if ([event window] == self)
253 NSEventType eventType = [event type];
254 if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
259 NSNotificationCenter* notificationCenter =
260 [NSNotificationCenter defaultCenter];
261 [notificationCenter addObserver:self
262 selector:@selector(beganTracking:)
263 name:NSMenuDidBeginTrackingNotification
264 object:[NSApp mainMenu]];
267 // Remove the callback.
268 - (void)stopObservingClicks {
272 [NSEvent removeMonitor:clickEventTap_];
273 clickEventTap_ = nil;
275 NSNotificationCenter* notificationCenter =
276 [NSNotificationCenter defaultCenter];
277 [notificationCenter removeObserver:self
278 name:NSMenuDidBeginTrackingNotification
279 object:[NSApp mainMenu]];
286 // Maximum number of characters we allow in a tooltip.
287 const size_t kMaxTooltipLength = 1024;
289 // TODO(suzhe): Upstream this function.
290 WebKit::WebColor WebColorFromNSColor(NSColor *color) {
292 [color getRed:&r green:&g blue:&b alpha:&a];
295 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
296 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
297 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
298 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
301 // Extract underline information from an attributed string. Mostly copied from
302 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
303 void ExtractUnderlines(
304 NSAttributedString* string,
305 std::vector<WebKit::WebCompositionUnderline>* underlines) {
306 int length = [[string string] length];
310 NSDictionary* attrs = [string attributesAtIndex:i
311 longestEffectiveRange:&range
312 inRange:NSMakeRange(i, length - i)];
313 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
314 WebKit::WebColor color = SK_ColorBLACK;
315 if (NSColor *colorAttr =
316 [attrs objectForKey:NSUnderlineColorAttributeName]) {
317 color = WebColorFromNSColor(
318 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
320 underlines->push_back(WebKit::WebCompositionUnderline(
321 range.location, NSMaxRange(range), color, [style intValue] > 1));
323 i = range.location + range.length;
327 // EnablePasswordInput() and DisablePasswordInput() are copied from
328 // enableSecureTextInput() and disableSecureTextInput() functions in
329 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
330 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
331 // here, because they are already called in webkit and they are system wide
333 void EnablePasswordInput() {
334 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
335 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
336 sizeof(CFArrayRef), &inputSources);
337 CFRelease(inputSources);
340 void DisablePasswordInput() {
341 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
344 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
345 // left of the primary screen (Carbon coordinates), and stuffs it into a
347 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
348 gfx::Rect new_rect(NSRectToCGRect(rect));
349 if ([[NSScreen screens] count] > 0) {
350 new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height -
351 new_rect.y() - new_rect.height());
356 // Returns the window that visually contains the given view. This is different
357 // from [view window] in the case of tab dragging, where the view's owning
358 // window is a floating panel attached to the actual browser window that the tab
359 // is visually part of.
360 NSWindow* ApparentWindowForView(NSView* view) {
361 // TODO(shess): In case of !window, the view has been removed from
362 // the view hierarchy because the tab isn't main. Could retrieve
363 // the information from the main tab for our window.
364 NSWindow* enclosing_window = [view window];
366 // See if this is a tab drag window. The width check is to distinguish that
367 // case from extension popup windows.
368 NSWindow* ancestor_window = [enclosing_window parentWindow];
369 if (ancestor_window && (NSWidth([enclosing_window frame]) ==
370 NSWidth([ancestor_window frame]))) {
371 enclosing_window = ancestor_window;
374 return enclosing_window;
377 WebKit::WebScreenInfo GetWebScreenInfo(NSView* view) {
378 gfx::Display display =
379 gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
381 NSScreen* screen = [NSScreen deepestScreen];
383 WebKit::WebScreenInfo results;
385 results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
386 results.depth = NSBitsPerPixelFromDepth([screen depth]);
387 results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
388 results.isMonochrome =
389 [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
390 results.rect = display.bounds();
391 results.availableRect = display.work_area();
399 ///////////////////////////////////////////////////////////////////////////////
400 // RenderWidgetHostView, public:
403 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
404 RenderWidgetHost* widget) {
405 return new RenderWidgetHostViewMac(widget);
409 void RenderWidgetHostViewPort::GetDefaultScreenInfo(
410 WebKit::WebScreenInfo* results) {
411 *results = GetWebScreenInfo(NULL);
414 ///////////////////////////////////////////////////////////////////////////////
415 // RenderWidgetHostViewMac, public:
417 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
418 : render_widget_host_(RenderWidgetHostImpl::From(widget)),
419 about_to_validate_and_paint_(false),
420 call_set_needs_display_in_rect_pending_(false),
421 last_frame_was_accelerated_(false),
422 text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
423 can_compose_inline_(true),
424 allow_overlapping_views_(false),
425 use_core_animation_(false),
428 fullscreen_parent_host_view_(NULL),
429 pending_swap_buffers_acks_weak_factory_(this),
430 next_swap_ack_time_(base::Time::Now()),
431 software_frame_weak_ptr_factory_(this) {
432 software_frame_manager_.reset(new SoftwareFrameManager(
433 software_frame_weak_ptr_factory_.GetWeakPtr()));
434 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
435 // goes away. Since we autorelease it, our caller must put
436 // |GetNativeView()| into the view hierarchy right after calling us.
437 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
438 initWithRenderWidgetHostViewMac:this] autorelease];
440 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_ALWAYS) {
441 EnableCoreAnimation();
444 render_widget_host_->SetView(this);
447 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
448 // This is being called from |cocoa_view_|'s destructor, so invalidate the
452 AckPendingSwapBuffers();
455 // Make sure that the layer doesn't reach into the now-invalid object.
456 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
457 software_layer_.reset();
459 // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
460 // RenderWidgetHost does we need to tell it not to hold a stale pointer to
462 if (render_widget_host_)
463 render_widget_host_->SetView(NULL);
466 void RenderWidgetHostViewMac::SetDelegate(
467 NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
468 [cocoa_view_ setDelegate:delegate];
471 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
472 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY) {
474 ScopedCAActionDisabler disabler;
475 EnableCoreAnimation();
480 if (allow_overlapping_views_ == overlapping)
482 allow_overlapping_views_ = overlapping;
483 [cocoa_view_ setNeedsDisplay:YES];
484 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
487 ///////////////////////////////////////////////////////////////////////////////
488 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
490 void RenderWidgetHostViewMac::EnableCoreAnimation() {
491 if (use_core_animation_)
494 use_core_animation_ = true;
496 // Un-bind the GL context from this view because the CoreAnimation path will
497 // not use explicit setView and clearDrawable calls.
498 ClearBoundContextDrawable();
500 software_layer_.reset([[CALayer alloc] init]);
501 if (!software_layer_)
502 LOG(ERROR) << "Failed to create CALayer for software rendering";
503 [software_layer_ setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
504 [software_layer_ setDelegate:cocoa_view_];
505 [software_layer_ setContentsGravity:kCAGravityTopLeft];
506 [software_layer_ setFrame:NSRectToCGRect([cocoa_view_ bounds])];
507 [software_layer_ setNeedsDisplay];
508 [cocoa_view_ updateSoftwareLayerScaleFactor];
510 [cocoa_view_ setLayer:software_layer_];
511 [cocoa_view_ setWantsLayer:YES];
513 if (compositing_iosurface_) {
514 if (!CreateCompositedIOSurfaceLayer()) {
515 LOG(ERROR) << "Failed to create CALayer for existing IOSurface";
516 GotAcceleratedCompositingError();
522 bool RenderWidgetHostViewMac::CreateCompositedIOSurface() {
523 if (compositing_iosurface_context_ && compositing_iosurface_)
526 ScopedCAActionDisabler disabler;
528 // Create the GL context and shaders.
529 if (!compositing_iosurface_context_) {
530 compositing_iosurface_context_ =
531 CompositingIOSurfaceContext::Get(window_number());
532 if (!compositing_iosurface_context_) {
533 LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
537 // Create the IOSurface texture.
538 if (!compositing_iosurface_) {
539 compositing_iosurface_.reset(CompositingIOSurfaceMac::Create(
540 compositing_iosurface_context_));
541 if (!compositing_iosurface_) {
542 LOG(ERROR) << "Failed to create CompositingIOSurface";
546 // Make sure that the IOSurface is updated to use the context that is owned
548 compositing_iosurface_->SetContext(compositing_iosurface_context_);
553 bool RenderWidgetHostViewMac::CreateCompositedIOSurfaceLayer() {
554 CHECK(compositing_iosurface_context_ && compositing_iosurface_);
555 if (compositing_iosurface_layer_ || !use_core_animation_)
558 ScopedCAActionDisabler disabler;
560 // Create the GL CoreAnimation layer.
561 if (!compositing_iosurface_layer_) {
562 compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
563 initWithRenderWidgetHostViewMac:this]);
564 if (!compositing_iosurface_layer_) {
565 LOG(ERROR) << "Failed to create CALayer for IOSurface";
568 [software_layer_ addSublayer:compositing_iosurface_layer_];
571 // Creating the CompositingIOSurfaceLayer may attempt to draw in setLayer,
572 // which, if it fails, will promptly tear down everything that was just
573 // created. If that happened, return failure.
574 return compositing_iosurface_context_ &&
575 compositing_iosurface_ &&
576 (compositing_iosurface_layer_ || !use_core_animation_);
579 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer(
580 DestroyContextBehavior destroy_context_behavior) {
581 ScopedCAActionDisabler disabler;
583 compositing_iosurface_.reset();
584 if (compositing_iosurface_layer_) {
585 [software_layer_ setNeedsDisplay];
586 [compositing_iosurface_layer_ removeFromSuperlayer];
587 [compositing_iosurface_layer_ disableCompositing];
588 compositing_iosurface_layer_.reset();
590 switch (destroy_context_behavior) {
591 case kLeaveContextBoundToView:
593 case kDestroyContext:
594 ClearBoundContextDrawable();
595 compositing_iosurface_context_ = NULL;
603 void RenderWidgetHostViewMac::ClearBoundContextDrawable() {
604 if (compositing_iosurface_context_ &&
606 [[compositing_iosurface_context_->nsgl_context() view]
607 isEqual:cocoa_view_]) {
608 [compositing_iosurface_context_->nsgl_context() clearDrawable];
612 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
614 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
615 IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
616 IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
617 IPC_MESSAGE_UNHANDLED(handled = false)
618 IPC_END_MESSAGE_MAP()
622 void RenderWidgetHostViewMac::InitAsChild(
623 gfx::NativeView parent_view) {
626 void RenderWidgetHostViewMac::InitAsPopup(
627 RenderWidgetHostView* parent_host_view,
628 const gfx::Rect& pos) {
629 bool activatable = popup_type_ == WebKit::WebPopupTypeNone;
630 [cocoa_view_ setCloseOnDeactivate:YES];
631 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
633 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
634 if ([[NSScreen screens] count] > 0) {
635 origin_global.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height -
636 pos.height() - origin_global.y;
639 popup_window_.reset([[RenderWidgetPopupWindow alloc]
640 initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
641 pos.width(), pos.height())
642 styleMask:NSBorderlessWindowMask
643 backing:NSBackingStoreBuffered
645 [popup_window_ setLevel:NSPopUpMenuWindowLevel];
646 [popup_window_ setReleasedWhenClosed:NO];
647 [popup_window_ makeKeyAndOrderFront:nil];
648 [[popup_window_ contentView] addSubview:cocoa_view_];
649 [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
650 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
651 [[NSNotificationCenter defaultCenter]
652 addObserver:cocoa_view_
653 selector:@selector(popupWindowWillClose:)
654 name:NSWindowWillCloseNotification
655 object:popup_window_];
658 // This function creates the fullscreen window and hides the dock and menubar if
659 // necessary. Note, this codepath is only used for pepper flash when
660 // pp::FlashFullScreen::SetFullscreen() is called. If
661 // pp::FullScreen::SetFullscreen() is called then the entire browser window
662 // will enter fullscreen instead.
663 void RenderWidgetHostViewMac::InitAsFullscreen(
664 RenderWidgetHostView* reference_host_view) {
665 fullscreen_parent_host_view_ =
666 static_cast<RenderWidgetHostViewMac*>(reference_host_view);
667 NSWindow* parent_window = nil;
668 if (reference_host_view)
669 parent_window = [reference_host_view->GetNativeView() window];
670 NSScreen* screen = [parent_window screen];
672 screen = [NSScreen mainScreen];
674 pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
675 initWithContentRect:[screen frame]
676 styleMask:NSBorderlessWindowMask
677 backing:NSBackingStoreBuffered
679 [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
680 [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
681 [cocoa_view_ setCanBeKeyView:YES];
682 [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
683 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
684 // If the pepper fullscreen window isn't opaque then there are performance
685 // issues when it's on the discrete GPU and the Chrome window is being drawn
686 // to. http://crbug.com/171911
687 [pepper_fullscreen_window_ setOpaque:YES];
689 // Note that this forms a reference cycle between the fullscreen window and
690 // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
691 // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
692 // This cycle is normally broken when -keyEvent: receives an <esc> key, which
693 // explicitly calls Shutdown on the render_widget_host_, which calls
694 // Destroy() on RWHVMac, which drops the reference to
695 // pepper_fullscreen_window_.
696 [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
698 // Note that this keeps another reference to pepper_fullscreen_window_.
699 fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
700 initWithWindow:pepper_fullscreen_window_.get()
701 desiredScreen:screen]);
702 [fullscreen_window_manager_ enterFullscreenMode];
703 [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
706 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
707 // See comment in InitAsFullscreen(): There is a reference cycle between
708 // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
709 // Tests that test pepper fullscreen mode without sending an <esc> event
710 // need to call this method to break the reference cycle.
711 [fullscreen_window_manager_ exitFullscreenMode];
712 fullscreen_window_manager_.reset();
713 [pepper_fullscreen_window_ close];
714 pepper_fullscreen_window_.reset();
717 int RenderWidgetHostViewMac::window_number() const {
718 NSWindow* window = [cocoa_view_ window];
721 return [window windowNumber];
724 float RenderWidgetHostViewMac::scale_factor() const {
725 return ScaleFactor(cocoa_view_);
728 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
729 return render_widget_host_;
732 void RenderWidgetHostViewMac::WasShown() {
733 if (!render_widget_host_->is_hidden())
736 if (web_contents_switch_paint_time_.is_null())
737 web_contents_switch_paint_time_ = base::TimeTicks::Now();
738 render_widget_host_->WasShown();
739 software_frame_manager_->SetVisibility(true);
741 // We're messing with the window, so do this to ensure no flashes.
742 if (!use_core_animation_)
743 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
745 [compositing_iosurface_layer_ setNeedsDisplay];
748 void RenderWidgetHostViewMac::WasHidden() {
749 if (render_widget_host_->is_hidden())
752 // Send ACKs for any pending SwapBuffers (if any) since we won't be displaying
753 // them and the GPU process is waiting.
754 AckPendingSwapBuffers();
756 // If we have a renderer, then inform it that we are being hidden so it can
757 // reduce its resource utilization.
758 render_widget_host_->WasHidden();
759 software_frame_manager_->SetVisibility(false);
761 // There can be a transparent flash as this view is removed and the next is
762 // added, because of OSX windowing races between displaying the contents of
763 // the NSView and its corresponding OpenGL context.
764 // disableScreenUpdatesUntilFlush prevents the transparent flash by avoiding
765 // screen updates until the next tab draws.
766 if (!use_core_animation_)
767 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
769 web_contents_switch_paint_time_ = base::TimeTicks();
772 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
773 gfx::Rect rect = GetViewBounds();
778 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
779 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
780 // TODO(thakis): fix, http://crbug.com/73362
781 if (render_widget_host_->is_hidden())
784 // During the initial creation of the RenderWidgetHostView in
785 // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
786 // an empty size. In the Windows code flow, it is not ignored because
787 // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
788 // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
789 // flags to keep things sized properly. On the other hand, if the size is not
790 // empty then this is a valid request for a pop-up.
791 if (rect.size().IsEmpty())
794 // Ignore the position of |rect| for non-popup rwhvs. This is because
795 // background tabs do not have a window, but the window is required for the
796 // coordinate conversions. Popups are always for a visible tab.
798 // The position of |rect| is screen coordinate system and we have to
799 // consider Cocoa coordinate system is upside-down and also multi-screen.
800 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
801 NSSize size = NSMakeSize(rect.width(), rect.height());
802 size = [cocoa_view_ convertSize:size toView:nil];
803 if ([[NSScreen screens] count] > 0) {
805 static_cast<NSScreen*>([[NSScreen screens] objectAtIndex:0]);
807 NSHeight([screen frame]) - size.height - origin_global.y;
809 [popup_window_ setFrame:NSMakeRect(origin_global.x, origin_global.y,
810 size.width, size.height)
813 DCHECK([[cocoa_view_ superview] isKindOfClass:[BaseView class]]);
814 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
815 gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
816 rect2.set_width(rect.width());
817 rect2.set_height(rect.height());
818 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
822 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
826 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
827 return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
830 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
832 return static_cast<gfx::NativeViewAccessible>(NULL);
835 void RenderWidgetHostViewMac::MovePluginWindows(
836 const gfx::Vector2d& scroll_offset,
837 const std::vector<WebPluginGeometry>& moves) {
838 // Must be overridden, but unused on this platform. Core Animation
839 // plugins are drawn by the GPU process (through the compositor),
840 // and Core Graphics plugins are drawn by the renderer process.
841 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
844 void RenderWidgetHostViewMac::Focus() {
845 [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
848 void RenderWidgetHostViewMac::Blur() {
850 [[cocoa_view_ window] makeFirstResponder:nil];
853 bool RenderWidgetHostViewMac::HasFocus() const {
854 return [[cocoa_view_ window] firstResponder] == cocoa_view_;
857 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
858 return !!render_widget_host_->GetBackingStore(false) ||
859 (compositing_iosurface_ && compositing_iosurface_->HasIOSurface());
862 void RenderWidgetHostViewMac::Show() {
863 [cocoa_view_ setHidden:NO];
868 void RenderWidgetHostViewMac::Hide() {
869 // We're messing with the window, so do this to ensure no flashes.
870 if (!use_core_animation_)
871 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
873 [cocoa_view_ setHidden:YES];
878 bool RenderWidgetHostViewMac::IsShowing() {
879 return ![cocoa_view_ isHidden];
882 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
883 NSRect bounds = [cocoa_view_ bounds];
884 // TODO(shess): In case of !window, the view has been removed from
885 // the view hierarchy because the tab isn't main. Could retrieve
886 // the information from the main tab for our window.
887 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
888 if (!enclosing_window)
889 return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
891 bounds = [cocoa_view_ convertRect:bounds toView:nil];
892 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
893 return FlipNSRectToRectScreen(bounds);
896 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
897 WebCursor web_cursor = cursor;
898 [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
901 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
902 is_loading_ = is_loading;
903 // If we ever decide to show the waiting cursor while the page is loading
904 // like Chrome does on Windows, call |UpdateCursor()| here.
907 void RenderWidgetHostViewMac::TextInputTypeChanged(
908 ui::TextInputType type,
909 ui::TextInputMode input_mode,
910 bool can_compose_inline) {
911 if (text_input_type_ != type
912 || can_compose_inline_ != can_compose_inline) {
913 text_input_type_ = type;
914 can_compose_inline_ = can_compose_inline;
916 SetTextInputActive(true);
918 // Let AppKit cache the new input context to make IMEs happy.
919 // See http://crbug.com/73039.
920 [NSApp updateWindows];
923 UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
929 void RenderWidgetHostViewMac::ImeCancelComposition() {
930 [cocoa_view_ cancelComposition];
933 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
934 const gfx::Range& range,
935 const std::vector<gfx::Rect>& character_bounds) {
936 // The RangeChanged message is only sent with valid values. The current
937 // caret position (start == end) will be sent if there is no IME range.
938 [cocoa_view_ setMarkedRange:range.ToNSRange()];
939 composition_range_ = range;
940 composition_bounds_ = character_bounds;
943 void RenderWidgetHostViewMac::DidUpdateBackingStore(
944 const gfx::Rect& scroll_rect,
945 const gfx::Vector2d& scroll_delta,
946 const std::vector<gfx::Rect>& copy_rects,
947 const ui::LatencyInfo& latency_info) {
950 software_latency_info_.MergeWith(latency_info);
952 if (render_widget_host_->is_hidden())
955 std::vector<gfx::Rect> rects(copy_rects);
957 // Because the findbar might be open, we cannot use scrollRect:by: here. For
958 // now, simply mark all of scroll rect as dirty.
959 if (!scroll_rect.IsEmpty())
960 rects.push_back(scroll_rect);
962 for (size_t i = 0; i < rects.size(); ++i) {
963 NSRect ns_rect = [cocoa_view_ flipRectToNSRect:rects[i]];
965 if (about_to_validate_and_paint_) {
966 // As much as we'd like to use -setNeedsDisplayInRect: here, we can't.
967 // We're in the middle of executing a -drawRect:, and as soon as it
968 // returns Cocoa will clear its record of what needs display. We instead
969 // use |performSelector:| to call |setNeedsDisplayInRect:| after returning
970 // to the main loop, at which point |drawRect:| is no longer on the
972 DCHECK([NSThread isMainThread]);
973 if (!call_set_needs_display_in_rect_pending_) {
974 [cocoa_view_ performSelector:@selector(callSetNeedsDisplayInRect)
977 call_set_needs_display_in_rect_pending_ = true;
978 invalid_rect_ = ns_rect;
980 // The old invalid rect is probably invalid now, since the view has most
981 // likely been resized, but there's no harm in dirtying the union. In
982 // the limit, this becomes equivalent to dirtying the whole view.
983 invalid_rect_ = NSUnionRect(invalid_rect_, ns_rect);
986 [cocoa_view_ setNeedsDisplayInRect:ns_rect];
990 if (!about_to_validate_and_paint_)
991 [cocoa_view_ displayIfNeeded];
994 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
999 void RenderWidgetHostViewMac::Destroy() {
1000 AckPendingSwapBuffers();
1002 [[NSNotificationCenter defaultCenter]
1003 removeObserver:cocoa_view_
1004 name:NSWindowWillCloseNotification
1005 object:popup_window_];
1007 // We've been told to destroy.
1008 [cocoa_view_ retain];
1009 [cocoa_view_ removeFromSuperview];
1010 [cocoa_view_ autorelease];
1012 [popup_window_ close];
1013 popup_window_.autorelease();
1015 [fullscreen_window_manager_ exitFullscreenMode];
1016 fullscreen_window_manager_.reset();
1017 [pepper_fullscreen_window_ close];
1019 // This can be called as part of processing the window's responder
1020 // chain, for instance |-performKeyEquivalent:|. In that case the
1021 // object needs to survive until the stack unwinds.
1022 pepper_fullscreen_window_.autorelease();
1024 // We get this call just before |render_widget_host_| deletes
1025 // itself. But we are owned by |cocoa_view_|, which may be retained
1026 // by some other code. Examples are WebContentsViewMac's
1027 // |latent_focus_view_| and TabWindowController's
1028 // |cachedContentView_|.
1029 render_widget_host_ = NULL;
1032 // Called from the renderer to tell us what the tooltip text should be. It
1033 // calls us frequently so we need to cache the value to prevent doing a lot
1035 void RenderWidgetHostViewMac::SetTooltipText(const string16& tooltip_text) {
1036 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1037 tooltip_text_ = tooltip_text;
1039 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1040 // Windows; we're just trying to be polite. Don't persist the trimmed
1041 // string, as then the comparison above will always fail and we'll try to
1042 // set it again every single time the mouse moves.
1043 string16 display_text = tooltip_text_;
1044 if (tooltip_text_.length() > kMaxTooltipLength)
1045 display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1047 NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1048 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1052 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1053 return [NSApp respondsToSelector:@selector(speakString:)] &&
1054 [NSApp respondsToSelector:@selector(stopSpeaking:)];
1057 void RenderWidgetHostViewMac::SpeakSelection() {
1058 if ([NSApp respondsToSelector:@selector(speakString:)])
1059 [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
1062 bool RenderWidgetHostViewMac::IsSpeaking() const {
1063 return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1067 void RenderWidgetHostViewMac::StopSpeaking() {
1068 if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1069 [NSApp stopSpeaking:cocoa_view_];
1073 // RenderWidgetHostViewCocoa uses the stored selection text,
1074 // which implements NSServicesRequests protocol.
1076 void RenderWidgetHostViewMac::SelectionChanged(const string16& text,
1078 const gfx::Range& range) {
1079 if (range.is_empty() || text.empty()) {
1080 selected_text_.clear();
1082 size_t pos = range.GetMin() - offset;
1083 size_t n = range.length();
1085 DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1086 if (pos >= text.length()) {
1087 DCHECK(false) << "The text can not cover range.";
1090 selected_text_ = UTF16ToUTF8(text.substr(pos, n));
1093 [cocoa_view_ setSelectedRange:range.ToNSRange()];
1094 // Updates markedRange when there is no marked text so that retrieving
1095 // markedRange immediately after calling setMarkdText: returns the current
1097 if (![cocoa_view_ hasMarkedText]) {
1098 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1101 RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1104 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1105 const ViewHostMsg_SelectionBounds_Params& params) {
1106 if (params.anchor_rect == params.focus_rect)
1107 caret_rect_ = params.anchor_rect;
1110 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1113 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1114 RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1116 // Create a fake mouse event to inform the render widget that the mouse
1118 NSWindow* window = [cocoa_view_ window];
1119 // TODO(asvitkine): If the location outside of the event stream doesn't
1120 // correspond to the current event (due to delayed event processing), then
1121 // this may result in a cursor flicker if there are later mouse move events
1122 // in the pipeline. Find a way to use the mouse location from the event that
1123 // dismissed the context menu.
1124 NSPoint location = [window mouseLocationOutsideOfEventStream];
1125 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1129 windowNumber:window_number()
1134 WebMouseEvent web_event =
1135 WebInputEventFactory::mouseEvent(event, cocoa_view_);
1137 web_event.type = WebInputEvent::MouseLeave;
1138 ForwardMouseEvent(web_event);
1141 bool RenderWidgetHostViewMac::IsPopup() const {
1142 return popup_type_ != WebKit::WebPopupTypeNone;
1145 BackingStore* RenderWidgetHostViewMac::AllocBackingStore(
1146 const gfx::Size& size) {
1147 float scale = ScaleFactor(cocoa_view_);
1148 return new BackingStoreMac(render_widget_host_, size, scale);
1151 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1152 const gfx::Rect& src_subrect,
1153 const gfx::Size& dst_size,
1154 const base::Callback<void(bool, const SkBitmap&)>& callback) {
1155 base::ScopedClosureRunner scoped_callback_runner(
1156 base::Bind(callback, false, SkBitmap()));
1157 if (!compositing_iosurface_ ||
1158 !compositing_iosurface_->HasIOSurface())
1161 float scale = ScaleFactor(cocoa_view_);
1162 gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1163 gfx::ScaleSize(dst_size, scale));
1165 ignore_result(scoped_callback_runner.Release());
1167 compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1172 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1173 const gfx::Rect& src_subrect,
1174 const scoped_refptr<media::VideoFrame>& target,
1175 const base::Callback<void(bool)>& callback) {
1176 base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1177 if (!render_widget_host_->is_accelerated_compositing_active() ||
1178 !compositing_iosurface_ ||
1179 !compositing_iosurface_->HasIOSurface())
1182 if (!target.get()) {
1187 if (target->format() != media::VideoFrame::YV12 &&
1188 target->format() != media::VideoFrame::I420) {
1193 if (src_subrect.IsEmpty())
1196 ignore_result(scoped_callback_runner.Release());
1197 compositing_iosurface_->CopyToVideoFrame(
1198 GetScaledOpenGLPixelRect(src_subrect),
1203 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1204 return (!render_widget_host_->GetBackingStore(false) &&
1205 render_widget_host_->is_accelerated_compositing_active() &&
1206 compositing_iosurface_ &&
1207 compositing_iosurface_->HasIOSurface());
1210 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1214 void RenderWidgetHostViewMac::BeginFrameSubscription(
1215 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1216 frame_subscriber_ = subscriber.Pass();
1219 void RenderWidgetHostViewMac::EndFrameSubscription() {
1220 frame_subscriber_.reset();
1223 // Sets whether or not to accept first responder status.
1224 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1225 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1228 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1229 if (render_widget_host_)
1230 render_widget_host_->ForwardMouseEvent(event);
1232 if (event.type == WebInputEvent::MouseLeave) {
1233 [cocoa_view_ setToolTipAtMousePoint:nil];
1234 tooltip_text_.clear();
1238 void RenderWidgetHostViewMac::KillSelf() {
1239 if (!weak_factory_.HasWeakPtrs()) {
1240 [cocoa_view_ setHidden:YES];
1241 base::MessageLoop::current()->PostTask(FROM_HERE,
1242 base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1243 weak_factory_.GetWeakPtr()));
1247 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1248 const NativeWebKeyboardEvent& event) {
1249 // Check WebInputEvent type since multiple types of events can be sent into
1250 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1251 // necessary to avoid double processing.
1252 // Also check the native type, since NSFlagsChanged is considered a key event
1253 // for WebKit purposes, but isn't considered a key event by the OS.
1254 if (event.type == WebInputEvent::RawKeyDown &&
1255 [event.os_event type] == NSKeyDown)
1256 return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1260 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1261 const string16& text, int plugin_id) {
1262 if (render_widget_host_) {
1263 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1264 render_widget_host_->GetRoutingID(), text, plugin_id));
1268 void RenderWidgetHostViewMac::CompositorSwapBuffers(
1269 uint64 surface_handle,
1270 const gfx::Size& size,
1271 float surface_scale_factor,
1272 const ui::LatencyInfo& latency_info) {
1273 if (render_widget_host_->is_hidden())
1276 NSWindow* window = [cocoa_view_ window];
1277 if (window_number() <= 0) {
1278 // There is no window to present so capturing during present won't work.
1279 // We check if frame subscriber wants this frame and capture manually.
1280 if (compositing_iosurface_ && frame_subscriber_) {
1281 const base::Time present_time = base::Time::Now();
1282 scoped_refptr<media::VideoFrame> frame;
1283 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1284 if (frame_subscriber_->ShouldCaptureFrame(present_time,
1285 &frame, &callback)) {
1286 compositing_iosurface_->SetIOSurface(
1287 surface_handle, size, surface_scale_factor, latency_info);
1288 compositing_iosurface_->CopyToVideoFrame(
1289 gfx::Rect(size), frame,
1290 base::Bind(callback, present_time));
1295 // TODO(shess) If the view does not have a window, or the window
1296 // does not have backing, the IOSurface will log "invalid drawable"
1297 // in -setView:. It is not clear how this code is reached with such
1298 // a case, so record some info into breakpad (some subset of
1299 // browsers are likely to crash later for unrelated reasons).
1300 // http://crbug.com/148882
1301 const char* const kCrashKey = "rwhvm_window";
1303 base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1306 base::StringPrintf("window %s delegate %s controller %s",
1307 object_getClassName(window),
1308 object_getClassName([window delegate]),
1309 object_getClassName([window windowController]));
1310 base::debug::SetCrashKeyValue(kCrashKey, value);
1316 if (!CreateCompositedIOSurface()) {
1317 LOG(ERROR) << "Failed to create CompositingIOSurface";
1318 GotAcceleratedCompositingError();
1322 if (!compositing_iosurface_->SetIOSurface(
1323 surface_handle, size, surface_scale_factor, latency_info)) {
1324 LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1325 GotAcceleratedCompositingError();
1329 // Create the layer for the composited content only after the IOSurface has
1330 // been initialized.
1331 if (!CreateCompositedIOSurfaceLayer()) {
1332 LOG(ERROR) << "Failed to create CompositingIOSurface layer";
1333 GotAcceleratedCompositingError();
1337 GotAcceleratedFrame();
1339 gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1340 if (window_size.IsEmpty()) {
1341 // setNeedsDisplay will never display and we'll never ack if the window is
1342 // empty, so ack now and don't bother calling setNeedsDisplay below.
1346 // No need to draw the surface if we are inside a drawRect. It will be done
1348 if (!about_to_validate_and_paint_) {
1349 if (use_core_animation_) {
1350 DCHECK(compositing_iosurface_layer_);
1351 [compositing_iosurface_layer_ setNeedsDisplay];
1353 if (!DrawIOSurfaceWithoutCoreAnimation()) {
1354 [cocoa_view_ setNeedsDisplay:YES];
1355 GotAcceleratedCompositingError();
1362 void RenderWidgetHostViewMac::AckPendingSwapBuffers() {
1363 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::AckPendingSwapBuffers");
1365 // Cancel any outstanding delayed calls to this function.
1366 pending_swap_buffers_acks_weak_factory_.InvalidateWeakPtrs();
1368 while (!pending_swap_buffers_acks_.empty()) {
1369 if (pending_swap_buffers_acks_.front().first != 0) {
1370 AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
1371 ack_params.sync_point = 0;
1372 if (compositing_iosurface_)
1373 ack_params.renderer_id = compositing_iosurface_->GetRendererID();
1374 RenderWidgetHostImpl::AcknowledgeBufferPresent(
1375 pending_swap_buffers_acks_.front().first,
1376 pending_swap_buffers_acks_.front().second,
1378 if (render_widget_host_) {
1379 render_widget_host_->AcknowledgeSwapBuffersToRenderer();
1382 pending_swap_buffers_acks_.erase(pending_swap_buffers_acks_.begin());
1386 void RenderWidgetHostViewMac::ThrottledAckPendingSwapBuffers() {
1387 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1389 // Send VSync parameters to the renderer's compositor thread.
1390 base::TimeTicks vsync_timebase;
1391 base::TimeDelta vsync_interval;
1392 GetVSyncParameters(&vsync_timebase, &vsync_interval);
1393 if (render_widget_host_ && compositing_iosurface_)
1394 render_widget_host_->UpdateVSyncParameters(vsync_timebase, vsync_interval);
1396 // If the render widget host is responsible for throttling swaps to vsync rate
1397 // then don't ack the swapbuffers until a full vsync has passed since the last
1399 bool throttle_swap_ack =
1400 render_widget_host_ &&
1401 !render_widget_host_->is_threaded_compositing_enabled() &&
1402 compositing_iosurface_ &&
1403 !compositing_iosurface_->is_vsync_disabled();
1404 base::Time now = base::Time::Now();
1405 if (throttle_swap_ack && next_swap_ack_time_ > now) {
1406 base::TimeDelta next_swap_ack_delay = next_swap_ack_time_ - now;
1407 next_swap_ack_time_ += vsync_interval;
1408 base::MessageLoop::current()->PostDelayedTask(
1410 base::Bind(&RenderWidgetHostViewMac::AckPendingSwapBuffers,
1411 pending_swap_buffers_acks_weak_factory_.GetWeakPtr()),
1412 next_swap_ack_delay);
1414 next_swap_ack_time_ = now + vsync_interval;
1415 AckPendingSwapBuffers();
1419 bool RenderWidgetHostViewMac::DrawIOSurfaceWithoutCoreAnimation() {
1420 CHECK(!use_core_animation_);
1421 CHECK(compositing_iosurface_);
1422 CHECK(compositing_iosurface_context_ == compositing_iosurface_->context());
1424 GLint old_gl_surface_order = 0;
1425 GLint new_gl_surface_order = allow_overlapping_views_ ? -1 : 1;
1426 [compositing_iosurface_context_->nsgl_context()
1427 getValues:&old_gl_surface_order
1428 forParameter:NSOpenGLCPSurfaceOrder];
1429 if (old_gl_surface_order != new_gl_surface_order) {
1430 [compositing_iosurface_context_->nsgl_context()
1431 setValues:&new_gl_surface_order
1432 forParameter:NSOpenGLCPSurfaceOrder];
1435 CGLError cgl_error = CGLSetCurrentContext(
1436 compositing_iosurface_context_->cgl_context());
1437 if (cgl_error != kCGLNoError) {
1438 LOG(ERROR) << "CGLSetCurrentContext error in DrawIOSurface: " << cgl_error;
1442 [compositing_iosurface_context_->nsgl_context() setView:cocoa_view_];
1443 return compositing_iosurface_->DrawIOSurface(
1444 gfx::Size(NSSizeToCGSize([cocoa_view_ frame].size)),
1450 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1451 AckPendingSwapBuffers();
1452 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1453 // The existing GL contexts may be in a bad state, so don't re-use any of the
1454 // existing ones anymore, rather, allocate new ones.
1455 CompositingIOSurfaceContext::MarkExistingContextsAsNotShareable();
1456 // Request that a new frame be generated.
1457 if (render_widget_host_)
1458 render_widget_host_->ScheduleComposite();
1459 // TODO(ccameron): It may be a good idea to request that the renderer recreate
1460 // its GL context as well, and fall back to software if this happens
1464 void RenderWidgetHostViewMac::GetVSyncParameters(
1465 base::TimeTicks* timebase, base::TimeDelta* interval) {
1466 if (compositing_iosurface_) {
1467 uint32 numerator = 0;
1468 uint32 denominator = 0;
1469 compositing_iosurface_->GetVSyncParameters(
1470 timebase, &numerator, &denominator);
1471 if (numerator > 0 && denominator > 0) {
1472 int64 interval_micros =
1473 1000000 * static_cast<int64>(numerator) / denominator;
1474 *interval = base::TimeDelta::FromMicroseconds(interval_micros);
1479 // Pass reasonable default values if unable to get the actual ones
1480 // (e.g. CVDisplayLink failed to return them because the display is
1482 static const int64 kOneOverSixtyMicroseconds = 16669;
1483 *timebase = base::TimeTicks::Now(),
1484 *interval = base::TimeDelta::FromMicroseconds(kOneOverSixtyMicroseconds);
1487 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1488 const std::vector<gfx::Rect>& bounds,
1489 const gfx::Range& range,
1490 size_t* line_break_point) {
1491 DCHECK(line_break_point);
1492 if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1495 // We can't check line breaking completely from only rectangle array. Thus we
1496 // assume the line breaking as the next character's y offset is larger than
1497 // a threshold. Currently the threshold is determined as minimum y offset plus
1498 // 75% of maximum height.
1499 // TODO(nona): Check the threshold is reliable or not.
1500 // TODO(nona): Bidi support.
1501 const size_t loop_end_idx = std::min(bounds.size(), range.end());
1503 int min_y_offset = kint32max;
1504 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1505 max_height = std::max(max_height, bounds[idx].height());
1506 min_y_offset = std::min(min_y_offset, bounds[idx].y());
1508 int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1509 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1510 if (bounds[idx].y() > line_break_threshold) {
1511 *line_break_point = idx;
1518 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1519 const gfx::Range& range,
1520 gfx::Range* actual_range) {
1521 DCHECK(actual_range);
1522 DCHECK(!composition_bounds_.empty());
1523 DCHECK(range.start() <= composition_bounds_.size());
1524 DCHECK(range.end() <= composition_bounds_.size());
1526 if (range.is_empty()) {
1527 *actual_range = range;
1528 if (range.start() == composition_bounds_.size()) {
1529 return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1530 composition_bounds_[range.start() - 1].y(),
1532 composition_bounds_[range.start() - 1].height());
1534 return gfx::Rect(composition_bounds_[range.start()].x(),
1535 composition_bounds_[range.start()].y(),
1537 composition_bounds_[range.start()].height());
1542 if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1543 end_idx = range.end();
1545 *actual_range = gfx::Range(range.start(), end_idx);
1546 gfx::Rect rect = composition_bounds_[range.start()];
1547 for (size_t i = range.start() + 1; i < end_idx; ++i) {
1548 rect.Union(composition_bounds_[i]);
1553 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1554 const gfx::Range& request_range) {
1555 if (composition_range_.is_empty())
1556 return gfx::Range::InvalidRange();
1558 if (request_range.is_reversed())
1559 return gfx::Range::InvalidRange();
1561 if (request_range.start() < composition_range_.start() ||
1562 request_range.start() > composition_range_.end() ||
1563 request_range.end() > composition_range_.end()) {
1564 return gfx::Range::InvalidRange();
1568 request_range.start() - composition_range_.start(),
1569 request_range.end() - composition_range_.start());
1572 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1575 NSRange* actual_range) {
1577 // This exists to make IMEs more responsive, see http://crbug.com/115920
1578 TRACE_EVENT0("browser",
1579 "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1581 // If requested range is same as caret location, we can just return it.
1582 if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1584 *actual_range = range;
1585 *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1589 const gfx::Range request_range_in_composition =
1590 ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1591 if (request_range_in_composition == gfx::Range::InvalidRange())
1594 // If firstRectForCharacterRange in WebFrame is failed in renderer,
1595 // ImeCompositionRangeChanged will be sent with empty vector.
1596 if (composition_bounds_.empty())
1598 DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1600 gfx::Range ui_actual_range;
1601 *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1602 request_range_in_composition,
1603 &ui_actual_range).ToCGRect());
1605 *actual_range = gfx::Range(
1606 composition_range_.start() + ui_actual_range.start(),
1607 composition_range_.start() + ui_actual_range.end()).ToNSRange();
1612 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1613 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1615 TRACE_EVENT0("browser",
1616 "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1617 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1619 pending_swap_buffers_acks_.push_back(std::make_pair(params.route_id,
1622 CompositorSwapBuffers(params.surface_handle,
1624 params.scale_factor,
1625 params.latency_info);
1627 ThrottledAckPendingSwapBuffers();
1630 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1631 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1633 TRACE_EVENT0("browser",
1634 "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1635 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1637 pending_swap_buffers_acks_.push_back(std::make_pair(params.route_id,
1640 CompositorSwapBuffers(params.surface_handle,
1641 params.surface_size,
1642 params.surface_scale_factor,
1643 params.latency_info);
1645 ThrottledAckPendingSwapBuffers();
1648 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1649 if (compositing_iosurface_)
1650 compositing_iosurface_->UnrefIOSurface();
1653 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1654 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1657 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1658 const gfx::Size& desired_size) {
1659 if (last_frame_was_accelerated_) {
1660 return compositing_iosurface_ &&
1661 compositing_iosurface_->HasIOSurface() &&
1662 (desired_size.IsEmpty() ||
1663 compositing_iosurface_->dip_io_surface_size() == desired_size);
1665 return (software_frame_manager_->HasCurrentFrame() &&
1666 (desired_size.IsEmpty() ||
1667 software_frame_manager_->GetCurrentFrameSizeInDIP() ==
1673 void RenderWidgetHostViewMac::AboutToWaitForBackingStoreMsg() {
1674 AckPendingSwapBuffers();
1677 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1678 uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1679 // Only software compositor frames are accepted.
1680 if (!frame->software_frame_data) {
1681 DLOG(ERROR) << "Received unexpected frame type.";
1683 UserMetricsAction("BadMessageTerminate_UnexpectedFrameType"));
1684 render_widget_host_->GetProcess()->ReceivedBadMessage();
1689 if (!software_frame_manager_->SwapToNewFrame(
1691 frame->software_frame_data.get(),
1692 frame->metadata.device_scale_factor,
1693 render_widget_host_->GetProcess()->GetHandle())) {
1694 render_widget_host_->GetProcess()->ReceivedBadMessage();
1697 software_frame_manager_->SwapToNewFrameComplete(
1698 !render_widget_host_->is_hidden());
1700 [cocoa_view_ setNeedsDisplay:YES];
1703 void RenderWidgetHostViewMac::OnAcceleratedCompositingStateChange() {
1706 void RenderWidgetHostViewMac::GetScreenInfo(WebKit::WebScreenInfo* results) {
1707 *results = GetWebScreenInfo(GetNativeView());
1710 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1711 // TODO(shess): In case of !window, the view has been removed from
1712 // the view hierarchy because the tab isn't main. Could retrieve
1713 // the information from the main tab for our window.
1714 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1715 if (!enclosing_window)
1718 NSRect bounds = [enclosing_window frame];
1719 return FlipNSRectToRectScreen(bounds);
1722 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1723 // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1724 // completely on Mac OS.
1725 return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
1728 void RenderWidgetHostViewMac::SetHasHorizontalScrollbar(
1729 bool has_horizontal_scrollbar) {
1730 [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
1733 void RenderWidgetHostViewMac::SetScrollOffsetPinning(
1734 bool is_pinned_to_left, bool is_pinned_to_right) {
1735 [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
1736 toRight:is_pinned_to_right];
1739 bool RenderWidgetHostViewMac::LockMouse() {
1743 mouse_locked_ = true;
1745 // Lock position of mouse cursor and hide it.
1746 CGAssociateMouseAndMouseCursorPosition(NO);
1749 // Clear the tooltip window.
1750 SetTooltipText(string16());
1755 void RenderWidgetHostViewMac::UnlockMouse() {
1758 mouse_locked_ = false;
1760 // Unlock position of mouse cursor and unhide it.
1761 CGAssociateMouseAndMouseCursorPosition(YES);
1764 if (render_widget_host_)
1765 render_widget_host_->LostMouseLock();
1768 void RenderWidgetHostViewMac::UnhandledWheelEvent(
1769 const WebKit::WebMouseWheelEvent& event) {
1770 // Only record a wheel event as unhandled if JavaScript handlers got a chance
1771 // to see it (no-op wheel events are ignored by the event dispatcher)
1772 if (event.deltaX || event.deltaY)
1773 [cocoa_view_ gotUnhandledWheelEvent];
1776 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
1777 if (render_widget_host_)
1778 return render_widget_host_->Send(message);
1783 void RenderWidgetHostViewMac::SoftwareFrameWasFreed(
1784 uint32 output_surface_id, unsigned frame_id) {
1785 cc::CompositorFrameAck ack;
1786 ack.last_software_frame_id = frame_id;
1787 RenderWidgetHostImpl::SendReclaimCompositorResources(
1788 render_widget_host_->GetRoutingID(),
1790 render_widget_host_->GetProcess()->GetID(),
1794 void RenderWidgetHostViewMac::ReleaseReferencesToSoftwareFrame() {
1797 void RenderWidgetHostViewMac::ShutdownHost() {
1798 weak_factory_.InvalidateWeakPtrs();
1799 render_widget_host_->Shutdown();
1800 // Do not touch any members at this point, |this| has been deleted.
1803 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
1804 // Update the scale factor of the layer to match the scale factor of the
1806 [compositing_iosurface_layer_ updateScaleFactor];
1808 if (!last_frame_was_accelerated_) {
1809 last_frame_was_accelerated_ = true;
1811 if (!use_core_animation_) {
1812 // Need to wipe the software view with transparency to expose the GL
1813 // underlay. Invalidate the whole window to do that.
1814 [cocoa_view_ setNeedsDisplay:YES];
1817 // Delete software backingstore.
1818 BackingStoreManager::RemoveBackingStore(render_widget_host_);
1819 software_frame_manager_->DiscardCurrentFrame();
1823 void RenderWidgetHostViewMac::GotSoftwareFrame() {
1824 if (last_frame_was_accelerated_) {
1825 last_frame_was_accelerated_ = false;
1827 AckPendingSwapBuffers();
1829 // If overlapping views are allowed, then don't unbind the context
1830 // from the view (that is, don't call clearDrawble -- just delete the
1831 // texture and IOSurface). Rather, let it sit behind the software frame
1832 // that will be put up in front. This will prevent transparent
1834 // http://crbug.com/154531
1835 // Also note that it is necessary that clearDrawable be called if
1836 // overlapping views are not allowed, e.g, for content shell.
1837 // http://crbug.com/178408
1838 // Disable screen updates so that the changes of flashes is minimized.
1839 // http://crbug.com/279472
1840 if (!use_core_animation_)
1841 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1842 if (allow_overlapping_views_)
1843 DestroyCompositedIOSurfaceAndLayer(kLeaveContextBoundToView);
1845 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1849 void RenderWidgetHostViewMac::SetActive(bool active) {
1850 if (render_widget_host_) {
1851 render_widget_host_->SetActive(active);
1854 render_widget_host_->Focus();
1856 render_widget_host_->Blur();
1860 SetTextInputActive(active);
1862 [cocoa_view_ setPluginImeActive:NO];
1867 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
1868 if (render_widget_host_) {
1869 render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
1870 render_widget_host_->GetRoutingID(), visible));
1874 void RenderWidgetHostViewMac::WindowFrameChanged() {
1875 if (render_widget_host_) {
1876 render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
1877 render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
1881 if (compositing_iosurface_ && use_core_animation_) {
1882 scoped_refptr<CompositingIOSurfaceContext> new_context =
1883 CompositingIOSurfaceContext::Get(window_number());
1885 // Un-bind the GL context from this view before binding the new GL
1886 // context. Having two GL contexts bound to a view will result in
1887 // crashes and corruption.
1888 // http://crbug.com/230883
1889 ClearBoundContextDrawable();
1890 compositing_iosurface_context_ = new_context;
1891 compositing_iosurface_->SetContext(compositing_iosurface_context_);
1896 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
1897 RenderWidgetHostViewMacDictionaryHelper helper(this);
1898 helper.ShowDefinitionForSelection();
1901 void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) {
1902 RenderWidgetHostViewBase::SetBackground(background);
1903 if (render_widget_host_)
1904 render_widget_host_->Send(new ViewMsg_SetBackground(
1905 render_widget_host_->GetRoutingID(), background));
1908 void RenderWidgetHostViewMac::OnAccessibilityEvents(
1909 const std::vector<AccessibilityHostMsg_EventParams>& params) {
1910 if (!GetBrowserAccessibilityManager()) {
1911 SetBrowserAccessibilityManager(
1912 new BrowserAccessibilityManagerMac(
1914 BrowserAccessibilityManagerMac::GetEmptyDocument(),
1917 GetBrowserAccessibilityManager()->OnAccessibilityEvents(params);
1920 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
1922 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1923 EnablePasswordInput();
1925 DisablePasswordInput();
1927 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1928 DisablePasswordInput();
1932 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
1934 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
1937 void RenderWidgetHostViewMac::OnStartPluginIme() {
1938 [cocoa_view_ setPluginImeActive:YES];
1941 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
1942 const gfx::Rect& rect) {
1943 gfx::Rect src_gl_subrect = rect;
1944 src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
1946 return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
1950 void RenderWidgetHostViewMac::FrameSwapped() {
1951 software_latency_info_.AddLatencyNumber(
1952 ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
1953 render_widget_host_->FrameSwapped(software_latency_info_);
1954 software_latency_info_.Clear();
1957 } // namespace content
1959 // RenderWidgetHostViewCocoa ---------------------------------------------------
1961 @implementation RenderWidgetHostViewCocoa
1963 @synthesize selectedRange = selectedRange_;
1964 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
1965 @synthesize markedRange = markedRange_;
1966 @synthesize delegate = delegate_;
1968 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
1969 self = [super initWithFrame:NSZeroRect];
1971 editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
1972 editCommand_helper_->AddEditingSelectorsToClass([self class]);
1974 renderWidgetHostView_.reset(r);
1975 canBeKeyView_ = YES;
1976 focusedPluginIdentifier_ = -1;
1977 deviceScaleFactor_ = ScaleFactor(self);
1980 if ([self respondsToSelector:
1981 @selector(setWantsBestResolutionOpenGLSurface:)]) {
1982 [self setWantsBestResolutionOpenGLSurface:YES];
1984 handlingGlobalFrameDidChange_ = NO;
1985 [[NSNotificationCenter defaultCenter]
1987 selector:@selector(globalFrameDidChange:)
1988 name:NSViewGlobalFrameDidChangeNotification
1995 // Unbind the GL context from this view. If this is not done before super's
1996 // dealloc is called then the GL context will crash when it reaches into
1997 // the view in its destructor.
1998 // http://crbug.com/255608
1999 if (renderWidgetHostView_)
2000 renderWidgetHostView_->AcceleratedSurfaceRelease();
2002 if (delegate_ && [delegate_ respondsToSelector:@selector(viewGone:)])
2003 [delegate_ viewGone:self];
2004 [[NSNotificationCenter defaultCenter] removeObserver:self];
2009 - (void)resetCursorRects {
2010 if (currentCursor_) {
2011 [self addCursorRect:[self visibleRect] cursor:currentCursor_];
2012 [currentCursor_ setOnMouseEntered:YES];
2016 - (void)gotUnhandledWheelEvent {
2018 [delegate_ respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
2019 [delegate_ gotUnhandledWheelEvent];
2023 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
2024 if (delegate_ && [delegate_ respondsToSelector:
2025 @selector(scrollOffsetPinnedToLeft:toRight:)]) {
2026 [delegate_ scrollOffsetPinnedToLeft:left toRight:right];
2030 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
2032 [delegate_ respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
2033 [delegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
2037 - (BOOL)respondsToSelector:(SEL)selector {
2038 // Trickiness: this doesn't mean "does this object's superclass respond to
2039 // this selector" but rather "does the -respondsToSelector impl from the
2040 // superclass say that this class responds to the selector".
2041 if ([super respondsToSelector:selector])
2045 return [delegate_ respondsToSelector:selector];
2050 - (id)forwardingTargetForSelector:(SEL)selector {
2051 if ([delegate_ respondsToSelector:selector])
2054 return [super forwardingTargetForSelector:selector];
2057 - (void)setCanBeKeyView:(BOOL)can {
2058 canBeKeyView_ = can;
2061 - (BOOL)acceptsMouseEventsWhenInactive {
2062 // Some types of windows (balloons, always-on-top panels) want to accept mouse
2063 // clicks w/o the first click being treated as 'activation'. Same applies to
2064 // mouse move events.
2065 return [[self window] level] > NSNormalWindowLevel;
2068 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
2069 return [self acceptsMouseEventsWhenInactive];
2072 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
2073 takesFocusOnlyOnMouseDown_ = b;
2076 - (void)setCloseOnDeactivate:(BOOL)b {
2077 closeOnDeactivate_ = b;
2080 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
2081 NSWindow* window = [self window];
2082 // If this is a background window, don't handle mouse movement events. This
2083 // is the expected behavior on the Mac as evidenced by other applications.
2084 if ([theEvent type] == NSMouseMoved &&
2085 ![self acceptsMouseEventsWhenInactive] &&
2086 ![window isKeyWindow]) {
2090 // Use hitTest to check whether the mouse is over a nonWebContentView - in
2091 // which case the mouse event should not be handled by the render host.
2092 const SEL nonWebContentViewSelector = @selector(nonWebContentView);
2093 NSView* contentView = [window contentView];
2094 NSView* view = [contentView hitTest:[theEvent locationInWindow]];
2095 // Traverse the superview hierarchy as the hitTest will return the frontmost
2096 // view, such as an NSTextView, while nonWebContentView may be specified by
2099 if ([view respondsToSelector:nonWebContentViewSelector] &&
2100 [view performSelector:nonWebContentViewSelector]) {
2101 // The cursor is over a nonWebContentView - ignore this mouse event.
2104 if ([view isKindOfClass:[self class]] && ![view isEqual:self]) {
2105 // The cursor is over an overlapping render widget. This check is done by
2106 // both views so the one that's returned by -hitTest: will end up
2107 // processing the event.
2110 view = [view superview];
2115 - (void)mouseEvent:(NSEvent*)theEvent {
2116 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
2117 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2118 BOOL handled = [delegate_ handleEvent:theEvent];
2123 if ([self shouldIgnoreMouseEvent:theEvent]) {
2124 // If this is the first such event, send a mouse exit to the host view.
2125 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
2126 WebMouseEvent exitEvent =
2127 WebInputEventFactory::mouseEvent(theEvent, self);
2128 exitEvent.type = WebInputEvent::MouseLeave;
2129 exitEvent.button = WebMouseEvent::ButtonNone;
2130 renderWidgetHostView_->ForwardMouseEvent(exitEvent);
2132 mouseEventWasIgnored_ = YES;
2136 if (mouseEventWasIgnored_) {
2137 // If this is the first mouse event after a previous event that was ignored
2138 // due to the hitTest, send a mouse enter event to the host view.
2139 if (renderWidgetHostView_->render_widget_host_) {
2140 WebMouseEvent enterEvent =
2141 WebInputEventFactory::mouseEvent(theEvent, self);
2142 enterEvent.type = WebInputEvent::MouseMove;
2143 enterEvent.button = WebMouseEvent::ButtonNone;
2144 renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2147 mouseEventWasIgnored_ = NO;
2149 // TODO(rohitrao): Probably need to handle other mouse down events here.
2150 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2151 if (renderWidgetHostView_->render_widget_host_)
2152 renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2154 // Manually take focus after the click but before forwarding it to the
2156 [[self window] makeFirstResponder:self];
2159 // Don't cancel child popups; killing them on a mouse click would prevent the
2160 // user from positioning the insertion point in the text field spawning the
2161 // popup. A click outside the text field would cause the text field to drop
2162 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2163 // the popup anyway, so we're OK.
2165 NSEventType type = [theEvent type];
2166 if (type == NSLeftMouseDown)
2167 hasOpenMouseDown_ = YES;
2168 else if (type == NSLeftMouseUp)
2169 hasOpenMouseDown_ = NO;
2171 // TODO(suzhe): We should send mouse events to the input method first if it
2172 // wants to handle them. But it won't work without implementing method
2173 // - (NSUInteger)characterIndexForPoint:.
2174 // See: http://code.google.com/p/chromium/issues/detail?id=47141
2175 // Instead of sending mouse events to the input method first, we now just
2176 // simply confirm all ongoing composition here.
2177 if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2178 type == NSOtherMouseDown) {
2179 [self confirmComposition];
2182 const WebMouseEvent event =
2183 WebInputEventFactory::mouseEvent(theEvent, self);
2184 renderWidgetHostView_->ForwardMouseEvent(event);
2187 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2188 // |performKeyEquivalent:| is sent to all views of a window, not only down the
2189 // responder chain (cf. "Handling Key Equivalents" in
2190 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2191 // ). We only want to handle key equivalents if we're first responder.
2192 if ([[self window] firstResponder] != self)
2195 // If we return |NO| from this function, cocoa will send the key event to
2196 // the menu and only if the menu does not process the event to |keyDown:|. We
2197 // want to send the event to a renderer _before_ sending it to the menu, so
2198 // we need to return |YES| for all events that might be swallowed by the menu.
2199 // We do not return |YES| for every keypress because we don't get |keyDown:|
2200 // events for keys that we handle this way.
2201 NSUInteger modifierFlags = [theEvent modifierFlags];
2202 if ((modifierFlags & NSCommandKeyMask) == 0) {
2203 // Make sure the menu does not contain key equivalents that don't
2205 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2209 // Command key combinations are sent via performKeyEquivalent rather than
2210 // keyDown:. We just forward this on and if WebCore doesn't want to handle
2211 // it, we let the WebContentsView figure out how to reinject it.
2212 [self keyEvent:theEvent wasKeyEquivalent:YES];
2216 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2217 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2218 // returned NO. If this function returns |YES|, Cocoa sends the event to
2219 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2220 // to us instead of doing key view loop control, ctrl-left/right get handled
2222 // (However, there are still some keys that Cocoa swallows, e.g. the key
2223 // equivalent that Cocoa uses for toggling the input language. In this case,
2224 // that's actually a good thing, though -- see http://crbug.com/26115 .)
2228 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2229 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2230 BOOL handled = [delegate_ handleEvent:theEvent];
2232 return kEventHandled;
2235 [self keyEvent:theEvent wasKeyEquivalent:NO];
2236 return kEventHandled;
2239 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2240 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2241 DCHECK([theEvent type] != NSKeyDown ||
2242 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2244 if ([theEvent type] == NSFlagsChanged) {
2245 // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2246 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2247 int keyCode = [theEvent keyCode];
2248 if (!keyCode || keyCode == 10 || keyCode == 63)
2252 // Don't cancel child popups; the key events are probably what's triggering
2253 // the popup in the first place.
2255 RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2258 NativeWebKeyboardEvent event(theEvent);
2260 // Force fullscreen windows to close on Escape so they won't keep the keyboard
2261 // grabbed or be stuck onscreen if the renderer is hanging.
2262 if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2263 event.windowsKeyCode == ui::VKEY_ESCAPE &&
2264 renderWidgetHostView_->pepper_fullscreen_window()) {
2265 RenderWidgetHostViewMac* parent =
2266 renderWidgetHostView_->fullscreen_parent_host_view();
2268 parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2269 widgetHost->Shutdown();
2273 // Suppress the escape key up event if necessary.
2274 if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2275 if (event.type == NativeWebKeyboardEvent::KeyUp)
2276 suppressNextEscapeKeyUp_ = NO;
2280 // We only handle key down events and just simply forward other events.
2281 if ([theEvent type] != NSKeyDown) {
2282 widgetHost->ForwardKeyboardEvent(event);
2284 // Possibly autohide the cursor.
2285 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2286 [NSCursor setHiddenUntilMouseMoves:YES];
2291 base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2293 // Records the current marked text state, so that we can know if the marked
2294 // text was deleted or not after handling the key down event.
2295 BOOL oldHasMarkedText = hasMarkedText_;
2297 // This method should not be called recursively.
2298 DCHECK(!handlingKeyDown_);
2300 // Tells insertText: and doCommandBySelector: that we are handling a key
2302 handlingKeyDown_ = YES;
2304 // These variables might be set when handling the keyboard event.
2305 // Clear them here so that we can know whether they have changed afterwards.
2306 textToBeInserted_.clear();
2307 markedText_.clear();
2308 underlines_.clear();
2309 unmarkTextCalled_ = NO;
2310 hasEditCommands_ = NO;
2311 editCommands_.clear();
2313 // Before doing anything with a key down, check to see if plugin IME has been
2314 // cancelled, since the plugin host needs to be informed of that before
2315 // receiving the keydown.
2316 if ([theEvent type] == NSKeyDown)
2317 [self checkForPluginImeCancellation];
2319 // Sends key down events to input method first, then we can decide what should
2320 // be done according to input method's feedback.
2321 // If a plugin is active, bypass this step since events are forwarded directly
2322 // to the plugin IME.
2323 if (focusedPluginIdentifier_ == -1)
2324 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2326 handlingKeyDown_ = NO;
2328 // Indicates if we should send the key event and corresponding editor commands
2329 // after processing the input method result.
2330 BOOL delayEventUntilAfterImeCompostion = NO;
2332 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2333 // while an input method is composing or inserting a text.
2334 // Gmail checks this code in its onkeydown handler to stop auto-completing
2335 // e-mail addresses while composing a CJK text.
2336 // If the text to be inserted has only one character, then we don't need this
2337 // trick, because we'll send the text as a key press event instead.
2338 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2339 NativeWebKeyboardEvent fakeEvent = event;
2340 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
2341 fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2342 fakeEvent.skip_in_browser = true;
2343 widgetHost->ForwardKeyboardEvent(fakeEvent);
2344 // If this key event was handled by the input method, but
2345 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2346 // enqueued edit commands, then in order to let webkit handle them
2347 // correctly, we need to send the real key event and corresponding edit
2348 // commands after processing the input method result.
2349 // We shouldn't do this if a new marked text was set by the input method,
2350 // otherwise the new marked text might be cancelled by webkit.
2351 if (hasEditCommands_ && !hasMarkedText_)
2352 delayEventUntilAfterImeCompostion = YES;
2354 if (!editCommands_.empty()) {
2355 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2356 widgetHost->GetRoutingID(), editCommands_));
2358 widgetHost->ForwardKeyboardEvent(event);
2361 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2362 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2363 // be set to NULL. So we check it here and return immediately if it's NULL.
2364 if (!renderWidgetHostView_->render_widget_host_)
2367 // Then send keypress and/or composition related events.
2368 // If there was a marked text or the text to be inserted is longer than 1
2369 // character, then we send the text by calling ConfirmComposition().
2370 // Otherwise, if the text to be inserted only contains 1 character, then we
2371 // can just send a keypress event which is fabricated by changing the type of
2372 // the keydown event, so that we can retain all necessary informations, such
2373 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2374 // prevent the browser from handling it again.
2375 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2376 // handle BMP characters here, as we can always insert non-BMP characters as
2378 BOOL textInserted = NO;
2379 if (textToBeInserted_.length() >
2380 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2381 widgetHost->ImeConfirmComposition(
2382 textToBeInserted_, gfx::Range::InvalidRange(), false);
2386 // Updates or cancels the composition. If some text has been inserted, then
2387 // we don't need to cancel the composition explicitly.
2388 if (hasMarkedText_ && markedText_.length()) {
2389 // Sends the updated marked text to the renderer so it can update the
2390 // composition node in WebKit.
2391 // When marked text is available, |selectedRange_| will be the range being
2392 // selected inside the marked text.
2393 widgetHost->ImeSetComposition(markedText_, underlines_,
2394 selectedRange_.location,
2395 NSMaxRange(selectedRange_));
2396 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2397 if (unmarkTextCalled_) {
2398 widgetHost->ImeConfirmComposition(
2399 string16(), gfx::Range::InvalidRange(), false);
2401 widgetHost->ImeCancelComposition();
2405 // If the key event was handled by the input method but it also generated some
2406 // edit commands, then we need to send the real key event and corresponding
2407 // edit commands here. This usually occurs when the input method wants to
2408 // finish current composition session but still wants the application to
2409 // handle the key event. See http://crbug.com/48161 for reference.
2410 if (delayEventUntilAfterImeCompostion) {
2411 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2412 // with windowsKeyCode == 0xE5 has already been sent to webkit.
2413 // So before sending the real key down event, we need to send a fake key up
2414 // event to balance it.
2415 NativeWebKeyboardEvent fakeEvent = event;
2416 fakeEvent.type = WebKit::WebInputEvent::KeyUp;
2417 fakeEvent.skip_in_browser = true;
2418 widgetHost->ForwardKeyboardEvent(fakeEvent);
2419 // Not checking |renderWidgetHostView_->render_widget_host_| here because
2420 // a key event with |skip_in_browser| == true won't be handled by browser,
2421 // thus it won't destroy the widget.
2423 if (!editCommands_.empty()) {
2424 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2425 widgetHost->GetRoutingID(), editCommands_));
2427 widgetHost->ForwardKeyboardEvent(event);
2429 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2430 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2431 // be set to NULL. So we check it here and return immediately if it's NULL.
2432 if (!renderWidgetHostView_->render_widget_host_)
2436 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2437 // Only send a corresponding key press event if there is no marked text.
2438 if (!hasMarkedText_) {
2439 if (!textInserted && textToBeInserted_.length() == 1) {
2440 // If a single character was inserted, then we just send it as a keypress
2442 event.type = WebKit::WebInputEvent::Char;
2443 event.text[0] = textToBeInserted_[0];
2445 event.skip_in_browser = true;
2446 widgetHost->ForwardKeyboardEvent(event);
2447 } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2448 [[theEvent characters] length] > 0 &&
2449 (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2450 (hasEditCommands_ && editCommands_.empty()))) {
2451 // We don't get insertText: calls if ctrl or cmd is down, or the key event
2452 // generates an insert command. So synthesize a keypress event for these
2453 // cases, unless the key event generated any other command.
2454 event.type = WebKit::WebInputEvent::Char;
2455 event.skip_in_browser = true;
2456 widgetHost->ForwardKeyboardEvent(event);
2460 // Possibly autohide the cursor.
2461 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2462 [NSCursor setHiddenUntilMouseMoves:YES];
2465 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2466 DCHECK(base::mac::IsOSLionOrLater());
2468 if ([event phase] != NSEventPhaseEnded &&
2469 [event phase] != NSEventPhaseCancelled) {
2473 if (renderWidgetHostView_->render_widget_host_) {
2474 const WebMouseWheelEvent& webEvent =
2475 WebInputEventFactory::mouseWheelEvent(event, self);
2476 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2479 if (endWheelMonitor_) {
2480 [NSEvent removeMonitor:endWheelMonitor_];
2481 endWheelMonitor_ = nil;
2485 - (void)scrollWheel:(NSEvent*)event {
2486 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2487 BOOL handled = [delegate_ handleEvent:event];
2492 // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
2493 // the event is received even when the mouse cursor is no longer over the view
2494 // when the scrolling ends (e.g. if the tab was switched). This is necessary
2495 // for ending rubber-banding in such cases.
2496 if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
2497 !endWheelMonitor_) {
2499 [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
2500 handler:^(NSEvent* blockEvent) {
2501 [self shortCircuitScrollWheelEvent:blockEvent];
2506 if (renderWidgetHostView_->render_widget_host_) {
2507 const WebMouseWheelEvent& webEvent =
2508 WebInputEventFactory::mouseWheelEvent(event, self);
2509 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2513 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
2514 NSWindow* oldWindow = [self window];
2516 // We're messing with the window, so do this to ensure no flashes. This one
2517 // prevents a flash when the current tab is closed.
2518 if (!renderWidgetHostView_->use_core_animation_)
2519 [oldWindow disableScreenUpdatesUntilFlush];
2521 // If the new window for this view is using CoreAnimation then enable
2522 // CoreAnimation on this view.
2523 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY &&
2524 [[newWindow contentView] wantsLayer]) {
2525 renderWidgetHostView_->EnableCoreAnimation();
2528 NSNotificationCenter* notificationCenter =
2529 [NSNotificationCenter defaultCenter];
2531 // Backing property notifications crash on 10.6 when building with the 10.7
2532 // SDK, see http://crbug.com/260595.
2533 static BOOL supportsBackingPropertiesNotification =
2534 SupportsBackingPropertiesChangedNotification();
2537 if (supportsBackingPropertiesNotification) {
2540 name:NSWindowDidChangeBackingPropertiesNotification
2545 name:NSWindowDidMoveNotification
2549 name:NSWindowDidEndLiveResizeNotification
2553 if (supportsBackingPropertiesNotification) {
2556 selector:@selector(windowDidChangeBackingProperties:)
2557 name:NSWindowDidChangeBackingPropertiesNotification
2562 selector:@selector(windowChangedGlobalFrame:)
2563 name:NSWindowDidMoveNotification
2567 selector:@selector(windowChangedGlobalFrame:)
2568 name:NSWindowDidEndLiveResizeNotification
2573 - (void)updateTabBackingStoreScaleFactor {
2574 if (!renderWidgetHostView_->render_widget_host_)
2577 float scaleFactor = ScaleFactor(self);
2578 if (scaleFactor == deviceScaleFactor_)
2580 deviceScaleFactor_ = scaleFactor;
2582 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
2583 renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
2584 if (backingStore) // NULL in hardware path.
2585 backingStore->ScaleFactorChanged(scaleFactor);
2587 [self updateSoftwareLayerScaleFactor];
2588 renderWidgetHostView_->render_widget_host_->NotifyScreenInfoChanged();
2591 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
2592 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
2593 NSWindow* window = (NSWindow*)[notification object];
2595 CGFloat newBackingScaleFactor = [window backingScaleFactor];
2596 CGFloat oldBackingScaleFactor = [base::mac::ObjCCast<NSNumber>(
2597 [[notification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey])
2599 if (newBackingScaleFactor != oldBackingScaleFactor) {
2600 // Background tabs check if their scale factor changed when they are added
2603 // Allocating a CGLayerRef with the current scale factor immediately from
2604 // this handler doesn't work. Schedule the backing store update on the
2605 // next runloop cycle, then things are read for CGLayerRef allocations to
2607 [self performSelector:@selector(updateTabBackingStoreScaleFactor)
2613 - (void)globalFrameDidChange:(NSNotification*)notification {
2614 if (handlingGlobalFrameDidChange_)
2617 handlingGlobalFrameDidChange_ = YES;
2618 if (renderWidgetHostView_->compositing_iosurface_context_) {
2619 [renderWidgetHostView_->compositing_iosurface_context_->nsgl_context()
2622 handlingGlobalFrameDidChange_ = NO;
2625 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
2626 renderWidgetHostView_->UpdateScreenInfo(
2627 renderWidgetHostView_->GetNativeView());
2630 - (void)setFrameSize:(NSSize)newSize {
2631 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
2633 // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
2634 // -setFrame: isn't neccessary.
2635 [super setFrameSize:newSize];
2636 if (renderWidgetHostView_->render_widget_host_) {
2637 renderWidgetHostView_->render_widget_host_->SendScreenRects();
2638 renderWidgetHostView_->render_widget_host_->WasResized();
2641 // This call is necessary to make the window wait for a new frame at the new
2642 // size to be available before the resize completes. Calling only
2643 // setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay on
2644 // this is not sufficient.
2645 ScopedCAActionDisabler disabler;
2646 CGRect frame = NSRectToCGRect([renderWidgetHostView_->cocoa_view() bounds]);
2647 [renderWidgetHostView_->software_layer_ setFrame:frame];
2648 [renderWidgetHostView_->software_layer_ setNeedsDisplay];
2649 [renderWidgetHostView_->compositing_iosurface_layer_ setFrame:frame];
2650 [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
2653 - (void)callSetNeedsDisplayInRect {
2654 DCHECK([NSThread isMainThread]);
2655 DCHECK(renderWidgetHostView_->call_set_needs_display_in_rect_pending_);
2656 [self setNeedsDisplayInRect:renderWidgetHostView_->invalid_rect_];
2657 renderWidgetHostView_->call_set_needs_display_in_rect_pending_ = false;
2658 renderWidgetHostView_->invalid_rect_ = NSZeroRect;
2660 if (renderWidgetHostView_->compositing_iosurface_layer_)
2661 [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
2664 // Fills with white the parts of the area to the right and bottom for |rect|
2665 // that intersect |damagedRect|.
2666 - (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
2667 dirtyRect:(gfx::Rect)damagedRect
2668 inContext:(CGContextRef)context {
2669 if (damagedRect.right() > rect.right()) {
2670 int x = std::max(rect.right(), damagedRect.x());
2671 int y = std::min(rect.bottom(), damagedRect.bottom());
2672 int width = damagedRect.right() - x;
2673 int height = damagedRect.y() - y;
2675 // Extra fun to get around the fact that gfx::Rects can't have
2686 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
2687 CGContextSetFillColorWithColor(context,
2688 CGColorGetConstantColor(kCGColorWhite));
2689 CGContextFillRect(context, NSRectToCGRect(r));
2691 if (damagedRect.bottom() > rect.bottom()) {
2692 int x = damagedRect.x();
2693 int y = damagedRect.bottom();
2694 int width = damagedRect.right() - x;
2695 int height = std::max(rect.bottom(), damagedRect.y()) - y;
2697 // Extra fun to get around the fact that gfx::Rects can't have
2708 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
2709 CGContextSetFillColorWithColor(context,
2710 CGColorGetConstantColor(kCGColorWhite));
2711 CGContextFillRect(context, NSRectToCGRect(r));
2715 - (void)drawRect:(NSRect)dirtyRect {
2716 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::drawRect");
2717 CHECK(!renderWidgetHostView_->use_core_animation_);
2719 if (!renderWidgetHostView_->render_widget_host_) {
2720 // TODO(shess): Consider using something more noticable?
2721 [[NSColor whiteColor] set];
2722 NSRectFill(dirtyRect);
2726 DCHECK(!renderWidgetHostView_->about_to_validate_and_paint_);
2728 // GetBackingStore works for both software and accelerated frames. If a
2729 // SwapBuffers occurs while GetBackingStore is blocking, we will continue to
2730 // blit the IOSurface below.
2731 renderWidgetHostView_->about_to_validate_and_paint_ = true;
2732 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
2733 renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
2734 renderWidgetHostView_->about_to_validate_and_paint_ = false;
2736 const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
2738 if (renderWidgetHostView_->last_frame_was_accelerated_ &&
2739 renderWidgetHostView_->compositing_iosurface_) {
2740 if (renderWidgetHostView_->allow_overlapping_views_) {
2741 CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
2743 // If overlapping views need to be allowed, punch a hole in the window
2744 // to expose the GL underlay.
2745 TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
2746 "h", damagedRect.height());
2747 // NSRectFill is extremely slow (15ms for a window on a fast MacPro), so
2748 // this is only done when it's a real invalidation from window damage (not
2749 // when a BuffersSwapped was received). Note that even a 1x1 NSRectFill
2750 // can take many milliseconds sometimes (!) so this is skipped completely
2751 // for drawRects that are triggered by BuffersSwapped messages.
2752 [[NSColor clearColor] set];
2753 NSRectFill(dirtyRect);
2756 if (renderWidgetHostView_->DrawIOSurfaceWithoutCoreAnimation())
2759 // On error, fall back to software and fall through to the non-accelerated
2761 renderWidgetHostView_->GotAcceleratedCompositingError();
2764 CGContextRef context = static_cast<CGContextRef>(
2765 [[NSGraphicsContext currentContext] graphicsPort]);
2766 [self drawBackingStore:backingStore
2767 dirtyRect:NSRectToCGRect(dirtyRect)
2771 - (void)drawBackingStore:(BackingStoreMac*)backingStore
2772 dirtyRect:(CGRect)dirtyRect
2773 inContext:(CGContextRef)context {
2774 content::SoftwareFrameManager* software_frame_manager =
2775 renderWidgetHostView_->software_frame_manager_.get();
2776 // There should never be both a legacy software and software composited
2778 DCHECK(!backingStore || !software_frame_manager->HasCurrentFrame());
2780 if (backingStore || software_frame_manager->HasCurrentFrame()) {
2781 // Note: All coordinates are in view units, not pixels.
2782 gfx::Rect bitmapRect(
2783 software_frame_manager->HasCurrentFrame() ?
2784 software_frame_manager->GetCurrentFrameSizeInDIP() :
2785 backingStore->size());
2787 // Specify the proper y offset to ensure that the view is rooted to the
2788 // upper left corner. This can be negative, if the window was resized
2789 // smaller and the renderer hasn't yet repainted.
2790 int yOffset = NSHeight([self bounds]) - bitmapRect.height();
2792 NSRect nsDirtyRect = NSRectFromCGRect(dirtyRect);
2793 const gfx::Rect damagedRect([self flipNSRectToRect:nsDirtyRect]);
2795 gfx::Rect paintRect = gfx::IntersectRects(bitmapRect, damagedRect);
2796 if (!paintRect.IsEmpty()) {
2797 if (software_frame_manager->HasCurrentFrame()) {
2798 // If a software compositor framebuffer is present, draw using that.
2799 gfx::Size sizeInPixels =
2800 software_frame_manager->GetCurrentFrameSizeInPixels();
2801 base::ScopedCFTypeRef<CGDataProviderRef> dataProvider(
2802 CGDataProviderCreateWithData(
2804 software_frame_manager->GetCurrentFramePixels(),
2805 4 * sizeInPixels.width() * sizeInPixels.height(),
2807 base::ScopedCFTypeRef<CGImageRef> image(
2809 sizeInPixels.width(),
2810 sizeInPixels.height(),
2813 4 * sizeInPixels.width(),
2814 base::mac::GetSystemColorSpace(),
2815 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
2819 kCGRenderingIntentDefault));
2820 CGRect imageRect = bitmapRect.ToCGRect();
2821 imageRect.origin.y = yOffset;
2822 CGContextDrawImage(context, imageRect, image);
2823 } else if (backingStore->cg_layer()) {
2824 // If we have a CGLayer, draw that into the window
2825 // TODO: add clipping to dirtyRect if it improves drawing performance.
2826 CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset),
2827 backingStore->cg_layer());
2829 // If we haven't created a layer yet, draw the cached bitmap into
2830 // the window. The CGLayer will be created the next time the renderer
2832 base::ScopedCFTypeRef<CGImageRef> image(
2833 CGBitmapContextCreateImage(backingStore->cg_bitmap()));
2834 CGRect imageRect = bitmapRect.ToCGRect();
2835 imageRect.origin.y = yOffset;
2836 CGContextDrawImage(context, imageRect, image);
2840 renderWidgetHostView_->FrameSwapped();
2842 // Fill the remaining portion of the damagedRect with white
2843 [self fillBottomRightRemainderOfRect:bitmapRect
2844 dirtyRect:damagedRect
2847 if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
2848 base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
2849 renderWidgetHostView_->whiteout_start_time_;
2850 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
2852 // Reset the start time to 0 so that we start recording again the next
2853 // time the backing store is NULL...
2854 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
2856 if (!renderWidgetHostView_->web_contents_switch_paint_time_.is_null()) {
2857 base::TimeDelta web_contents_switch_paint_duration =
2858 base::TimeTicks::Now() -
2859 renderWidgetHostView_->web_contents_switch_paint_time_;
2860 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
2861 web_contents_switch_paint_duration);
2862 // Reset contents_switch_paint_time_ to 0 so future tab selections are
2864 renderWidgetHostView_->web_contents_switch_paint_time_ =
2868 CGContextSetFillColorWithColor(context,
2869 CGColorGetConstantColor(kCGColorWhite));
2870 CGContextFillRect(context, dirtyRect);
2871 if (renderWidgetHostView_->whiteout_start_time_.is_null())
2872 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
2876 - (BOOL)canBecomeKeyView {
2877 if (!renderWidgetHostView_->render_widget_host_)
2880 return canBeKeyView_;
2883 - (BOOL)acceptsFirstResponder {
2884 if (!renderWidgetHostView_->render_widget_host_)
2887 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
2890 - (BOOL)becomeFirstResponder {
2891 if (!renderWidgetHostView_->render_widget_host_)
2894 renderWidgetHostView_->render_widget_host_->Focus();
2895 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
2896 renderWidgetHostView_->SetTextInputActive(true);
2898 // Cancel any onging composition text which was left before we lost focus.
2899 // TODO(suzhe): We should do it in -resignFirstResponder: method, but
2900 // somehow that method won't be called when switching among different tabs.
2901 // See http://crbug.com/47209
2902 [self cancelComposition];
2904 NSNumber* direction = [NSNumber numberWithUnsignedInteger:
2905 [[self window] keyViewSelectionDirection]];
2906 NSDictionary* userInfo =
2907 [NSDictionary dictionaryWithObject:direction
2908 forKey:kSelectionDirection];
2909 [[NSNotificationCenter defaultCenter]
2910 postNotificationName:kViewDidBecomeFirstResponder
2917 - (BOOL)resignFirstResponder {
2918 renderWidgetHostView_->SetTextInputActive(false);
2919 if (!renderWidgetHostView_->render_widget_host_)
2922 if (closeOnDeactivate_)
2923 renderWidgetHostView_->KillSelf();
2925 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
2926 renderWidgetHostView_->render_widget_host_->Blur();
2928 // We should cancel any onging composition whenever RWH's Blur() method gets
2929 // called, because in this case, webkit will confirm the ongoing composition
2931 [self cancelComposition];
2936 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
2937 if (delegate_ && [delegate_ respondsToSelector:
2938 @selector(validateUserInterfaceItem:isValidItem:)]) {
2940 BOOL known = [delegate_ validateUserInterfaceItem:item
2941 isValidItem:&valid];
2946 SEL action = [item action];
2948 if (action == @selector(stopSpeaking:)) {
2949 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
2950 renderWidgetHostView_->IsSpeaking();
2952 if (action == @selector(startSpeaking:)) {
2953 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
2954 renderWidgetHostView_->SupportsSpeech();
2957 // For now, these actions are always enabled for render view,
2958 // this is sub-optimal.
2959 // TODO(suzhe): Plumb the "can*" methods up from WebCore.
2960 if (action == @selector(undo:) ||
2961 action == @selector(redo:) ||
2962 action == @selector(cut:) ||
2963 action == @selector(copy:) ||
2964 action == @selector(copyToFindPboard:) ||
2965 action == @selector(paste:) ||
2966 action == @selector(pasteAndMatchStyle:)) {
2967 return renderWidgetHostView_->render_widget_host_->IsRenderView();
2970 return editCommand_helper_->IsMenuItemEnabled(action, self);
2973 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
2974 return renderWidgetHostView_.get();
2977 // Determine whether we should autohide the cursor (i.e., hide it until mouse
2978 // move) for the given event. Customize here to be more selective about which
2979 // key presses to autohide on.
2980 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
2981 return ([event type] == NSKeyDown &&
2982 !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
2985 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
2986 index:(NSUInteger)index
2987 maxCount:(NSUInteger)maxCount {
2988 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
2989 NSUInteger totalLength = [fullArray count];
2990 if (index >= totalLength)
2992 NSUInteger length = MIN(totalLength - index, maxCount);
2993 return [fullArray subarrayWithRange:NSMakeRange(index, length)];
2996 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
2997 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
2998 return [fullArray count];
3001 - (id)accessibilityAttributeValue:(NSString *)attribute {
3002 BrowserAccessibilityManager* manager =
3003 renderWidgetHostView_->GetBrowserAccessibilityManager();
3005 // Contents specifies document view of RenderWidgetHostViewCocoa provided by
3006 // BrowserAccessibilityManager. Children includes all subviews in addition to
3007 // contents. Currently we do not have subviews besides the document view.
3008 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
3009 [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
3011 return [NSArray arrayWithObjects:manager->
3012 GetRoot()->ToBrowserAccessibilityCocoa(), nil];
3013 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
3014 return NSAccessibilityScrollAreaRole;
3016 id ret = [super accessibilityAttributeValue:attribute];
3020 - (NSArray*)accessibilityAttributeNames {
3021 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
3022 [ret addObject:NSAccessibilityContentsAttribute];
3023 [ret addObjectsFromArray:[super accessibilityAttributeNames]];
3027 - (id)accessibilityHitTest:(NSPoint)point {
3028 if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
3030 NSPoint pointInWindow = [[self window] convertScreenToBase:point];
3031 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
3032 localPoint.y = NSHeight([self bounds]) - localPoint.y;
3033 BrowserAccessibilityCocoa* root = renderWidgetHostView_->
3034 GetBrowserAccessibilityManager()->
3035 GetRoot()->ToBrowserAccessibilityCocoa();
3036 id obj = [root accessibilityHitTest:localPoint];
3040 - (BOOL)accessibilityIsIgnored {
3041 return !renderWidgetHostView_->GetBrowserAccessibilityManager();
3044 - (NSUInteger)accessibilityGetIndexOf:(id)child {
3045 BrowserAccessibilityManager* manager =
3046 renderWidgetHostView_->GetBrowserAccessibilityManager();
3047 // Only child is root.
3049 manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
3056 - (id)accessibilityFocusedUIElement {
3057 BrowserAccessibilityManager* manager =
3058 renderWidgetHostView_->GetBrowserAccessibilityManager();
3060 BrowserAccessibility* focused_item = manager->GetFocus(NULL);
3061 DCHECK(focused_item);
3063 BrowserAccessibilityCocoa* focused_item_cocoa =
3064 focused_item->ToBrowserAccessibilityCocoa();
3065 DCHECK(focused_item_cocoa);
3066 if (focused_item_cocoa)
3067 return focused_item_cocoa;
3070 return [super accessibilityFocusedUIElement];
3073 - (void)doDefaultAction:(int32)accessibilityObjectId {
3074 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3075 rwh->Send(new AccessibilityMsg_DoDefaultAction(
3076 rwh->GetRoutingID(), accessibilityObjectId));
3079 // VoiceOver uses this method to move the caret to the beginning of the next
3080 // word in a text field.
3081 - (void)accessibilitySetTextSelection:(int32)accId
3082 startOffset:(int32)startOffset
3083 endOffset:(int32)endOffset {
3084 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3085 rwh->AccessibilitySetTextSelection(accId, startOffset, endOffset);
3088 // Convert a web accessibility's location in web coordinates into a cocoa
3089 // screen coordinate.
3090 - (NSPoint)accessibilityPointInScreen:
3091 (BrowserAccessibilityCocoa*)accessibility {
3092 NSPoint origin = [accessibility origin];
3093 NSSize size = [[accessibility size] sizeValue];
3094 origin.y = NSHeight([self bounds]) - origin.y;
3095 NSPoint originInWindow = [self convertPoint:origin toView:nil];
3096 NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow];
3097 originInScreen.y = originInScreen.y - size.height;
3098 return originInScreen;
3101 - (void)setAccessibilityFocus:(BOOL)focus
3102 accessibilityId:(int32)accessibilityObjectId {
3104 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3105 rwh->Send(new AccessibilityMsg_SetFocus(
3106 rwh->GetRoutingID(), accessibilityObjectId));
3108 // Immediately set the focused item even though we have not officially set
3109 // focus on it as VoiceOver expects to get the focused item after this
3111 BrowserAccessibilityManager* manager =
3112 renderWidgetHostView_->GetBrowserAccessibilityManager();
3113 manager->SetFocus(manager->GetFromRendererID(accessibilityObjectId), false);
3117 - (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
3118 // Performs a right click copying WebKit's
3119 // accessibilityPerformShowMenuAction.
3120 NSPoint location = [self accessibilityPointInScreen:accessibility];
3121 NSSize size = [[accessibility size] sizeValue];
3122 location = [[self window] convertScreenToBase:location];
3123 location.x += size.width/2;
3124 location.y += size.height/2;
3126 NSEvent* fakeRightClick = [NSEvent
3127 mouseEventWithType:NSRightMouseDown
3131 windowNumber:[[self window] windowNumber]
3132 context:[NSGraphicsContext currentContext]
3137 [self mouseEvent:fakeRightClick];
3140 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
3141 // with minor modifications for code style and commenting.
3143 // The 'public' interface is -setToolTipAtMousePoint:. This differs from
3144 // -setToolTip: in that the updated tooltip takes effect immediately,
3145 // without the user's having to move the mouse out of and back into the view.
3147 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
3148 // the view, which in turn requires overriding some internal tracking-rect
3149 // methods (to keep track of its owner & userdata, which need to be filled out
3150 // in the fake events.) --snej 7/6/09
3154 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3155 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
3157 * Redistribution and use in source and binary forms, with or without
3158 * modification, are permitted provided that the following conditions
3161 * 1. Redistributions of source code must retain the above copyright
3162 * notice, this list of conditions and the following disclaimer.
3163 * 2. Redistributions in binary form must reproduce the above copyright
3164 * notice, this list of conditions and the following disclaimer in the
3165 * documentation and/or other materials provided with the distribution.
3166 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
3167 * its contributors may be used to endorse or promote products derived
3168 * from this software without specific prior written permission.
3170 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
3171 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3172 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3173 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
3174 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3175 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
3176 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3177 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3178 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3179 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3182 // Any non-zero value will do, but using something recognizable might help us
3184 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
3186 // Override of a public NSView method, replacing the inherited functionality.
3187 // See above for rationale.
3188 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
3190 userData:(void *)data
3191 assumeInside:(BOOL)assumeInside {
3192 DCHECK(trackingRectOwner_ == nil);
3193 trackingRectOwner_ = owner;
3194 trackingRectUserData_ = data;
3195 return kTrackingRectTag;
3198 // Override of (apparently) a private NSView method(!) See above for rationale.
3199 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
3201 userData:(void *)data
3202 assumeInside:(BOOL)assumeInside
3203 useTrackingNum:(int)tag {
3204 DCHECK(tag == 0 || tag == kTrackingRectTag);
3205 DCHECK(trackingRectOwner_ == nil);
3206 trackingRectOwner_ = owner;
3207 trackingRectUserData_ = data;
3208 return kTrackingRectTag;
3211 // Override of (apparently) a private NSView method(!) See above for rationale.
3212 - (void)_addTrackingRects:(NSRect *)rects
3214 userDataList:(void **)userDataList
3215 assumeInsideList:(BOOL *)assumeInsideList
3216 trackingNums:(NSTrackingRectTag *)trackingNums
3219 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3220 DCHECK(trackingRectOwner_ == nil);
3221 trackingRectOwner_ = owner;
3222 trackingRectUserData_ = userDataList[0];
3223 trackingNums[0] = kTrackingRectTag;
3226 // Override of a public NSView method, replacing the inherited functionality.
3227 // See above for rationale.
3228 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3232 if (tag == kTrackingRectTag) {
3233 trackingRectOwner_ = nil;
3237 if (tag == lastToolTipTag_) {
3238 [super removeTrackingRect:tag];
3239 lastToolTipTag_ = 0;
3243 // If any other tracking rect is being removed, we don't know how it was
3244 // created and it's possible there's a leak involved (see Radar 3500217).
3248 // Override of (apparently) a private NSView method(!)
3249 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3250 for (int i = 0; i < count; ++i) {
3254 DCHECK(tag == kTrackingRectTag);
3255 trackingRectOwner_ = nil;
3259 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3260 - (void)_sendToolTipMouseExited {
3261 // Nothing matters except window, trackingNumber, and userData.
3262 int windowNumber = [[self window] windowNumber];
3263 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3264 location:NSZeroPoint
3267 windowNumber:windowNumber
3270 trackingNumber:kTrackingRectTag
3271 userData:trackingRectUserData_];
3272 [trackingRectOwner_ mouseExited:fakeEvent];
3275 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3276 - (void)_sendToolTipMouseEntered {
3277 // Nothing matters except window, trackingNumber, and userData.
3278 int windowNumber = [[self window] windowNumber];
3279 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3280 location:NSZeroPoint
3283 windowNumber:windowNumber
3286 trackingNumber:kTrackingRectTag
3287 userData:trackingRectUserData_];
3288 [trackingRectOwner_ mouseEntered:fakeEvent];
3291 // Sets the view's current tooltip, to be displayed at the current mouse
3292 // location. (This does not make the tooltip appear -- as usual, it only
3293 // appears after a delay.) Pass null to remove the tooltip.
3294 - (void)setToolTipAtMousePoint:(NSString *)string {
3295 NSString *toolTip = [string length] == 0 ? nil : string;
3296 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3297 (!toolTip && !toolTip_)) {
3302 [self _sendToolTipMouseExited];
3305 toolTip_.reset([toolTip copy]);
3308 // See radar 3500217 for why we remove all tooltips
3309 // rather than just the single one we created.
3310 [self removeAllToolTips];
3311 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3312 lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3315 [self _sendToolTipMouseEntered];
3319 // NSView calls this to get the text when displaying the tooltip.
3320 - (NSString *)view:(NSView *)view
3321 stringForToolTip:(NSToolTipTag)tag
3322 point:(NSPoint)point
3323 userData:(void *)data {
3324 return [[toolTip_ copy] autorelease];
3327 // Below is our NSTextInputClient implementation.
3329 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3330 // functions to process this event.
3332 // [WebHTMLView keyDown] ->
3333 // EventHandler::keyEvent() ->
3335 // [WebEditorClient handleKeyboardEvent] ->
3336 // [WebHTMLView _interceptEditingKeyEvent] ->
3337 // [NSResponder interpretKeyEvents] ->
3338 // [WebHTMLView insertText] ->
3339 // Editor::insertText()
3341 // Unfortunately, it is hard for Chromium to use this implementation because
3342 // it causes key-typing jank.
3343 // RenderWidgetHostViewMac is running in a browser process. On the other
3344 // hand, Editor and EventHandler are running in a renderer process.
3345 // So, if we used this implementation, a NSKeyDown event is dispatched to
3346 // the following functions of Chromium.
3348 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3349 // |Sync IPC (KeyDown)| (*1) ->
3350 // EventHandler::keyEvent() (renderer) ->
3352 // EditorClientImpl::handleKeyboardEvent() (renderer) ->
3353 // |Sync IPC| (*2) ->
3354 // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3355 // [self interpretKeyEvents] ->
3356 // [RenderWidgetHostViewMac insertText] (browser) ->
3358 // Editor::insertText() (renderer)
3360 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3361 // result of EventHandler::keyEvent().
3362 // (*2) we need to wait until this call finishes since WebEditorClient uses
3363 // the result of [WebHTMLView _interceptEditingKeyEvent].
3365 // This needs many sync IPC messages sent between a browser and a renderer for
3366 // each key event, which would probably result in key-typing jank.
3367 // To avoid this problem, this implementation processes key events (and input
3368 // method events) totally in a browser process and sends asynchronous input
3369 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3370 // renderer process.
3372 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3373 // |Async IPC (RawKeyDown)| ->
3374 // [self interpretKeyEvents] ->
3375 // [RenderWidgetHostViewMac insertText] (browser) ->
3376 // |Async IPC (Char)| ->
3377 // Editor::insertText() (renderer)
3379 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3380 // make any key-typing jank. --hbono 7/23/09
3383 extern NSString *NSTextInputReplacementRangeAttributeName;
3386 - (NSArray *)validAttributesForMarkedText {
3387 // This code is just copied from WebKit except renaming variables.
3388 if (!validAttributesForMarkedText_) {
3389 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3390 NSUnderlineStyleAttributeName,
3391 NSUnderlineColorAttributeName,
3392 NSMarkedClauseSegmentAttributeName,
3393 NSTextInputReplacementRangeAttributeName,
3396 return validAttributesForMarkedText_.get();
3399 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3400 DCHECK([self window]);
3401 // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3402 // coordinates (upper left origin). Scroll offsets will be taken care of in
3404 thePoint = [[self window] convertScreenToBase:thePoint];
3405 thePoint = [self convertPoint:thePoint fromView:nil];
3406 thePoint.y = NSHeight([self frame]) - thePoint.y;
3409 TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3410 renderWidgetHostView_->render_widget_host_,
3411 gfx::Point(thePoint.x, thePoint.y));
3415 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3416 actualRange:(NSRangePointer)actualRange {
3418 if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3422 rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3423 renderWidgetHostView_->render_widget_host_, theRange);
3425 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3427 *actualRange = theRange;
3430 // The returned rectangle is in WebKit coordinates (upper left origin), so
3431 // flip the coordinate system.
3432 NSRect viewFrame = [self frame];
3433 rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3437 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3438 actualRange:(NSRangePointer)actualRange {
3439 NSRect rect = [self firstViewRectForCharacterRange:theRange
3440 actualRange:actualRange];
3442 // Convert into screen coordinates for return.
3443 rect = [self convertRect:rect toView:nil];
3444 rect.origin = [[self window] convertBaseToScreen:rect.origin];
3448 - (NSRange)markedRange {
3449 // An input method calls this method to check if an application really has
3450 // a text being composed when hasMarkedText call returns true.
3451 // Returns the range saved in the setMarkedText method so the input method
3452 // calls the setMarkedText method and we can update the composition node
3453 // there. (When this method returns an empty range, the input method doesn't
3454 // call the setMarkedText method.)
3455 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3458 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3459 actualRange:(NSRangePointer)actualRange {
3460 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3462 *actualRange = range;
3463 NSAttributedString* str =
3464 TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3465 renderWidgetHostView_->render_widget_host_, range);
3469 - (NSInteger)conversationIdentifier {
3470 return reinterpret_cast<NSInteger>(self);
3473 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3474 // nil when the caret is in non-editable content or password box to avoid
3475 // making input methods do their work.
3476 - (NSTextInputContext *)inputContext {
3477 if (focusedPluginIdentifier_ != -1)
3478 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3480 switch(renderWidgetHostView_->text_input_type_) {
3481 case ui::TEXT_INPUT_TYPE_NONE:
3482 case ui::TEXT_INPUT_TYPE_PASSWORD:
3485 return [super inputContext];
3489 - (BOOL)hasMarkedText {
3490 // An input method calls this function to figure out whether or not an
3491 // application is really composing a text. If it is composing, it calls
3492 // the markedRange method, and maybe calls the setMarkedText method.
3493 // It seems an input method usually calls this function when it is about to
3494 // cancel an ongoing composition. If an application has a non-empty marked
3495 // range, it calls the setMarkedText method to delete the range.
3496 return hasMarkedText_;
3499 - (void)unmarkText {
3500 // Delete the composition node of the renderer and finish an ongoing
3502 // It seems an input method calls the setMarkedText method and set an empty
3503 // text when it cancels an ongoing composition, i.e. I have never seen an
3504 // input method calls this method.
3505 hasMarkedText_ = NO;
3506 markedText_.clear();
3507 underlines_.clear();
3509 // If we are handling a key down event, then ConfirmComposition() will be
3510 // called in keyEvent: method.
3511 if (!handlingKeyDown_) {
3512 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3513 string16(), gfx::Range::InvalidRange(), false);
3515 unmarkTextCalled_ = YES;
3519 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3520 replacementRange:(NSRange)replacementRange {
3521 // An input method updates the composition string.
3522 // We send the given text and range to the renderer so it can update the
3523 // composition node of WebKit.
3524 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3525 // the full web content.
3526 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3527 NSString* im_text = isAttributedString ? [string string] : string;
3528 int length = [im_text length];
3530 // |markedRange_| will get set on a callback from ImeSetComposition().
3531 selectedRange_ = newSelRange;
3532 markedText_ = base::SysNSStringToUTF16(im_text);
3533 hasMarkedText_ = (length > 0);
3535 underlines_.clear();
3536 if (isAttributedString) {
3537 ExtractUnderlines(string, &underlines_);
3539 // Use a thin black underline by default.
3540 underlines_.push_back(
3541 WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
3544 // If we are handling a key down event, then SetComposition() will be
3545 // called in keyEvent: method.
3546 // Input methods of Mac use setMarkedText calls with an empty text to cancel
3547 // an ongoing composition. So, we should check whether or not the given text
3548 // is empty to update the input method state. (Our input method backend can
3549 // automatically cancels an ongoing composition when we send an empty text.
3550 // So, it is OK to send an empty text to the renderer.)
3551 if (!handlingKeyDown_) {
3552 renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3553 markedText_, underlines_,
3554 newSelRange.location, NSMaxRange(newSelRange));
3558 - (void)doCommandBySelector:(SEL)selector {
3559 // An input method calls this function to dispatch an editing command to be
3560 // handled by this view.
3561 if (selector == @selector(noop:))
3564 std::string command(
3565 [RenderWidgetHostViewMacEditCommandHelper::
3566 CommandNameForSelector(selector) UTF8String]);
3568 // If this method is called when handling a key down event, then we need to
3569 // handle the command in the key event handler. Otherwise we can just handle
3571 if (handlingKeyDown_) {
3572 hasEditCommands_ = YES;
3573 // We ignore commands that insert characters, because this was causing
3574 // strange behavior (e.g. tab always inserted a tab rather than moving to
3575 // the next field on the page).
3576 if (!StartsWithASCII(command, "insert", false))
3577 editCommands_.push_back(EditCommand(command, ""));
3579 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3580 rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
3585 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
3586 // An input method has characters to be inserted.
3587 // Same as Linux, Mac calls this method not only:
3588 // * when an input method finishs composing text, but also;
3589 // * when we type an ASCII character (without using input methods).
3590 // When we aren't using input methods, we should send the given character as
3591 // a Char event so it is dispatched to an onkeypress() event handler of
3593 // On the other hand, when we are using input methods, we should send the
3594 // given characters as an input method event and prevent the characters from
3595 // being dispatched to onkeypress() event handlers.
3596 // Text inserting might be initiated by other source instead of keyboard
3597 // events, such as the Characters dialog. In this case the text should be
3598 // sent as an input method event as well.
3599 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3600 // the full web content.
3601 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3602 NSString* im_text = isAttributedString ? [string string] : string;
3603 if (handlingKeyDown_) {
3604 textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
3606 gfx::Range replacement_range(replacementRange);
3607 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3608 base::SysNSStringToUTF16(im_text), replacement_range, false);
3611 // Inserting text will delete all marked text automatically.
3612 hasMarkedText_ = NO;
3615 - (void)insertText:(id)string {
3616 [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
3619 - (void)viewDidMoveToWindow {
3621 [self updateTabBackingStoreScaleFactor];
3623 if (canBeKeyView_) {
3624 NSWindow* newWindow = [self window];
3625 // Pointer comparison only, since we don't know if lastWindow_ is still
3628 // If we move into a new window, refresh the frame information. We
3629 // don't need to do it if it was the same window as it used to be in,
3630 // since that case is covered by WasShown(). We only want to do this for
3631 // real browser views, not popups.
3632 if (newWindow != lastWindow_) {
3633 lastWindow_ = newWindow;
3634 renderWidgetHostView_->WindowFrameChanged();
3639 // If we switch windows (or are removed from the view hierarchy), cancel any
3640 // open mouse-downs.
3641 if (hasOpenMouseDown_) {
3642 WebMouseEvent event;
3643 event.type = WebInputEvent::MouseUp;
3644 event.button = WebMouseEvent::ButtonLeft;
3645 renderWidgetHostView_->ForwardMouseEvent(event);
3647 hasOpenMouseDown_ = NO;
3650 // Resize the view's layers to match the new window size.
3651 ScopedCAActionDisabler disabler;
3652 [renderWidgetHostView_->software_layer_
3653 setFrame:NSRectToCGRect([self bounds])];
3654 [renderWidgetHostView_->compositing_iosurface_layer_
3655 setFrame:NSRectToCGRect([self bounds])];
3658 - (void)undo:(id)sender {
3659 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3660 static_cast<RenderViewHostImpl*>(
3661 renderWidgetHostView_->render_widget_host_)->Undo();
3665 - (void)redo:(id)sender {
3666 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3667 static_cast<RenderViewHostImpl*>(
3668 renderWidgetHostView_->render_widget_host_)->Redo();
3672 - (void)cut:(id)sender {
3673 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3674 static_cast<RenderViewHostImpl*>(
3675 renderWidgetHostView_->render_widget_host_)->Cut();
3679 - (void)copy:(id)sender {
3680 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3681 static_cast<RenderViewHostImpl*>(
3682 renderWidgetHostView_->render_widget_host_)->Copy();
3686 - (void)copyToFindPboard:(id)sender {
3687 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3688 static_cast<RenderViewHostImpl*>(
3689 renderWidgetHostView_->render_widget_host_)->CopyToFindPboard();
3693 - (void)paste:(id)sender {
3694 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3695 static_cast<RenderViewHostImpl*>(
3696 renderWidgetHostView_->render_widget_host_)->Paste();
3700 - (void)pasteAndMatchStyle:(id)sender {
3701 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3702 static_cast<RenderViewHostImpl*>(
3703 renderWidgetHostView_->render_widget_host_)->PasteAndMatchStyle();
3707 - (void)selectAll:(id)sender {
3708 // editCommand_helper_ adds implementations for most NSResponder methods
3709 // dynamically. But the renderer side only sends selection results back to
3710 // the browser if they were triggered by a keyboard event or went through
3711 // one of the Select methods on RWH. Since selectAll: is called from the
3712 // menu handler, neither is true.
3713 // Explicitly call SelectAll() here to make sure the renderer returns
3714 // selection results.
3715 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3716 static_cast<RenderViewHostImpl*>(
3717 renderWidgetHostView_->render_widget_host_)->SelectAll();
3721 - (void)startSpeaking:(id)sender {
3722 renderWidgetHostView_->SpeakSelection();
3725 - (void)stopSpeaking:(id)sender {
3726 renderWidgetHostView_->StopSpeaking();
3729 - (void)cancelComposition {
3730 if (!hasMarkedText_)
3733 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
3734 // doesn't call any NSTextInput functions, such as setMarkedText or
3735 // insertText. So, we need to send an IPC message to a renderer so it can
3736 // delete the composition node.
3737 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3738 [currentInputManager markedTextAbandoned:self];
3740 hasMarkedText_ = NO;
3741 // Should not call [self unmarkText] here, because it'll send unnecessary
3742 // cancel composition IPC message to the renderer.
3745 - (void)confirmComposition {
3746 if (!hasMarkedText_)
3749 if (renderWidgetHostView_->render_widget_host_)
3750 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3751 string16(), gfx::Range::InvalidRange(), false);
3753 [self cancelComposition];
3756 - (void)setPluginImeActive:(BOOL)active {
3757 if (active == pluginImeActive_)
3760 pluginImeActive_ = active;
3762 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
3763 renderWidgetHostView_->PluginImeCompositionCompleted(
3764 string16(), focusedPluginIdentifier_);
3768 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
3770 focusedPluginIdentifier_ = pluginId;
3771 else if (focusedPluginIdentifier_ == pluginId)
3772 focusedPluginIdentifier_ = -1;
3774 // Whenever plugin focus changes, plugin IME resets.
3775 [self setPluginImeActive:NO];
3778 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
3779 if (!pluginImeActive_)
3782 ComplexTextInputPanel* inputPanel =
3783 [ComplexTextInputPanel sharedComplexTextInputPanel];
3784 NSString* composited_string = nil;
3785 BOOL handled = [inputPanel interpretKeyEvent:event
3786 string:&composited_string];
3787 if (composited_string) {
3788 renderWidgetHostView_->PluginImeCompositionCompleted(
3789 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
3790 pluginImeActive_ = NO;
3795 - (void)checkForPluginImeCancellation {
3796 if (pluginImeActive_ &&
3797 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
3798 renderWidgetHostView_->PluginImeCompositionCompleted(
3799 string16(), focusedPluginIdentifier_);
3800 pluginImeActive_ = NO;
3804 // Overriding a NSResponder method to support application services.
3806 - (id)validRequestorForSendType:(NSString*)sendType
3807 returnType:(NSString*)returnType {
3809 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
3810 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
3811 BOOL hasText = !renderWidgetHostView_->selected_text().empty();
3813 renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
3815 if (sendTypeIsString && hasText && !returnType) {
3817 } else if (!sendType && returnTypeIsString && takesText) {
3819 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
3822 requestor = [super validRequestorForSendType:sendType
3823 returnType:returnType];
3828 - (void)viewWillStartLiveResize {
3829 [super viewWillStartLiveResize];
3830 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3832 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
3835 - (void)viewDidEndLiveResize {
3836 [super viewDidEndLiveResize];
3837 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3839 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
3842 - (void)updateCursor:(NSCursor*)cursor {
3843 if (currentCursor_ == cursor)
3846 currentCursor_.reset([cursor retain]);
3847 [[self window] invalidateCursorRectsForView:self];
3850 - (void)popupWindowWillClose:(NSNotification *)notification {
3851 renderWidgetHostView_->KillSelf();
3854 - (void)updateSoftwareLayerScaleFactor {
3855 if (![renderWidgetHostView_->software_layer_
3856 respondsToSelector:@selector(setContentsScale:)])
3859 ScopedCAActionDisabler disabler;
3860 [renderWidgetHostView_->software_layer_ setContentsScale:deviceScaleFactor_];
3863 // Delegate methods for the software CALayer
3864 - (void)drawLayer:(CALayer*)layer
3865 inContext:(CGContextRef)context {
3866 TRACE_EVENT0("browser", "CompositingIOSurfaceLayer::drawLayer");
3868 DCHECK(renderWidgetHostView_->use_core_animation_);
3869 DCHECK([layer isEqual:renderWidgetHostView_->software_layer_]);
3871 CGRect clipRect = CGContextGetClipBoundingBox(context);
3873 if (!renderWidgetHostView_->render_widget_host_ ||
3874 renderWidgetHostView_->render_widget_host_->is_hidden()) {
3875 CGContextSetFillColorWithColor(context,
3876 CGColorGetConstantColor(kCGColorWhite));
3877 CGContextFillRect(context, clipRect);
3881 renderWidgetHostView_->about_to_validate_and_paint_ = true;
3882 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
3883 renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
3884 renderWidgetHostView_->about_to_validate_and_paint_ = false;
3886 [self drawBackingStore:backingStore
3891 - (void)setNeedsDisplay:(BOOL)flag {
3892 [renderWidgetHostView_->software_layer_ setNeedsDisplay];
3893 [super setNeedsDisplay:flag];
3896 - (void)setNeedsDisplayInRect:(NSRect)rect {
3897 [renderWidgetHostView_->software_layer_
3898 setNeedsDisplayInRect:NSRectToCGRect(rect)];
3899 [super setNeedsDisplayInRect:rect];
3905 // Supporting application services
3907 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
3909 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
3910 types:(NSArray*)types {
3911 const std::string& str = renderWidgetHostView_->selected_text();
3912 if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
3914 base::scoped_nsobject<NSString> text(
3915 [[NSString alloc] initWithUTF8String:str.c_str()]);
3916 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
3917 [pboard declareTypes:toDeclare owner:nil];
3918 return [pboard setString:text forType:NSStringPboardType];
3921 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
3922 NSString *string = [pboard stringForType:NSStringPboardType];
3923 if (!string) return NO;
3925 // If the user is currently using an IME, confirm the IME input,
3926 // and then insert the text from the service, the same as TextEdit and Safari.
3927 [self confirmComposition];
3928 [self insertText:string];
3933 if (renderWidgetHostView_->use_core_animation_)
3935 return [super isOpaque];