Ignore non-active fullscreen windows for shelf state.
[chromium-blink-merge.git] / content / browser / renderer_host / render_widget_host_view_mac.mm
blobaae648c7ff9d9efd8c17a06216011c76efa40189
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/platform/WebScreenInfo.h"
53 #include "third_party/WebKit/public/web/WebInputEvent.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 blink::WebInputEvent;
80 using blink::WebInputEventFactory;
81 using blink::WebMouseEvent;
82 using blink::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;
95   }
96   if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
97           switches::kUseCoreAnimation) == "lazy") {
98     return CORE_ANIMATION_ENABLED_LAZY;
99   }
100   if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
101           switches::kUseCoreAnimation) == "disabled") {
102     return CORE_ANIMATION_DISABLED;
103   }
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;
111 - (BOOL)isSpeaking;
112 @end
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;
119 @end
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).
132 #endif  // 10.7
134 // This method will return YES for OS X versions 10.7.3 and later, and NO
135 // otherwise.
136 // Used to prevent a crash when building with the 10.7 SDK and accessing the
137 // notification below. See: http://crbug.com/260595.
138 static BOOL SupportsBackingPropertiesChangedNotification() {
139   // windowDidChangeBackingProperties: method has been added to the
140   // NSWindowDelegate protocol in 10.7.3, at the same time as the
141   // NSWindowDidChangeBackingPropertiesNotification notification was added.
142   // If the protocol contains this method description, the notification should
143   // be supported as well.
144   Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
145   struct objc_method_description methodDescription =
146       protocol_getMethodDescription(
147           windowDelegateProtocol,
148           @selector(windowDidChangeBackingProperties:),
149           NO,
150           YES);
152   // If the protocol does not contain the method, the returned method
153   // description is {NULL, NULL}
154   return methodDescription.name != NULL || methodDescription.types != NULL;
157 static float ScaleFactor(NSView* view) {
158   return ui::GetImageScale(ui::GetScaleFactorForNativeView(view));
161 // Private methods:
162 @interface RenderWidgetHostViewCocoa ()
163 @property(nonatomic, assign) NSRange selectedRange;
164 @property(nonatomic, assign) NSRange markedRange;
165 @property(nonatomic, assign)
166     NSObject<RenderWidgetHostViewMacDelegate>* delegate;
168 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
169 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
170 - (void)gotUnhandledWheelEvent;
171 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right;
172 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar;
173 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
174 - (void)windowDidChangeBackingProperties:(NSNotification*)notification;
175 - (void)windowChangedGlobalFrame:(NSNotification*)notification;
176 - (void)drawBackingStore:(BackingStoreMac*)backingStore
177                dirtyRect:(CGRect)dirtyRect
178                inContext:(CGContextRef)context;
179 - (void)updateSoftwareLayerScaleFactor;
180 - (void)checkForPluginImeCancellation;
181 - (void)updateTabBackingStoreScaleFactor;
182 @end
184 // A window subclass that allows the fullscreen window to become main and gain
185 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
186 // handled by the browser.
187 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
188 @end
190 @implementation PepperFlashFullscreenWindow
192 - (BOOL)canBecomeKeyWindow {
193   return YES;
196 - (BOOL)canBecomeMainWindow {
197   return YES;
200 @end
202 @interface RenderWidgetPopupWindow : NSWindow {
203    // The event tap that allows monitoring of all events, to properly close with
204    // a click outside the bounds of the window.
205   id clickEventTap_;
207 @end
209 @implementation RenderWidgetPopupWindow
211 - (id)initWithContentRect:(NSRect)contentRect
212                 styleMask:(NSUInteger)windowStyle
213                   backing:(NSBackingStoreType)bufferingType
214                     defer:(BOOL)deferCreation {
215   if (self = [super initWithContentRect:contentRect
216                               styleMask:windowStyle
217                                 backing:bufferingType
218                                   defer:deferCreation]) {
219     CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
220     [self setOpaque:NO];
221     [self setBackgroundColor:[NSColor clearColor]];
222     [self startObservingClicks];
223   }
224   return self;
227 - (void)close {
228   [self stopObservingClicks];
229   [super close];
232 // Gets called when the menubar is clicked.
233 // Needed because the local event monitor doesn't see the click on the menubar.
234 - (void)beganTracking:(NSNotification*)notification {
235   [self close];
238 // Install the callback.
239 - (void)startObservingClicks {
240   clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
241       handler:^NSEvent* (NSEvent* event) {
242           if ([event window] == self)
243             return event;
244           NSEventType eventType = [event type];
245           if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
246             [self close];
247           return event;
248   }];
250   NSNotificationCenter* notificationCenter =
251       [NSNotificationCenter defaultCenter];
252   [notificationCenter addObserver:self
253          selector:@selector(beganTracking:)
254              name:NSMenuDidBeginTrackingNotification
255            object:[NSApp mainMenu]];
258 // Remove the callback.
259 - (void)stopObservingClicks {
260   if (!clickEventTap_)
261     return;
263   [NSEvent removeMonitor:clickEventTap_];
264    clickEventTap_ = nil;
266   NSNotificationCenter* notificationCenter =
267       [NSNotificationCenter defaultCenter];
268   [notificationCenter removeObserver:self
269                 name:NSMenuDidBeginTrackingNotification
270               object:[NSApp mainMenu]];
273 @end
275 namespace {
277 // Maximum number of characters we allow in a tooltip.
278 const size_t kMaxTooltipLength = 1024;
280 // TODO(suzhe): Upstream this function.
281 blink::WebColor WebColorFromNSColor(NSColor *color) {
282   CGFloat r, g, b, a;
283   [color getRed:&r green:&g blue:&b alpha:&a];
285   return
286       std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
287       std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
288       std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8  |
289       std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
292 // Extract underline information from an attributed string. Mostly copied from
293 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
294 void ExtractUnderlines(
295     NSAttributedString* string,
296     std::vector<blink::WebCompositionUnderline>* underlines) {
297   int length = [[string string] length];
298   int i = 0;
299   while (i < length) {
300     NSRange range;
301     NSDictionary* attrs = [string attributesAtIndex:i
302                               longestEffectiveRange:&range
303                                             inRange:NSMakeRange(i, length - i)];
304     if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
305       blink::WebColor color = SK_ColorBLACK;
306       if (NSColor *colorAttr =
307           [attrs objectForKey:NSUnderlineColorAttributeName]) {
308         color = WebColorFromNSColor(
309             [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
310       }
311       underlines->push_back(blink::WebCompositionUnderline(
312           range.location, NSMaxRange(range), color, [style intValue] > 1));
313     }
314     i = range.location + range.length;
315   }
318 // EnablePasswordInput() and DisablePasswordInput() are copied from
319 // enableSecureTextInput() and disableSecureTextInput() functions in
320 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
321 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
322 // here, because they are already called in webkit and they are system wide
323 // functions.
324 void EnablePasswordInput() {
325   CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
326   TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
327                          sizeof(CFArrayRef), &inputSources);
328   CFRelease(inputSources);
331 void DisablePasswordInput() {
332   TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
335 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
336 // left of the primary screen (Carbon coordinates), and stuffs it into a
337 // gfx::Rect.
338 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
339   gfx::Rect new_rect(NSRectToCGRect(rect));
340   if ([[NSScreen screens] count] > 0) {
341     new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height -
342                    new_rect.y() - new_rect.height());
343   }
344   return new_rect;
347 // Returns the window that visually contains the given view. This is different
348 // from [view window] in the case of tab dragging, where the view's owning
349 // window is a floating panel attached to the actual browser window that the tab
350 // is visually part of.
351 NSWindow* ApparentWindowForView(NSView* view) {
352   // TODO(shess): In case of !window, the view has been removed from
353   // the view hierarchy because the tab isn't main.  Could retrieve
354   // the information from the main tab for our window.
355   NSWindow* enclosing_window = [view window];
357   // See if this is a tab drag window. The width check is to distinguish that
358   // case from extension popup windows.
359   NSWindow* ancestor_window = [enclosing_window parentWindow];
360   if (ancestor_window && (NSWidth([enclosing_window frame]) ==
361                           NSWidth([ancestor_window frame]))) {
362     enclosing_window = ancestor_window;
363   }
365   return enclosing_window;
368 blink::WebScreenInfo GetWebScreenInfo(NSView* view) {
369   gfx::Display display =
370       gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
372   NSScreen* screen = [NSScreen deepestScreen];
374   blink::WebScreenInfo results;
376   results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
377   results.depth = NSBitsPerPixelFromDepth([screen depth]);
378   results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
379   results.isMonochrome =
380       [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
381   results.rect = display.bounds();
382   results.availableRect = display.work_area();
383   return results;
386 }  // namespace
388 namespace content {
390 ///////////////////////////////////////////////////////////////////////////////
391 // RenderWidgetHostView, public:
393 // static
394 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
395     RenderWidgetHost* widget) {
396   return new RenderWidgetHostViewMac(widget);
399 // static
400 void RenderWidgetHostViewPort::GetDefaultScreenInfo(
401     blink::WebScreenInfo* results) {
402   *results = GetWebScreenInfo(NULL);
405 ///////////////////////////////////////////////////////////////////////////////
406 // RenderWidgetHostViewMac, public:
408 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
409     : render_widget_host_(RenderWidgetHostImpl::From(widget)),
410       about_to_validate_and_paint_(false),
411       call_set_needs_display_in_rect_pending_(false),
412       last_frame_was_accelerated_(false),
413       text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
414       can_compose_inline_(true),
415       allow_overlapping_views_(false),
416       use_core_animation_(false),
417       is_loading_(false),
418       weak_factory_(this),
419       fullscreen_parent_host_view_(NULL),
420       pending_swap_buffers_acks_weak_factory_(this),
421       underlay_view_has_drawn_(false),
422       overlay_view_weak_factory_(this),
423       next_swap_ack_time_(base::Time::Now()),
424       software_frame_weak_ptr_factory_(this) {
425   software_frame_manager_.reset(new SoftwareFrameManager(
426       software_frame_weak_ptr_factory_.GetWeakPtr()));
427   // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
428   // goes away.  Since we autorelease it, our caller must put
429   // |GetNativeView()| into the view hierarchy right after calling us.
430   cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
431                   initWithRenderWidgetHostViewMac:this] autorelease];
433   if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_ALWAYS) {
434     EnableCoreAnimation();
435   }
437   render_widget_host_->SetView(this);
440 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
441   // This is being called from |cocoa_view_|'s destructor, so invalidate the
442   // pointer.
443   cocoa_view_ = nil;
445   AckPendingSwapBuffers();
446   UnlockMouse();
448   // Make sure that the layer doesn't reach into the now-invalid object.
449   DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
450   software_layer_.reset();
452   // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
453   // RenderWidgetHost does we need to tell it not to hold a stale pointer to
454   // us.
455   if (render_widget_host_)
456     render_widget_host_->SetView(NULL);
459 void RenderWidgetHostViewMac::SetDelegate(
460     NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
461   [cocoa_view_ setDelegate:delegate];
464 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
465   if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY) {
466     if (overlapping) {
467       ScopedCAActionDisabler disabler;
468       EnableCoreAnimation();
469       return;
470     }
471   }
473   if (allow_overlapping_views_ == overlapping)
474     return;
475   allow_overlapping_views_ = overlapping;
476   [cocoa_view_ setNeedsDisplay:YES];
477   [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
480 ///////////////////////////////////////////////////////////////////////////////
481 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
483 void RenderWidgetHostViewMac::EnableCoreAnimation() {
484   if (use_core_animation_)
485     return;
487   use_core_animation_ = true;
489   // Un-bind the GL context from this view because the CoreAnimation path will
490   // not use explicit setView and clearDrawable calls.
491   ClearBoundContextDrawable();
493   software_layer_.reset([[CALayer alloc] init]);
494   if (!software_layer_)
495     LOG(ERROR) << "Failed to create CALayer for software rendering";
496   [software_layer_ setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
497   [software_layer_ setDelegate:cocoa_view_];
498   [software_layer_ setContentsGravity:kCAGravityTopLeft];
499   [software_layer_ setFrame:NSRectToCGRect([cocoa_view_ bounds])];
500   [software_layer_ setNeedsDisplay];
501   [cocoa_view_ updateSoftwareLayerScaleFactor];
503   [cocoa_view_ setLayer:software_layer_];
504   [cocoa_view_ setWantsLayer:YES];
506   if (compositing_iosurface_) {
507     if (!CreateCompositedIOSurfaceLayer()) {
508       LOG(ERROR) << "Failed to create CALayer for existing IOSurface";
509       GotAcceleratedCompositingError();
510       return;
511     }
512   }
515 bool RenderWidgetHostViewMac::CreateCompositedIOSurface() {
516   if (compositing_iosurface_context_ && compositing_iosurface_)
517     return true;
519   ScopedCAActionDisabler disabler;
521   // Create the GL context and shaders.
522   if (!compositing_iosurface_context_) {
523     compositing_iosurface_context_ =
524         CompositingIOSurfaceContext::Get(window_number());
525     if (!compositing_iosurface_context_) {
526       LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
527       return false;
528     }
529   }
530   // Create the IOSurface texture.
531   if (!compositing_iosurface_) {
532     compositing_iosurface_.reset(CompositingIOSurfaceMac::Create());
533     if (!compositing_iosurface_) {
534       LOG(ERROR) << "Failed to create CompositingIOSurface";
535       return false;
536     }
537   }
539   return true;
542 bool RenderWidgetHostViewMac::CreateCompositedIOSurfaceLayer() {
543   CHECK(compositing_iosurface_context_ && compositing_iosurface_);
544   if (compositing_iosurface_layer_ || !use_core_animation_)
545     return true;
547   ScopedCAActionDisabler disabler;
549   // Create the GL CoreAnimation layer.
550   if (!compositing_iosurface_layer_) {
551     compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
552         initWithRenderWidgetHostViewMac:this]);
553     if (!compositing_iosurface_layer_) {
554       LOG(ERROR) << "Failed to create CALayer for IOSurface";
555       return false;
556     }
557     [software_layer_ addSublayer:compositing_iosurface_layer_];
558   }
560   // Creating the CompositingIOSurfaceLayer may attempt to draw in setLayer,
561   // which, if it fails, will promptly tear down everything that was just
562   // created. If that happened, return failure.
563   return compositing_iosurface_context_ &&
564          compositing_iosurface_ &&
565          (compositing_iosurface_layer_ || !use_core_animation_);
568 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer(
569     DestroyContextBehavior destroy_context_behavior) {
570   ScopedCAActionDisabler disabler;
572   compositing_iosurface_.reset();
573   if (compositing_iosurface_layer_) {
574     [software_layer_ setNeedsDisplay];
575     [compositing_iosurface_layer_ removeFromSuperlayer];
576     [compositing_iosurface_layer_ disableCompositing];
577     compositing_iosurface_layer_.reset();
578   }
579   switch (destroy_context_behavior) {
580     case kLeaveContextBoundToView:
581       break;
582     case kDestroyContext:
583       ClearBoundContextDrawable();
584       compositing_iosurface_context_ = NULL;
585       break;
586     default:
587       NOTREACHED();
588       break;
589   }
592 void RenderWidgetHostViewMac::ClearBoundContextDrawable() {
593   if (compositing_iosurface_context_ &&
594       cocoa_view_ &&
595       [[compositing_iosurface_context_->nsgl_context() view]
596           isEqual:cocoa_view_]) {
597     [compositing_iosurface_context_->nsgl_context() clearDrawable];
598   }
601 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
602   bool handled = true;
603   IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
604     IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
605     IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
606     IPC_MESSAGE_UNHANDLED(handled = false)
607   IPC_END_MESSAGE_MAP()
608   return handled;
611 void RenderWidgetHostViewMac::InitAsChild(
612     gfx::NativeView parent_view) {
615 void RenderWidgetHostViewMac::InitAsPopup(
616     RenderWidgetHostView* parent_host_view,
617     const gfx::Rect& pos) {
618   bool activatable = popup_type_ == blink::WebPopupTypeNone;
619   [cocoa_view_ setCloseOnDeactivate:YES];
620   [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
622   NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
623   if ([[NSScreen screens] count] > 0) {
624     origin_global.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height -
625         pos.height() - origin_global.y;
626   }
628   popup_window_.reset([[RenderWidgetPopupWindow alloc]
629       initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
630                                      pos.width(), pos.height())
631                 styleMask:NSBorderlessWindowMask
632                   backing:NSBackingStoreBuffered
633                     defer:NO]);
634   [popup_window_ setLevel:NSPopUpMenuWindowLevel];
635   [popup_window_ setReleasedWhenClosed:NO];
636   [popup_window_ makeKeyAndOrderFront:nil];
637   [[popup_window_ contentView] addSubview:cocoa_view_];
638   [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
639   [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
640   [[NSNotificationCenter defaultCenter]
641       addObserver:cocoa_view_
642          selector:@selector(popupWindowWillClose:)
643              name:NSWindowWillCloseNotification
644            object:popup_window_];
647 // This function creates the fullscreen window and hides the dock and menubar if
648 // necessary. Note, this codepath is only used for pepper flash when
649 // pp::FlashFullScreen::SetFullscreen() is called. If
650 // pp::FullScreen::SetFullscreen() is called then the entire browser window
651 // will enter fullscreen instead.
652 void RenderWidgetHostViewMac::InitAsFullscreen(
653     RenderWidgetHostView* reference_host_view) {
654   fullscreen_parent_host_view_ =
655       static_cast<RenderWidgetHostViewMac*>(reference_host_view);
656   NSWindow* parent_window = nil;
657   if (reference_host_view)
658     parent_window = [reference_host_view->GetNativeView() window];
659   NSScreen* screen = [parent_window screen];
660   if (!screen)
661     screen = [NSScreen mainScreen];
663   pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
664       initWithContentRect:[screen frame]
665                 styleMask:NSBorderlessWindowMask
666                   backing:NSBackingStoreBuffered
667                     defer:NO]);
668   [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
669   [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
670   [cocoa_view_ setCanBeKeyView:YES];
671   [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
672   [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
673   // If the pepper fullscreen window isn't opaque then there are performance
674   // issues when it's on the discrete GPU and the Chrome window is being drawn
675   // to. http://crbug.com/171911
676   [pepper_fullscreen_window_ setOpaque:YES];
678   // Note that this forms a reference cycle between the fullscreen window and
679   // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
680   // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
681   // This cycle is normally broken when -keyEvent: receives an <esc> key, which
682   // explicitly calls Shutdown on the render_widget_host_, which calls
683   // Destroy() on RWHVMac, which drops the reference to
684   // pepper_fullscreen_window_.
685   [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
687   // Note that this keeps another reference to pepper_fullscreen_window_.
688   fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
689       initWithWindow:pepper_fullscreen_window_.get()
690        desiredScreen:screen]);
691   [fullscreen_window_manager_ enterFullscreenMode];
692   [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
695 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
696   // See comment in InitAsFullscreen(): There is a reference cycle between
697   // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
698   // Tests that test pepper fullscreen mode without sending an <esc> event
699   // need to call this method to break the reference cycle.
700   [fullscreen_window_manager_ exitFullscreenMode];
701   fullscreen_window_manager_.reset();
702   [pepper_fullscreen_window_ close];
703   pepper_fullscreen_window_.reset();
706 int RenderWidgetHostViewMac::window_number() const {
707   NSWindow* window = [cocoa_view_ window];
708   if (!window)
709     return -1;
710   return [window windowNumber];
713 float RenderWidgetHostViewMac::scale_factor() const {
714   return ScaleFactor(cocoa_view_);
717 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
718   return render_widget_host_;
721 void RenderWidgetHostViewMac::WasShown() {
722   if (!render_widget_host_->is_hidden())
723     return;
725   if (web_contents_switch_paint_time_.is_null())
726     web_contents_switch_paint_time_ = base::TimeTicks::Now();
727   render_widget_host_->WasShown();
728   software_frame_manager_->SetVisibility(true);
730   // We're messing with the window, so do this to ensure no flashes.
731   if (!use_core_animation_)
732     [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
734   [compositing_iosurface_layer_ setNeedsDisplay];
737 void RenderWidgetHostViewMac::WasHidden() {
738   if (render_widget_host_->is_hidden())
739     return;
741   // Send ACKs for any pending SwapBuffers (if any) since we won't be displaying
742   // them and the GPU process is waiting.
743   AckPendingSwapBuffers();
745   // If we have a renderer, then inform it that we are being hidden so it can
746   // reduce its resource utilization.
747   render_widget_host_->WasHidden();
748   software_frame_manager_->SetVisibility(false);
750   // There can be a transparent flash as this view is removed and the next is
751   // added, because of OSX windowing races between displaying the contents of
752   // the NSView and its corresponding OpenGL context.
753   // disableScreenUpdatesUntilFlush prevents the transparent flash by avoiding
754   // screen updates until the next tab draws.
755   if (!use_core_animation_)
756     [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
758   web_contents_switch_paint_time_ = base::TimeTicks();
761 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
762   gfx::Rect rect = GetViewBounds();
763   rect.set_size(size);
764   SetBounds(rect);
767 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
768   // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
769   // TODO(thakis): fix, http://crbug.com/73362
770   if (render_widget_host_->is_hidden())
771     return;
773   // During the initial creation of the RenderWidgetHostView in
774   // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
775   // an empty size. In the Windows code flow, it is not ignored because
776   // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
777   // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
778   // flags to keep things sized properly. On the other hand, if the size is not
779   // empty then this is a valid request for a pop-up.
780   if (rect.size().IsEmpty())
781     return;
783   // Ignore the position of |rect| for non-popup rwhvs. This is because
784   // background tabs do not have a window, but the window is required for the
785   // coordinate conversions. Popups are always for a visible tab.
786   if (IsPopup()) {
787     // The position of |rect| is screen coordinate system and we have to
788     // consider Cocoa coordinate system is upside-down and also multi-screen.
789     NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
790     NSSize size = NSMakeSize(rect.width(), rect.height());
791     size = [cocoa_view_ convertSize:size toView:nil];
792     if ([[NSScreen screens] count] > 0) {
793       NSScreen* screen =
794           static_cast<NSScreen*>([[NSScreen screens] objectAtIndex:0]);
795       origin_global.y =
796           NSHeight([screen frame]) - size.height - origin_global.y;
797     }
798     [popup_window_ setFrame:NSMakeRect(origin_global.x, origin_global.y,
799                                        size.width, size.height)
800                     display:YES];
801   } else {
802     DCHECK([[cocoa_view_ superview] isKindOfClass:[BaseView class]]);
803     BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
804     gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
805     rect2.set_width(rect.width());
806     rect2.set_height(rect.height());
807     [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
808   }
811 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
812   return cocoa_view_;
815 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
816   return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
819 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
820   NOTIMPLEMENTED();
821   return static_cast<gfx::NativeViewAccessible>(NULL);
824 void RenderWidgetHostViewMac::MovePluginWindows(
825     const gfx::Vector2d& scroll_offset,
826     const std::vector<WebPluginGeometry>& moves) {
827   // Must be overridden, but unused on this platform. Core Animation
828   // plugins are drawn by the GPU process (through the compositor),
829   // and Core Graphics plugins are drawn by the renderer process.
830   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
833 void RenderWidgetHostViewMac::Focus() {
834   [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
837 void RenderWidgetHostViewMac::Blur() {
838   UnlockMouse();
839   [[cocoa_view_ window] makeFirstResponder:nil];
842 bool RenderWidgetHostViewMac::HasFocus() const {
843   return [[cocoa_view_ window] firstResponder] == cocoa_view_;
846 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
847   return !!render_widget_host_->GetBackingStore(false) ||
848       (compositing_iosurface_ && compositing_iosurface_->HasIOSurface());
851 void RenderWidgetHostViewMac::Show() {
852   [cocoa_view_ setHidden:NO];
854   WasShown();
857 void RenderWidgetHostViewMac::Hide() {
858   // We're messing with the window, so do this to ensure no flashes.
859   if (!use_core_animation_)
860     [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
862   [cocoa_view_ setHidden:YES];
864   WasHidden();
867 bool RenderWidgetHostViewMac::IsShowing() {
868   return ![cocoa_view_ isHidden];
871 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
872   NSRect bounds = [cocoa_view_ bounds];
873   // TODO(shess): In case of !window, the view has been removed from
874   // the view hierarchy because the tab isn't main.  Could retrieve
875   // the information from the main tab for our window.
876   NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
877   if (!enclosing_window)
878     return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
880   bounds = [cocoa_view_ convertRect:bounds toView:nil];
881   bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
882   return FlipNSRectToRectScreen(bounds);
885 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
886   WebCursor web_cursor = cursor;
887   [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
890 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
891   is_loading_ = is_loading;
892   // If we ever decide to show the waiting cursor while the page is loading
893   // like Chrome does on Windows, call |UpdateCursor()| here.
896 void RenderWidgetHostViewMac::TextInputTypeChanged(
897     ui::TextInputType type,
898     ui::TextInputMode input_mode,
899     bool can_compose_inline) {
900   if (text_input_type_ != type
901       || can_compose_inline_ != can_compose_inline) {
902     text_input_type_ = type;
903     can_compose_inline_ = can_compose_inline;
904     if (HasFocus()) {
905       SetTextInputActive(true);
907       // Let AppKit cache the new input context to make IMEs happy.
908       // See http://crbug.com/73039.
909       [NSApp updateWindows];
911 #ifndef __LP64__
912       UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
913 #endif
914     }
915   }
918 void RenderWidgetHostViewMac::ImeCancelComposition() {
919   [cocoa_view_ cancelComposition];
922 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
923     const gfx::Range& range,
924     const std::vector<gfx::Rect>& character_bounds) {
925   // The RangeChanged message is only sent with valid values. The current
926   // caret position (start == end) will be sent if there is no IME range.
927   [cocoa_view_ setMarkedRange:range.ToNSRange()];
928   composition_range_ = range;
929   composition_bounds_ = character_bounds;
932 void RenderWidgetHostViewMac::DidUpdateBackingStore(
933     const gfx::Rect& scroll_rect,
934     const gfx::Vector2d& scroll_delta,
935     const std::vector<gfx::Rect>& copy_rects,
936     const ui::LatencyInfo& latency_info) {
937   GotSoftwareFrame();
939   software_latency_info_.MergeWith(latency_info);
941   if (render_widget_host_->is_hidden())
942     return;
944   std::vector<gfx::Rect> rects(copy_rects);
946   // Because the findbar might be open, we cannot use scrollRect:by: here. For
947   // now, simply mark all of scroll rect as dirty.
948   if (!scroll_rect.IsEmpty())
949     rects.push_back(scroll_rect);
951   for (size_t i = 0; i < rects.size(); ++i) {
952     NSRect ns_rect = [cocoa_view_ flipRectToNSRect:rects[i]];
954     if (about_to_validate_and_paint_) {
955       // As much as we'd like to use -setNeedsDisplayInRect: here, we can't.
956       // We're in the middle of executing a -drawRect:, and as soon as it
957       // returns Cocoa will clear its record of what needs display. We instead
958       // use |performSelector:| to call |setNeedsDisplayInRect:| after returning
959       //  to the main loop, at which point |drawRect:| is no longer on the
960       // stack.
961       DCHECK([NSThread isMainThread]);
962       if (!call_set_needs_display_in_rect_pending_) {
963         [cocoa_view_ performSelector:@selector(callSetNeedsDisplayInRect)
964                       withObject:nil
965                       afterDelay:0];
966         call_set_needs_display_in_rect_pending_ = true;
967         invalid_rect_ = ns_rect;
968       } else {
969         // The old invalid rect is probably invalid now, since the view has most
970         // likely been resized, but there's no harm in dirtying the union.  In
971         // the limit, this becomes equivalent to dirtying the whole view.
972         invalid_rect_ = NSUnionRect(invalid_rect_, ns_rect);
973       }
974     } else {
975       [cocoa_view_ setNeedsDisplayInRect:ns_rect];
976     }
977   }
979   if (!about_to_validate_and_paint_)
980     [cocoa_view_ displayIfNeeded];
983 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
984                                                 int error_code) {
985   Destroy();
988 void RenderWidgetHostViewMac::Destroy() {
989   AckPendingSwapBuffers();
991   [[NSNotificationCenter defaultCenter]
992       removeObserver:cocoa_view_
993                 name:NSWindowWillCloseNotification
994               object:popup_window_];
996   // We've been told to destroy.
997   [cocoa_view_ retain];
998   [cocoa_view_ removeFromSuperview];
999   [cocoa_view_ autorelease];
1001   [popup_window_ close];
1002   popup_window_.autorelease();
1004   [fullscreen_window_manager_ exitFullscreenMode];
1005   fullscreen_window_manager_.reset();
1006   [pepper_fullscreen_window_ close];
1008   // This can be called as part of processing the window's responder
1009   // chain, for instance |-performKeyEquivalent:|.  In that case the
1010   // object needs to survive until the stack unwinds.
1011   pepper_fullscreen_window_.autorelease();
1013   // We get this call just before |render_widget_host_| deletes
1014   // itself.  But we are owned by |cocoa_view_|, which may be retained
1015   // by some other code.  Examples are WebContentsViewMac's
1016   // |latent_focus_view_| and TabWindowController's
1017   // |cachedContentView_|.
1018   render_widget_host_ = NULL;
1021 // Called from the renderer to tell us what the tooltip text should be. It
1022 // calls us frequently so we need to cache the value to prevent doing a lot
1023 // of repeat work.
1024 void RenderWidgetHostViewMac::SetTooltipText(
1025     const base::string16& tooltip_text) {
1026   if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1027     tooltip_text_ = tooltip_text;
1029     // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1030     // Windows; we're just trying to be polite. Don't persist the trimmed
1031     // string, as then the comparison above will always fail and we'll try to
1032     // set it again every single time the mouse moves.
1033     base::string16 display_text = tooltip_text_;
1034     if (tooltip_text_.length() > kMaxTooltipLength)
1035       display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1037     NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1038     [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1039   }
1042 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1043   return [NSApp respondsToSelector:@selector(speakString:)] &&
1044          [NSApp respondsToSelector:@selector(stopSpeaking:)];
1047 void RenderWidgetHostViewMac::SpeakSelection() {
1048   if ([NSApp respondsToSelector:@selector(speakString:)])
1049     [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
1052 bool RenderWidgetHostViewMac::IsSpeaking() const {
1053   return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1054          [NSApp isSpeaking];
1057 void RenderWidgetHostViewMac::StopSpeaking() {
1058   if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1059     [NSApp stopSpeaking:cocoa_view_];
1063 // RenderWidgetHostViewCocoa uses the stored selection text,
1064 // which implements NSServicesRequests protocol.
1066 void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text,
1067                                                size_t offset,
1068                                                const gfx::Range& range) {
1069   if (range.is_empty() || text.empty()) {
1070     selected_text_.clear();
1071   } else {
1072     size_t pos = range.GetMin() - offset;
1073     size_t n = range.length();
1075     DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1076     if (pos >= text.length()) {
1077       DCHECK(false) << "The text can not cover range.";
1078       return;
1079     }
1080     selected_text_ = UTF16ToUTF8(text.substr(pos, n));
1081   }
1083   [cocoa_view_ setSelectedRange:range.ToNSRange()];
1084   // Updates markedRange when there is no marked text so that retrieving
1085   // markedRange immediately after calling setMarkdText: returns the current
1086   // caret position.
1087   if (![cocoa_view_ hasMarkedText]) {
1088     [cocoa_view_ setMarkedRange:range.ToNSRange()];
1089   }
1091   RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1094 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1095     const ViewHostMsg_SelectionBounds_Params& params) {
1096   if (params.anchor_rect == params.focus_rect)
1097     caret_rect_ = params.anchor_rect;
1100 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1103 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1104   RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1106   // Create a fake mouse event to inform the render widget that the mouse
1107   // left or entered.
1108   NSWindow* window = [cocoa_view_ window];
1109   // TODO(asvitkine): If the location outside of the event stream doesn't
1110   // correspond to the current event (due to delayed event processing), then
1111   // this may result in a cursor flicker if there are later mouse move events
1112   // in the pipeline. Find a way to use the mouse location from the event that
1113   // dismissed the context menu.
1114   NSPoint location = [window mouseLocationOutsideOfEventStream];
1115   NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1116                                       location:location
1117                                  modifierFlags:0
1118                                      timestamp:0
1119                                   windowNumber:window_number()
1120                                        context:nil
1121                                    eventNumber:0
1122                                     clickCount:0
1123                                       pressure:0];
1124   WebMouseEvent web_event =
1125       WebInputEventFactory::mouseEvent(event, cocoa_view_);
1126   if (showing)
1127     web_event.type = WebInputEvent::MouseLeave;
1128   ForwardMouseEvent(web_event);
1131 bool RenderWidgetHostViewMac::IsPopup() const {
1132   return popup_type_ != blink::WebPopupTypeNone;
1135 BackingStore* RenderWidgetHostViewMac::AllocBackingStore(
1136     const gfx::Size& size) {
1137   float scale = ScaleFactor(cocoa_view_);
1138   return new BackingStoreMac(render_widget_host_, size, scale);
1141 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1142     const gfx::Rect& src_subrect,
1143     const gfx::Size& dst_size,
1144     const base::Callback<void(bool, const SkBitmap&)>& callback) {
1145   base::ScopedClosureRunner scoped_callback_runner(
1146       base::Bind(callback, false, SkBitmap()));
1147   if (!compositing_iosurface_ ||
1148       !compositing_iosurface_->HasIOSurface())
1149     return;
1151   float scale = ScaleFactor(cocoa_view_);
1152   gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1153       gfx::ScaleSize(dst_size, scale));
1155   ignore_result(scoped_callback_runner.Release());
1157   compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1158                                  dst_pixel_size,
1159                                  callback);
1162 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1163       const gfx::Rect& src_subrect,
1164       const scoped_refptr<media::VideoFrame>& target,
1165       const base::Callback<void(bool)>& callback) {
1166   base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1167   if (!render_widget_host_->is_accelerated_compositing_active() ||
1168       !compositing_iosurface_ ||
1169       !compositing_iosurface_->HasIOSurface())
1170     return;
1172   if (!target.get()) {
1173     NOTREACHED();
1174     return;
1175   }
1177   if (target->format() != media::VideoFrame::YV12 &&
1178       target->format() != media::VideoFrame::I420) {
1179     NOTREACHED();
1180     return;
1181   }
1183   if (src_subrect.IsEmpty())
1184     return;
1186   ignore_result(scoped_callback_runner.Release());
1187   compositing_iosurface_->CopyToVideoFrame(
1188       GetScaledOpenGLPixelRect(src_subrect),
1189       target,
1190       callback);
1193 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1194   return (!render_widget_host_->GetBackingStore(false) &&
1195           render_widget_host_->is_accelerated_compositing_active() &&
1196           compositing_iosurface_ &&
1197           compositing_iosurface_->HasIOSurface());
1200 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1201   return true;
1204 void RenderWidgetHostViewMac::BeginFrameSubscription(
1205     scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1206   frame_subscriber_ = subscriber.Pass();
1209 void RenderWidgetHostViewMac::EndFrameSubscription() {
1210   frame_subscriber_.reset();
1213 // Sets whether or not to accept first responder status.
1214 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1215   [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1218 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1219   if (render_widget_host_)
1220     render_widget_host_->ForwardMouseEvent(event);
1222   if (event.type == WebInputEvent::MouseLeave) {
1223     [cocoa_view_ setToolTipAtMousePoint:nil];
1224     tooltip_text_.clear();
1225   }
1228 void RenderWidgetHostViewMac::KillSelf() {
1229   if (!weak_factory_.HasWeakPtrs()) {
1230     [cocoa_view_ setHidden:YES];
1231     base::MessageLoop::current()->PostTask(FROM_HERE,
1232         base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1233                    weak_factory_.GetWeakPtr()));
1234   }
1237 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1238     const NativeWebKeyboardEvent& event) {
1239   // Check WebInputEvent type since multiple types of events can be sent into
1240   // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1241   // necessary to avoid double processing.
1242   // Also check the native type, since NSFlagsChanged is considered a key event
1243   // for WebKit purposes, but isn't considered a key event by the OS.
1244   if (event.type == WebInputEvent::RawKeyDown &&
1245       [event.os_event type] == NSKeyDown)
1246     return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1247   return false;
1250 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1251     const base::string16& text, int plugin_id) {
1252   if (render_widget_host_) {
1253     render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1254         render_widget_host_->GetRoutingID(), text, plugin_id));
1255   }
1258 void RenderWidgetHostViewMac::CompositorSwapBuffers(
1259     uint64 surface_handle,
1260     const gfx::Size& size,
1261     float surface_scale_factor,
1262     const ui::LatencyInfo& latency_info) {
1263   if (render_widget_host_->is_hidden())
1264     return;
1266   NSWindow* window = [cocoa_view_ window];
1267   if (window_number() <= 0) {
1268     // There is no window to present so capturing during present won't work.
1269     // We check if frame subscriber wants this frame and capture manually.
1270     if (compositing_iosurface_ && frame_subscriber_) {
1271       const base::Time present_time = base::Time::Now();
1272       scoped_refptr<media::VideoFrame> frame;
1273       RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1274       if (frame_subscriber_->ShouldCaptureFrame(present_time,
1275                                                 &frame, &callback)) {
1276         compositing_iosurface_->SetIOSurface(
1277             surface_handle, size, surface_scale_factor, latency_info);
1278         compositing_iosurface_->CopyToVideoFrame(
1279             gfx::Rect(size), frame,
1280             base::Bind(callback, present_time));
1281         return;
1282       }
1283     }
1285     // TODO(shess) If the view does not have a window, or the window
1286     // does not have backing, the IOSurface will log "invalid drawable"
1287     // in -setView:.  It is not clear how this code is reached with such
1288     // a case, so record some info into breakpad (some subset of
1289     // browsers are likely to crash later for unrelated reasons).
1290     // http://crbug.com/148882
1291     const char* const kCrashKey = "rwhvm_window";
1292     if (!window) {
1293       base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1294     } else {
1295       std::string value =
1296           base::StringPrintf("window %s delegate %s controller %s",
1297               object_getClassName(window),
1298               object_getClassName([window delegate]),
1299               object_getClassName([window windowController]));
1300       base::debug::SetCrashKeyValue(kCrashKey, value);
1301     }
1303     return;
1304   }
1306   if (!CreateCompositedIOSurface()) {
1307     LOG(ERROR) << "Failed to create CompositingIOSurface";
1308     GotAcceleratedCompositingError();
1309     return;
1310   }
1312   if (!compositing_iosurface_->SetIOSurface(
1313           surface_handle, size, surface_scale_factor, latency_info)) {
1314     LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1315     GotAcceleratedCompositingError();
1316     return;
1317   }
1319   // Create the layer for the composited content only after the IOSurface has
1320   // been initialized.
1321   if (!CreateCompositedIOSurfaceLayer()) {
1322     LOG(ERROR) << "Failed to create CompositingIOSurface layer";
1323     GotAcceleratedCompositingError();
1324     return;
1325   }
1327   GotAcceleratedFrame();
1329   gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1330   if (window_size.IsEmpty()) {
1331     // setNeedsDisplay will never display and we'll never ack if the window is
1332     // empty, so ack now and don't bother calling setNeedsDisplay below.
1333     return;
1334   }
1336   // No need to draw the surface if we are inside a drawRect. It will be done
1337   // later.
1338   if (!about_to_validate_and_paint_) {
1339     if (use_core_animation_) {
1340       DCHECK(compositing_iosurface_layer_);
1341       [compositing_iosurface_layer_ setNeedsDisplay];
1342     } else {
1343       if (!DrawIOSurfaceWithoutCoreAnimation()) {
1344         [cocoa_view_ setNeedsDisplay:YES];
1345         GotAcceleratedCompositingError();
1346         return;
1347       }
1348     }
1349   }
1352 void RenderWidgetHostViewMac::AckPendingSwapBuffers() {
1353   TRACE_EVENT0("browser", "RenderWidgetHostViewMac::AckPendingSwapBuffers");
1355   // Cancel any outstanding delayed calls to this function.
1356   pending_swap_buffers_acks_weak_factory_.InvalidateWeakPtrs();
1358   while (!pending_swap_buffers_acks_.empty()) {
1359     if (pending_swap_buffers_acks_.front().first != 0) {
1360       AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
1361       ack_params.sync_point = 0;
1362       if (compositing_iosurface_)
1363         ack_params.renderer_id = compositing_iosurface_->GetRendererID();
1364       RenderWidgetHostImpl::AcknowledgeBufferPresent(
1365           pending_swap_buffers_acks_.front().first,
1366           pending_swap_buffers_acks_.front().second,
1367           ack_params);
1368       if (render_widget_host_) {
1369         render_widget_host_->AcknowledgeSwapBuffersToRenderer();
1370       }
1371     }
1372     pending_swap_buffers_acks_.erase(pending_swap_buffers_acks_.begin());
1373   }
1376 void RenderWidgetHostViewMac::ThrottledAckPendingSwapBuffers() {
1377   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1379   // Send VSync parameters to the renderer's compositor thread.
1380   base::TimeTicks vsync_timebase;
1381   base::TimeDelta vsync_interval;
1382   GetVSyncParameters(&vsync_timebase, &vsync_interval);
1383   if (render_widget_host_ && compositing_iosurface_)
1384     render_widget_host_->UpdateVSyncParameters(vsync_timebase, vsync_interval);
1386   // If the render widget host is responsible for throttling swaps to vsync rate
1387   // then don't ack the swapbuffers until a full vsync has passed since the last
1388   // ack was sent.
1389   bool throttle_swap_ack =
1390       render_widget_host_ &&
1391       !render_widget_host_->is_threaded_compositing_enabled() &&
1392       compositing_iosurface_context_ &&
1393       !compositing_iosurface_context_->is_vsync_disabled();
1394   base::Time now = base::Time::Now();
1395   if (throttle_swap_ack && next_swap_ack_time_ > now) {
1396     base::TimeDelta next_swap_ack_delay = next_swap_ack_time_ - now;
1397     next_swap_ack_time_ += vsync_interval;
1398     base::MessageLoop::current()->PostDelayedTask(
1399         FROM_HERE,
1400         base::Bind(&RenderWidgetHostViewMac::AckPendingSwapBuffers,
1401                    pending_swap_buffers_acks_weak_factory_.GetWeakPtr()),
1402         next_swap_ack_delay);
1403   } else {
1404     next_swap_ack_time_ = now + vsync_interval;
1405     AckPendingSwapBuffers();
1406   }
1409 bool RenderWidgetHostViewMac::DrawIOSurfaceWithoutCoreAnimation() {
1410   CHECK(!use_core_animation_);
1411   CHECK(compositing_iosurface_);
1413   GLint old_gl_surface_order = 0;
1414   GLint new_gl_surface_order = allow_overlapping_views_ ? -1 : 1;
1415   [compositing_iosurface_context_->nsgl_context()
1416       getValues:&old_gl_surface_order
1417       forParameter:NSOpenGLCPSurfaceOrder];
1418   if (old_gl_surface_order != new_gl_surface_order) {
1419     [compositing_iosurface_context_->nsgl_context()
1420         setValues:&new_gl_surface_order
1421         forParameter:NSOpenGLCPSurfaceOrder];
1422   }
1424   // Instead of drawing, request that underlay view redraws.
1425   if (underlay_view_ &&
1426       underlay_view_->compositing_iosurface_ &&
1427       underlay_view_has_drawn_) {
1428     [underlay_view_->cocoa_view() setNeedsDisplay:YES];
1429     return true;
1430   }
1432   CGLError cgl_error = CGLSetCurrentContext(
1433       compositing_iosurface_context_->cgl_context());
1434   if (cgl_error != kCGLNoError) {
1435     LOG(ERROR) << "CGLSetCurrentContext error in DrawIOSurface: " << cgl_error;
1436     return false;
1437   }
1439   [compositing_iosurface_context_->nsgl_context() setView:cocoa_view_];
1440   bool has_overlay = overlay_view_ && overlay_view_->compositing_iosurface_;
1442   gfx::Rect view_rect(NSRectToCGRect([cocoa_view_ frame]));
1443   if (!compositing_iosurface_->DrawIOSurface(
1444       compositing_iosurface_context_,
1445       view_rect,
1446       scale_factor(),
1447       frame_subscriber(),
1448       !has_overlay)) {
1449     return false;
1450   }
1452   if (has_overlay) {
1453     overlay_view_->underlay_view_has_drawn_ = true;
1454     gfx::Rect overlay_view_rect(
1455         NSRectToCGRect([overlay_view_->cocoa_view() frame]));
1456     overlay_view_rect.set_x(overlay_view_offset_.x());
1457     overlay_view_rect.set_y(view_rect.height() -
1458                             overlay_view_rect.height() -
1459                             overlay_view_offset_.y());
1460     return overlay_view_->compositing_iosurface_->DrawIOSurface(
1461         compositing_iosurface_context_,
1462         overlay_view_rect,
1463         overlay_view_->scale_factor(),
1464         overlay_view_->frame_subscriber(),
1465         true);
1466   }
1468   return true;
1471 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1472   AckPendingSwapBuffers();
1473   DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1474   // The existing GL contexts may be in a bad state, so don't re-use any of the
1475   // existing ones anymore, rather, allocate new ones.
1476   CompositingIOSurfaceContext::MarkExistingContextsAsNotShareable();
1477   // Request that a new frame be generated.
1478   if (render_widget_host_)
1479     render_widget_host_->ScheduleComposite();
1480   // TODO(ccameron): It may be a good idea to request that the renderer recreate
1481   // its GL context as well, and fall back to software if this happens
1482   // repeatedly.
1485 void RenderWidgetHostViewMac::SetOverlayView(
1486     RenderWidgetHostViewMac* overlay, const gfx::Point& offset) {
1487   if (overlay_view_)
1488     overlay_view_->underlay_view_.reset();
1490   overlay_view_ = overlay->overlay_view_weak_factory_.GetWeakPtr();
1491   overlay_view_offset_ = offset;
1492   overlay_view_->underlay_view_ = overlay_view_weak_factory_.GetWeakPtr();
1493   overlay_view_->underlay_view_has_drawn_ = false;
1495   [cocoa_view_ setNeedsDisplay:YES];
1496   [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1499 void RenderWidgetHostViewMac::RemoveOverlayView() {
1500   if (overlay_view_) {
1501     overlay_view_->underlay_view_.reset();
1502     overlay_view_.reset();
1503   }
1504   [cocoa_view_ setNeedsDisplay:YES];
1505   [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1508 void RenderWidgetHostViewMac::GetVSyncParameters(
1509   base::TimeTicks* timebase, base::TimeDelta* interval) {
1510   if (compositing_iosurface_) {
1511     uint32 numerator = 0;
1512     uint32 denominator = 0;
1513     compositing_iosurface_->GetVSyncParameters(
1514         timebase, &numerator, &denominator);
1515     if (numerator > 0 && denominator > 0) {
1516       int64 interval_micros =
1517           1000000 * static_cast<int64>(numerator) / denominator;
1518       *interval = base::TimeDelta::FromMicroseconds(interval_micros);
1519       return;
1520     }
1521   }
1523   // Pass reasonable default values if unable to get the actual ones
1524   // (e.g. CVDisplayLink failed to return them because the display is
1525   // in sleep mode).
1526   static const int64 kOneOverSixtyMicroseconds = 16669;
1527   *timebase = base::TimeTicks::Now(),
1528   *interval = base::TimeDelta::FromMicroseconds(kOneOverSixtyMicroseconds);
1531 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1532     const std::vector<gfx::Rect>& bounds,
1533     const gfx::Range& range,
1534     size_t* line_break_point) {
1535   DCHECK(line_break_point);
1536   if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1537     return false;
1539   // We can't check line breaking completely from only rectangle array. Thus we
1540   // assume the line breaking as the next character's y offset is larger than
1541   // a threshold. Currently the threshold is determined as minimum y offset plus
1542   // 75% of maximum height.
1543   // TODO(nona): Check the threshold is reliable or not.
1544   // TODO(nona): Bidi support.
1545   const size_t loop_end_idx = std::min(bounds.size(), range.end());
1546   int max_height = 0;
1547   int min_y_offset = kint32max;
1548   for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1549     max_height = std::max(max_height, bounds[idx].height());
1550     min_y_offset = std::min(min_y_offset, bounds[idx].y());
1551   }
1552   int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1553   for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1554     if (bounds[idx].y() > line_break_threshold) {
1555       *line_break_point = idx;
1556       return true;
1557     }
1558   }
1559   return false;
1562 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1563     const gfx::Range& range,
1564     gfx::Range* actual_range) {
1565   DCHECK(actual_range);
1566   DCHECK(!composition_bounds_.empty());
1567   DCHECK(range.start() <= composition_bounds_.size());
1568   DCHECK(range.end() <= composition_bounds_.size());
1570   if (range.is_empty()) {
1571     *actual_range = range;
1572     if (range.start() == composition_bounds_.size()) {
1573       return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1574                        composition_bounds_[range.start() - 1].y(),
1575                        0,
1576                        composition_bounds_[range.start() - 1].height());
1577     } else {
1578       return gfx::Rect(composition_bounds_[range.start()].x(),
1579                        composition_bounds_[range.start()].y(),
1580                        0,
1581                        composition_bounds_[range.start()].height());
1582     }
1583   }
1585   size_t end_idx;
1586   if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1587     end_idx = range.end();
1588   }
1589   *actual_range = gfx::Range(range.start(), end_idx);
1590   gfx::Rect rect = composition_bounds_[range.start()];
1591   for (size_t i = range.start() + 1; i < end_idx; ++i) {
1592     rect.Union(composition_bounds_[i]);
1593   }
1594   return rect;
1597 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1598     const gfx::Range& request_range) {
1599   if (composition_range_.is_empty())
1600     return gfx::Range::InvalidRange();
1602   if (request_range.is_reversed())
1603     return gfx::Range::InvalidRange();
1605   if (request_range.start() < composition_range_.start() ||
1606       request_range.start() > composition_range_.end() ||
1607       request_range.end() > composition_range_.end()) {
1608     return gfx::Range::InvalidRange();
1609   }
1611   return gfx::Range(
1612       request_range.start() - composition_range_.start(),
1613       request_range.end() - composition_range_.start());
1616 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1617     NSRange range,
1618     NSRect* rect,
1619     NSRange* actual_range) {
1620   DCHECK(rect);
1621   // This exists to make IMEs more responsive, see http://crbug.com/115920
1622   TRACE_EVENT0("browser",
1623                "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1625   // If requested range is same as caret location, we can just return it.
1626   if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1627     if (actual_range)
1628       *actual_range = range;
1629     *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1630     return true;
1631   }
1633   const gfx::Range request_range_in_composition =
1634       ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1635   if (request_range_in_composition == gfx::Range::InvalidRange())
1636     return false;
1638   // If firstRectForCharacterRange in WebFrame is failed in renderer,
1639   // ImeCompositionRangeChanged will be sent with empty vector.
1640   if (composition_bounds_.empty())
1641     return false;
1642   DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1644   gfx::Range ui_actual_range;
1645   *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1646                                request_range_in_composition,
1647                                &ui_actual_range).ToCGRect());
1648   if (actual_range) {
1649     *actual_range = gfx::Range(
1650         composition_range_.start() + ui_actual_range.start(),
1651         composition_range_.start() + ui_actual_range.end()).ToNSRange();
1652   }
1653   return true;
1656 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1657     const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1658     int gpu_host_id) {
1659   TRACE_EVENT0("browser",
1660       "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1661   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1663   pending_swap_buffers_acks_.push_back(std::make_pair(params.route_id,
1664                                                       gpu_host_id));
1666   CompositorSwapBuffers(params.surface_handle,
1667                         params.size,
1668                         params.scale_factor,
1669                         params.latency_info);
1671   ThrottledAckPendingSwapBuffers();
1674 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1675     const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1676     int gpu_host_id) {
1677   TRACE_EVENT0("browser",
1678       "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1679   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1681   pending_swap_buffers_acks_.push_back(std::make_pair(params.route_id,
1682                                                       gpu_host_id));
1684   CompositorSwapBuffers(params.surface_handle,
1685                         params.surface_size,
1686                         params.surface_scale_factor,
1687                         params.latency_info);
1689   ThrottledAckPendingSwapBuffers();
1692 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1693   if (compositing_iosurface_)
1694     compositing_iosurface_->UnrefIOSurface();
1697 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1698   DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1701 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1702       const gfx::Size& desired_size) {
1703   if (last_frame_was_accelerated_) {
1704     return compositing_iosurface_ &&
1705            compositing_iosurface_->HasIOSurface() &&
1706            (desired_size.IsEmpty() ||
1707                compositing_iosurface_->dip_io_surface_size() == desired_size);
1708   } else {
1709     return (software_frame_manager_->HasCurrentFrame() &&
1710            (desired_size.IsEmpty() ||
1711                software_frame_manager_->GetCurrentFrameSizeInDIP() ==
1712                    desired_size));
1713   }
1714   return false;
1717 void RenderWidgetHostViewMac::AboutToWaitForBackingStoreMsg() {
1718   AckPendingSwapBuffers();
1721 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1722     uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1723   // Only software compositor frames are accepted.
1724   if (!frame->software_frame_data) {
1725     DLOG(ERROR) << "Received unexpected frame type.";
1726     RecordAction(
1727         UserMetricsAction("BadMessageTerminate_UnexpectedFrameType"));
1728     render_widget_host_->GetProcess()->ReceivedBadMessage();
1729     return;
1730   }
1732   GotSoftwareFrame();
1733   if (!software_frame_manager_->SwapToNewFrame(
1734           output_surface_id,
1735           frame->software_frame_data.get(),
1736           frame->metadata.device_scale_factor,
1737           render_widget_host_->GetProcess()->GetHandle())) {
1738     render_widget_host_->GetProcess()->ReceivedBadMessage();
1739     return;
1740   }
1741   software_frame_manager_->SwapToNewFrameComplete(
1742       !render_widget_host_->is_hidden());
1744   cc::CompositorFrameAck ack;
1745   RenderWidgetHostImpl::SendSwapCompositorFrameAck(
1746       render_widget_host_->GetRoutingID(),
1747       output_surface_id,
1748       render_widget_host_->GetProcess()->GetID(),
1749       ack);
1751   [cocoa_view_ setNeedsDisplay:YES];
1754 void RenderWidgetHostViewMac::OnAcceleratedCompositingStateChange() {
1757 void RenderWidgetHostViewMac::AcceleratedSurfaceInitialized(int host_id,
1758                                                             int route_id) {
1761 void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) {
1762   *results = GetWebScreenInfo(GetNativeView());
1765 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1766   // TODO(shess): In case of !window, the view has been removed from
1767   // the view hierarchy because the tab isn't main.  Could retrieve
1768   // the information from the main tab for our window.
1769   NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1770   if (!enclosing_window)
1771     return gfx::Rect();
1773   NSRect bounds = [enclosing_window frame];
1774   return FlipNSRectToRectScreen(bounds);
1777 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1778   // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1779   // completely on Mac OS.
1780   return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
1783 void RenderWidgetHostViewMac::SetHasHorizontalScrollbar(
1784     bool has_horizontal_scrollbar) {
1785   [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
1788 void RenderWidgetHostViewMac::SetScrollOffsetPinning(
1789     bool is_pinned_to_left, bool is_pinned_to_right) {
1790   [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
1791                                 toRight:is_pinned_to_right];
1794 bool RenderWidgetHostViewMac::LockMouse() {
1795   if (mouse_locked_)
1796     return true;
1798   mouse_locked_ = true;
1800   // Lock position of mouse cursor and hide it.
1801   CGAssociateMouseAndMouseCursorPosition(NO);
1802   [NSCursor hide];
1804   // Clear the tooltip window.
1805   SetTooltipText(base::string16());
1807   return true;
1810 void RenderWidgetHostViewMac::UnlockMouse() {
1811   if (!mouse_locked_)
1812     return;
1813   mouse_locked_ = false;
1815   // Unlock position of mouse cursor and unhide it.
1816   CGAssociateMouseAndMouseCursorPosition(YES);
1817   [NSCursor unhide];
1819   if (render_widget_host_)
1820     render_widget_host_->LostMouseLock();
1823 void RenderWidgetHostViewMac::UnhandledWheelEvent(
1824     const blink::WebMouseWheelEvent& event) {
1825   // Only record a wheel event as unhandled if JavaScript handlers got a chance
1826   // to see it (no-op wheel events are ignored by the event dispatcher)
1827   if (event.deltaX || event.deltaY)
1828     [cocoa_view_ gotUnhandledWheelEvent];
1831 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
1832   if (render_widget_host_)
1833     return render_widget_host_->Send(message);
1834   delete message;
1835   return false;
1838 void RenderWidgetHostViewMac::SoftwareFrameWasFreed(
1839     uint32 output_surface_id, unsigned frame_id) {
1840   cc::CompositorFrameAck ack;
1841   ack.last_software_frame_id = frame_id;
1842   RenderWidgetHostImpl::SendReclaimCompositorResources(
1843       render_widget_host_->GetRoutingID(),
1844       output_surface_id,
1845       render_widget_host_->GetProcess()->GetID(),
1846       ack);
1849 void RenderWidgetHostViewMac::ReleaseReferencesToSoftwareFrame() {
1852 void RenderWidgetHostViewMac::ShutdownHost() {
1853   weak_factory_.InvalidateWeakPtrs();
1854   render_widget_host_->Shutdown();
1855   // Do not touch any members at this point, |this| has been deleted.
1858 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
1859   // Update the scale factor of the layer to match the scale factor of the
1860   // IOSurface.
1861   [compositing_iosurface_layer_ updateScaleFactor];
1863   if (!last_frame_was_accelerated_) {
1864     last_frame_was_accelerated_ = true;
1866     if (!use_core_animation_) {
1867       // Need to wipe the software view with transparency to expose the GL
1868       // underlay. Invalidate the whole window to do that.
1869       [cocoa_view_ setNeedsDisplay:YES];
1870     }
1872     // Delete software backingstore.
1873     BackingStoreManager::RemoveBackingStore(render_widget_host_);
1874     software_frame_manager_->DiscardCurrentFrame();
1875   }
1878 void RenderWidgetHostViewMac::GotSoftwareFrame() {
1879   if (last_frame_was_accelerated_) {
1880     last_frame_was_accelerated_ = false;
1882     AckPendingSwapBuffers();
1884     // If overlapping views are allowed, then don't unbind the context
1885     // from the view (that is, don't call clearDrawble -- just delete the
1886     // texture and IOSurface). Rather, let it sit behind the software frame
1887     // that will be put up in front. This will prevent transparent
1888     // flashes.
1889     // http://crbug.com/154531
1890     // Also note that it is necessary that clearDrawable be called if
1891     // overlapping views are not allowed, e.g, for content shell.
1892     // http://crbug.com/178408
1893     // Disable screen updates so that the changes of flashes is minimized.
1894     // http://crbug.com/279472
1895     if (!use_core_animation_)
1896       [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1897     if (allow_overlapping_views_)
1898       DestroyCompositedIOSurfaceAndLayer(kLeaveContextBoundToView);
1899     else
1900       DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1901   }
1904 void RenderWidgetHostViewMac::SetActive(bool active) {
1905   if (render_widget_host_) {
1906     render_widget_host_->SetActive(active);
1907     if (active) {
1908       if (HasFocus())
1909         render_widget_host_->Focus();
1910     } else {
1911       render_widget_host_->Blur();
1912     }
1913   }
1914   if (HasFocus())
1915     SetTextInputActive(active);
1916   if (!active) {
1917     [cocoa_view_ setPluginImeActive:NO];
1918     UnlockMouse();
1919   }
1922 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
1923   if (render_widget_host_) {
1924     render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
1925         render_widget_host_->GetRoutingID(), visible));
1926   }
1929 void RenderWidgetHostViewMac::WindowFrameChanged() {
1930   if (render_widget_host_) {
1931     render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
1932         render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
1933         GetViewBounds()));
1934   }
1936   if (compositing_iosurface_ && use_core_animation_) {
1937     scoped_refptr<CompositingIOSurfaceContext> new_context =
1938         CompositingIOSurfaceContext::Get(window_number());
1939     if (new_context) {
1940       // Un-bind the GL context from this view before binding the new GL
1941       // context. Having two GL contexts bound to a view will result in
1942       // crashes and corruption.
1943       // http://crbug.com/230883
1944       ClearBoundContextDrawable();
1945       compositing_iosurface_context_ = new_context;
1946     }
1947   }
1950 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
1951   RenderWidgetHostViewMacDictionaryHelper helper(this);
1952   helper.ShowDefinitionForSelection();
1955 void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) {
1956   RenderWidgetHostViewBase::SetBackground(background);
1957   if (render_widget_host_)
1958     render_widget_host_->Send(new ViewMsg_SetBackground(
1959         render_widget_host_->GetRoutingID(), background));
1962 void RenderWidgetHostViewMac::OnAccessibilityEvents(
1963     const std::vector<AccessibilityHostMsg_EventParams>& params) {
1964   if (!GetBrowserAccessibilityManager()) {
1965     SetBrowserAccessibilityManager(
1966         new BrowserAccessibilityManagerMac(
1967             cocoa_view_,
1968             BrowserAccessibilityManagerMac::GetEmptyDocument(),
1969             NULL));
1970   }
1971   GetBrowserAccessibilityManager()->OnAccessibilityEvents(params);
1974 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
1975   if (active) {
1976     if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1977       EnablePasswordInput();
1978     else
1979       DisablePasswordInput();
1980   } else {
1981     if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1982       DisablePasswordInput();
1983   }
1986 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
1987                                                    int plugin_id) {
1988   [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
1991 void RenderWidgetHostViewMac::OnStartPluginIme() {
1992   [cocoa_view_ setPluginImeActive:YES];
1995 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
1996     const gfx::Rect& rect) {
1997   gfx::Rect src_gl_subrect = rect;
1998   src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
2000   return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
2001                                              scale_factor()));
2004 void RenderWidgetHostViewMac::FrameSwapped() {
2005   software_latency_info_.AddLatencyNumber(
2006       ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
2007   render_widget_host_->FrameSwapped(software_latency_info_);
2008   software_latency_info_.Clear();
2011 }  // namespace content
2013 // RenderWidgetHostViewCocoa ---------------------------------------------------
2015 @implementation RenderWidgetHostViewCocoa
2017 @synthesize selectedRange = selectedRange_;
2018 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
2019 @synthesize markedRange = markedRange_;
2020 @synthesize delegate = delegate_;
2022 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
2023   self = [super initWithFrame:NSZeroRect];
2024   if (self) {
2025     editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
2026     editCommand_helper_->AddEditingSelectorsToClass([self class]);
2028     renderWidgetHostView_.reset(r);
2029     canBeKeyView_ = YES;
2030     focusedPluginIdentifier_ = -1;
2031     deviceScaleFactor_ = ScaleFactor(self);
2033     // OpenGL support:
2034     if ([self respondsToSelector:
2035         @selector(setWantsBestResolutionOpenGLSurface:)]) {
2036       [self setWantsBestResolutionOpenGLSurface:YES];
2037     }
2038     handlingGlobalFrameDidChange_ = NO;
2039     [[NSNotificationCenter defaultCenter]
2040         addObserver:self
2041            selector:@selector(globalFrameDidChange:)
2042                name:NSViewGlobalFrameDidChangeNotification
2043              object:self];
2044   }
2045   return self;
2048 - (void)dealloc {
2049   // Unbind the GL context from this view. If this is not done before super's
2050   // dealloc is called then the GL context will crash when it reaches into
2051   // the view in its destructor.
2052   // http://crbug.com/255608
2053   if (renderWidgetHostView_)
2054     renderWidgetHostView_->AcceleratedSurfaceRelease();
2056   if (delegate_ && [delegate_ respondsToSelector:@selector(viewGone:)])
2057     [delegate_ viewGone:self];
2058   [[NSNotificationCenter defaultCenter] removeObserver:self];
2060   [super dealloc];
2063 - (void)resetCursorRects {
2064   if (currentCursor_) {
2065     [self addCursorRect:[self visibleRect] cursor:currentCursor_];
2066     [currentCursor_ setOnMouseEntered:YES];
2067   }
2070 - (void)gotUnhandledWheelEvent {
2071   if (delegate_ &&
2072       [delegate_ respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
2073     [delegate_ gotUnhandledWheelEvent];
2074   }
2077 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
2078   if (delegate_ && [delegate_ respondsToSelector:
2079       @selector(scrollOffsetPinnedToLeft:toRight:)]) {
2080     [delegate_ scrollOffsetPinnedToLeft:left toRight:right];
2081   }
2084 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
2085   if (delegate_ &&
2086       [delegate_ respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
2087     [delegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
2088   }
2091 - (BOOL)respondsToSelector:(SEL)selector {
2092   // Trickiness: this doesn't mean "does this object's superclass respond to
2093   // this selector" but rather "does the -respondsToSelector impl from the
2094   // superclass say that this class responds to the selector".
2095   if ([super respondsToSelector:selector])
2096     return YES;
2098   if (delegate_)
2099     return [delegate_ respondsToSelector:selector];
2101   return NO;
2104 - (id)forwardingTargetForSelector:(SEL)selector {
2105   if ([delegate_ respondsToSelector:selector])
2106     return delegate_;
2108   return [super forwardingTargetForSelector:selector];
2111 - (void)setCanBeKeyView:(BOOL)can {
2112   canBeKeyView_ = can;
2115 - (BOOL)acceptsMouseEventsWhenInactive {
2116   // Some types of windows (balloons, always-on-top panels) want to accept mouse
2117   // clicks w/o the first click being treated as 'activation'. Same applies to
2118   // mouse move events.
2119   return [[self window] level] > NSNormalWindowLevel;
2122 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
2123   return [self acceptsMouseEventsWhenInactive];
2126 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
2127   takesFocusOnlyOnMouseDown_ = b;
2130 - (void)setCloseOnDeactivate:(BOOL)b {
2131   closeOnDeactivate_ = b;
2134 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
2135   NSWindow* window = [self window];
2136   // If this is a background window, don't handle mouse movement events. This
2137   // is the expected behavior on the Mac as evidenced by other applications.
2138   if ([theEvent type] == NSMouseMoved &&
2139       ![self acceptsMouseEventsWhenInactive] &&
2140       ![window isKeyWindow]) {
2141     return YES;
2142   }
2144   // Use hitTest to check whether the mouse is over a nonWebContentView - in
2145   // which case the mouse event should not be handled by the render host.
2146   const SEL nonWebContentViewSelector = @selector(nonWebContentView);
2147   NSView* contentView = [window contentView];
2148   NSView* view = [contentView hitTest:[theEvent locationInWindow]];
2149   // Traverse the superview hierarchy as the hitTest will return the frontmost
2150   // view, such as an NSTextView, while nonWebContentView may be specified by
2151   // its parent view.
2152   while (view) {
2153     if ([view respondsToSelector:nonWebContentViewSelector] &&
2154         [view performSelector:nonWebContentViewSelector]) {
2155       // The cursor is over a nonWebContentView - ignore this mouse event.
2156       return YES;
2157     }
2158     if ([view isKindOfClass:[self class]] && ![view isEqual:self] &&
2159         !hasOpenMouseDown_) {
2160       // The cursor is over an overlapping render widget. This check is done by
2161       // both views so the one that's returned by -hitTest: will end up
2162       // processing the event.
2163       // Note that while dragging, we only get events for the render view where
2164       // drag started, even if mouse is  actually over another view or outside
2165       // the window. Cocoa does this for us. We should handle these events and
2166       // not ignore (since there is no other render view to handle them). Thus
2167       // the |!hasOpenMouseDown_| check above.
2168       return YES;
2169     }
2170     view = [view superview];
2171   }
2172   return NO;
2175 - (void)mouseEvent:(NSEvent*)theEvent {
2176   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
2177   if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2178     BOOL handled = [delegate_ handleEvent:theEvent];
2179     if (handled)
2180       return;
2181   }
2183   if ([self shouldIgnoreMouseEvent:theEvent]) {
2184     // If this is the first such event, send a mouse exit to the host view.
2185     if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
2186       WebMouseEvent exitEvent =
2187           WebInputEventFactory::mouseEvent(theEvent, self);
2188       exitEvent.type = WebInputEvent::MouseLeave;
2189       exitEvent.button = WebMouseEvent::ButtonNone;
2190       renderWidgetHostView_->ForwardMouseEvent(exitEvent);
2191     }
2192     mouseEventWasIgnored_ = YES;
2193     return;
2194   }
2196   if (mouseEventWasIgnored_) {
2197     // If this is the first mouse event after a previous event that was ignored
2198     // due to the hitTest, send a mouse enter event to the host view.
2199     if (renderWidgetHostView_->render_widget_host_) {
2200       WebMouseEvent enterEvent =
2201           WebInputEventFactory::mouseEvent(theEvent, self);
2202       enterEvent.type = WebInputEvent::MouseMove;
2203       enterEvent.button = WebMouseEvent::ButtonNone;
2204       renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2205     }
2206   }
2207   mouseEventWasIgnored_ = NO;
2209   // TODO(rohitrao): Probably need to handle other mouse down events here.
2210   if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2211     if (renderWidgetHostView_->render_widget_host_)
2212       renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2214     // Manually take focus after the click but before forwarding it to the
2215     // renderer.
2216     [[self window] makeFirstResponder:self];
2217   }
2219   // Don't cancel child popups; killing them on a mouse click would prevent the
2220   // user from positioning the insertion point in the text field spawning the
2221   // popup. A click outside the text field would cause the text field to drop
2222   // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2223   // the popup anyway, so we're OK.
2225   NSEventType type = [theEvent type];
2226   if (type == NSLeftMouseDown)
2227     hasOpenMouseDown_ = YES;
2228   else if (type == NSLeftMouseUp)
2229     hasOpenMouseDown_ = NO;
2231   // TODO(suzhe): We should send mouse events to the input method first if it
2232   // wants to handle them. But it won't work without implementing method
2233   // - (NSUInteger)characterIndexForPoint:.
2234   // See: http://code.google.com/p/chromium/issues/detail?id=47141
2235   // Instead of sending mouse events to the input method first, we now just
2236   // simply confirm all ongoing composition here.
2237   if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2238       type == NSOtherMouseDown) {
2239     [self confirmComposition];
2240   }
2242   const WebMouseEvent event =
2243       WebInputEventFactory::mouseEvent(theEvent, self);
2244   renderWidgetHostView_->ForwardMouseEvent(event);
2247 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2248   // |performKeyEquivalent:| is sent to all views of a window, not only down the
2249   // responder chain (cf. "Handling Key Equivalents" in
2250   // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2251   // ). We only want to handle key equivalents if we're first responder.
2252   if ([[self window] firstResponder] != self)
2253     return NO;
2255   // If we return |NO| from this function, cocoa will send the key event to
2256   // the menu and only if the menu does not process the event to |keyDown:|. We
2257   // want to send the event to a renderer _before_ sending it to the menu, so
2258   // we need to return |YES| for all events that might be swallowed by the menu.
2259   // We do not return |YES| for every keypress because we don't get |keyDown:|
2260   // events for keys that we handle this way.
2261   NSUInteger modifierFlags = [theEvent modifierFlags];
2262   if ((modifierFlags & NSCommandKeyMask) == 0) {
2263     // Make sure the menu does not contain key equivalents that don't
2264     // contain cmd.
2265     DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2266     return NO;
2267   }
2269   // Command key combinations are sent via performKeyEquivalent rather than
2270   // keyDown:. We just forward this on and if WebCore doesn't want to handle
2271   // it, we let the WebContentsView figure out how to reinject it.
2272   [self keyEvent:theEvent wasKeyEquivalent:YES];
2273   return YES;
2276 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2277   // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2278   // returned NO. If this function returns |YES|, Cocoa sends the event to
2279   // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2280   // to us instead of doing key view loop control, ctrl-left/right get handled
2281   // correctly, etc.
2282   // (However, there are still some keys that Cocoa swallows, e.g. the key
2283   // equivalent that Cocoa uses for toggling the input language. In this case,
2284   // that's actually a good thing, though -- see http://crbug.com/26115 .)
2285   return YES;
2288 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2289   if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2290     BOOL handled = [delegate_ handleEvent:theEvent];
2291     if (handled)
2292       return kEventHandled;
2293   }
2295   [self keyEvent:theEvent wasKeyEquivalent:NO];
2296   return kEventHandled;
2299 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2300   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2301   DCHECK([theEvent type] != NSKeyDown ||
2302          !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2304   if ([theEvent type] == NSFlagsChanged) {
2305     // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2306     // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2307     int keyCode = [theEvent keyCode];
2308     if (!keyCode || keyCode == 10 || keyCode == 63)
2309       return;
2310   }
2312   // Don't cancel child popups; the key events are probably what's triggering
2313   // the popup in the first place.
2315   RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2316   DCHECK(widgetHost);
2318   NativeWebKeyboardEvent event(theEvent);
2320   // Force fullscreen windows to close on Escape so they won't keep the keyboard
2321   // grabbed or be stuck onscreen if the renderer is hanging.
2322   if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2323       event.windowsKeyCode == ui::VKEY_ESCAPE &&
2324       renderWidgetHostView_->pepper_fullscreen_window()) {
2325     RenderWidgetHostViewMac* parent =
2326         renderWidgetHostView_->fullscreen_parent_host_view();
2327     if (parent)
2328       parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2329     widgetHost->Shutdown();
2330     return;
2331   }
2333   // Suppress the escape key up event if necessary.
2334   if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2335     if (event.type == NativeWebKeyboardEvent::KeyUp)
2336       suppressNextEscapeKeyUp_ = NO;
2337     return;
2338   }
2340   // We only handle key down events and just simply forward other events.
2341   if ([theEvent type] != NSKeyDown) {
2342     widgetHost->ForwardKeyboardEvent(event);
2344     // Possibly autohide the cursor.
2345     if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2346       [NSCursor setHiddenUntilMouseMoves:YES];
2348     return;
2349   }
2351   base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2353   // Records the current marked text state, so that we can know if the marked
2354   // text was deleted or not after handling the key down event.
2355   BOOL oldHasMarkedText = hasMarkedText_;
2357   // This method should not be called recursively.
2358   DCHECK(!handlingKeyDown_);
2360   // Tells insertText: and doCommandBySelector: that we are handling a key
2361   // down event.
2362   handlingKeyDown_ = YES;
2364   // These variables might be set when handling the keyboard event.
2365   // Clear them here so that we can know whether they have changed afterwards.
2366   textToBeInserted_.clear();
2367   markedText_.clear();
2368   underlines_.clear();
2369   unmarkTextCalled_ = NO;
2370   hasEditCommands_ = NO;
2371   editCommands_.clear();
2373   // Before doing anything with a key down, check to see if plugin IME has been
2374   // cancelled, since the plugin host needs to be informed of that before
2375   // receiving the keydown.
2376   if ([theEvent type] == NSKeyDown)
2377     [self checkForPluginImeCancellation];
2379   // Sends key down events to input method first, then we can decide what should
2380   // be done according to input method's feedback.
2381   // If a plugin is active, bypass this step since events are forwarded directly
2382   // to the plugin IME.
2383   if (focusedPluginIdentifier_ == -1)
2384     [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2386   handlingKeyDown_ = NO;
2388   // Indicates if we should send the key event and corresponding editor commands
2389   // after processing the input method result.
2390   BOOL delayEventUntilAfterImeCompostion = NO;
2392   // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2393   // while an input method is composing or inserting a text.
2394   // Gmail checks this code in its onkeydown handler to stop auto-completing
2395   // e-mail addresses while composing a CJK text.
2396   // If the text to be inserted has only one character, then we don't need this
2397   // trick, because we'll send the text as a key press event instead.
2398   if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2399     NativeWebKeyboardEvent fakeEvent = event;
2400     fakeEvent.windowsKeyCode = 0xE5;  // VKEY_PROCESSKEY
2401     fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2402     fakeEvent.skip_in_browser = true;
2403     widgetHost->ForwardKeyboardEvent(fakeEvent);
2404     // If this key event was handled by the input method, but
2405     // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2406     // enqueued edit commands, then in order to let webkit handle them
2407     // correctly, we need to send the real key event and corresponding edit
2408     // commands after processing the input method result.
2409     // We shouldn't do this if a new marked text was set by the input method,
2410     // otherwise the new marked text might be cancelled by webkit.
2411     if (hasEditCommands_ && !hasMarkedText_)
2412       delayEventUntilAfterImeCompostion = YES;
2413   } else {
2414     if (!editCommands_.empty()) {
2415       widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2416           widgetHost->GetRoutingID(), editCommands_));
2417     }
2418     widgetHost->ForwardKeyboardEvent(event);
2419   }
2421   // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2422   // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2423   // be set to NULL. So we check it here and return immediately if it's NULL.
2424   if (!renderWidgetHostView_->render_widget_host_)
2425     return;
2427   // Then send keypress and/or composition related events.
2428   // If there was a marked text or the text to be inserted is longer than 1
2429   // character, then we send the text by calling ConfirmComposition().
2430   // Otherwise, if the text to be inserted only contains 1 character, then we
2431   // can just send a keypress event which is fabricated by changing the type of
2432   // the keydown event, so that we can retain all necessary informations, such
2433   // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2434   // prevent the browser from handling it again.
2435   // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2436   // handle BMP characters here, as we can always insert non-BMP characters as
2437   // text.
2438   BOOL textInserted = NO;
2439   if (textToBeInserted_.length() >
2440       ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2441     widgetHost->ImeConfirmComposition(
2442         textToBeInserted_, gfx::Range::InvalidRange(), false);
2443     textInserted = YES;
2444   }
2446   // Updates or cancels the composition. If some text has been inserted, then
2447   // we don't need to cancel the composition explicitly.
2448   if (hasMarkedText_ && markedText_.length()) {
2449     // Sends the updated marked text to the renderer so it can update the
2450     // composition node in WebKit.
2451     // When marked text is available, |selectedRange_| will be the range being
2452     // selected inside the marked text.
2453     widgetHost->ImeSetComposition(markedText_, underlines_,
2454                                   selectedRange_.location,
2455                                   NSMaxRange(selectedRange_));
2456   } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2457     if (unmarkTextCalled_) {
2458       widgetHost->ImeConfirmComposition(
2459           base::string16(), gfx::Range::InvalidRange(), false);
2460     } else {
2461       widgetHost->ImeCancelComposition();
2462     }
2463   }
2465   // If the key event was handled by the input method but it also generated some
2466   // edit commands, then we need to send the real key event and corresponding
2467   // edit commands here. This usually occurs when the input method wants to
2468   // finish current composition session but still wants the application to
2469   // handle the key event. See http://crbug.com/48161 for reference.
2470   if (delayEventUntilAfterImeCompostion) {
2471     // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2472     // with windowsKeyCode == 0xE5 has already been sent to webkit.
2473     // So before sending the real key down event, we need to send a fake key up
2474     // event to balance it.
2475     NativeWebKeyboardEvent fakeEvent = event;
2476     fakeEvent.type = blink::WebInputEvent::KeyUp;
2477     fakeEvent.skip_in_browser = true;
2478     widgetHost->ForwardKeyboardEvent(fakeEvent);
2479     // Not checking |renderWidgetHostView_->render_widget_host_| here because
2480     // a key event with |skip_in_browser| == true won't be handled by browser,
2481     // thus it won't destroy the widget.
2483     if (!editCommands_.empty()) {
2484       widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2485           widgetHost->GetRoutingID(), editCommands_));
2486     }
2487     widgetHost->ForwardKeyboardEvent(event);
2489     // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2490     // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2491     // be set to NULL. So we check it here and return immediately if it's NULL.
2492     if (!renderWidgetHostView_->render_widget_host_)
2493       return;
2494   }
2496   const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2497   // Only send a corresponding key press event if there is no marked text.
2498   if (!hasMarkedText_) {
2499     if (!textInserted && textToBeInserted_.length() == 1) {
2500       // If a single character was inserted, then we just send it as a keypress
2501       // event.
2502       event.type = blink::WebInputEvent::Char;
2503       event.text[0] = textToBeInserted_[0];
2504       event.text[1] = 0;
2505       event.skip_in_browser = true;
2506       widgetHost->ForwardKeyboardEvent(event);
2507     } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2508                [[theEvent characters] length] > 0 &&
2509                (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2510                 (hasEditCommands_ && editCommands_.empty()))) {
2511       // We don't get insertText: calls if ctrl or cmd is down, or the key event
2512       // generates an insert command. So synthesize a keypress event for these
2513       // cases, unless the key event generated any other command.
2514       event.type = blink::WebInputEvent::Char;
2515       event.skip_in_browser = true;
2516       widgetHost->ForwardKeyboardEvent(event);
2517     }
2518   }
2520   // Possibly autohide the cursor.
2521   if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2522     [NSCursor setHiddenUntilMouseMoves:YES];
2525 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2526   DCHECK(base::mac::IsOSLionOrLater());
2528   if ([event phase] != NSEventPhaseEnded &&
2529       [event phase] != NSEventPhaseCancelled) {
2530     return;
2531   }
2533   if (renderWidgetHostView_->render_widget_host_) {
2534     const WebMouseWheelEvent& webEvent =
2535         WebInputEventFactory::mouseWheelEvent(event, self);
2536     renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2537   }
2539   if (endWheelMonitor_) {
2540     [NSEvent removeMonitor:endWheelMonitor_];
2541     endWheelMonitor_ = nil;
2542   }
2545 - (void)scrollWheel:(NSEvent*)event {
2546   if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2547     BOOL handled = [delegate_ handleEvent:event];
2548     if (handled)
2549       return;
2550   }
2552   // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
2553   // the event is received even when the mouse cursor is no longer over the view
2554   // when the scrolling ends (e.g. if the tab was switched). This is necessary
2555   // for ending rubber-banding in such cases.
2556   if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
2557       !endWheelMonitor_) {
2558     endWheelMonitor_ =
2559         [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
2560             handler:^(NSEvent* blockEvent) {
2561               [self shortCircuitScrollWheelEvent:blockEvent];
2562               return blockEvent;
2563             }];
2564   }
2566   if (renderWidgetHostView_->render_widget_host_) {
2567     const WebMouseWheelEvent& webEvent =
2568         WebInputEventFactory::mouseWheelEvent(event, self);
2569     renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2570   }
2573 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
2574   NSWindow* oldWindow = [self window];
2576   // We're messing with the window, so do this to ensure no flashes. This one
2577   // prevents a flash when the current tab is closed.
2578   if (!renderWidgetHostView_->use_core_animation_)
2579     [oldWindow disableScreenUpdatesUntilFlush];
2581   // If the new window for this view is using CoreAnimation then enable
2582   // CoreAnimation on this view.
2583   if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY &&
2584       [[newWindow contentView] wantsLayer]) {
2585     renderWidgetHostView_->EnableCoreAnimation();
2586   }
2588   NSNotificationCenter* notificationCenter =
2589       [NSNotificationCenter defaultCenter];
2591   // Backing property notifications crash on 10.6 when building with the 10.7
2592   // SDK, see http://crbug.com/260595.
2593   static BOOL supportsBackingPropertiesNotification =
2594       SupportsBackingPropertiesChangedNotification();
2596   if (oldWindow) {
2597     if (supportsBackingPropertiesNotification) {
2598       [notificationCenter
2599           removeObserver:self
2600                     name:NSWindowDidChangeBackingPropertiesNotification
2601                   object:oldWindow];
2602     }
2603     [notificationCenter
2604         removeObserver:self
2605                   name:NSWindowDidMoveNotification
2606                 object:oldWindow];
2607     [notificationCenter
2608         removeObserver:self
2609                   name:NSWindowDidEndLiveResizeNotification
2610                 object:oldWindow];
2611   }
2612   if (newWindow) {
2613     if (supportsBackingPropertiesNotification) {
2614       [notificationCenter
2615           addObserver:self
2616              selector:@selector(windowDidChangeBackingProperties:)
2617                  name:NSWindowDidChangeBackingPropertiesNotification
2618                object:newWindow];
2619     }
2620     [notificationCenter
2621         addObserver:self
2622            selector:@selector(windowChangedGlobalFrame:)
2623                name:NSWindowDidMoveNotification
2624              object:newWindow];
2625     [notificationCenter
2626         addObserver:self
2627            selector:@selector(windowChangedGlobalFrame:)
2628                name:NSWindowDidEndLiveResizeNotification
2629              object:newWindow];
2630   }
2633 - (void)updateTabBackingStoreScaleFactor {
2634   if (!renderWidgetHostView_->render_widget_host_)
2635     return;
2637   float scaleFactor = ScaleFactor(self);
2638   if (scaleFactor == deviceScaleFactor_)
2639     return;
2640   deviceScaleFactor_ = scaleFactor;
2642   BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
2643       renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
2644   if (backingStore)  // NULL in hardware path.
2645     backingStore->ScaleFactorChanged(scaleFactor);
2647   [self updateSoftwareLayerScaleFactor];
2648   renderWidgetHostView_->render_widget_host_->NotifyScreenInfoChanged();
2651 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
2652 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
2653   NSWindow* window = (NSWindow*)[notification object];
2655   CGFloat newBackingScaleFactor = [window backingScaleFactor];
2656   CGFloat oldBackingScaleFactor = [base::mac::ObjCCast<NSNumber>(
2657       [[notification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey])
2658       doubleValue];
2659   if (newBackingScaleFactor != oldBackingScaleFactor) {
2660     // Background tabs check if their scale factor changed when they are added
2661     // to a window.
2663     // Allocating a CGLayerRef with the current scale factor immediately from
2664     // this handler doesn't work. Schedule the backing store update on the
2665     // next runloop cycle, then things are read for CGLayerRef allocations to
2666     // work.
2667     [self performSelector:@selector(updateTabBackingStoreScaleFactor)
2668                withObject:nil
2669                afterDelay:0];
2670   }
2673 - (void)globalFrameDidChange:(NSNotification*)notification {
2674   if (handlingGlobalFrameDidChange_)
2675     return;
2677   handlingGlobalFrameDidChange_ = YES;
2678   if (renderWidgetHostView_->compositing_iosurface_context_) {
2679     [renderWidgetHostView_->compositing_iosurface_context_->nsgl_context()
2680         update];
2681   }
2682   handlingGlobalFrameDidChange_ = NO;
2685 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
2686   renderWidgetHostView_->UpdateScreenInfo(
2687       renderWidgetHostView_->GetNativeView());
2690 - (void)setFrameSize:(NSSize)newSize {
2691   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
2693   // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
2694   // -setFrame: isn't neccessary.
2695   [super setFrameSize:newSize];
2696   if (renderWidgetHostView_->render_widget_host_) {
2697     renderWidgetHostView_->render_widget_host_->SendScreenRects();
2698     renderWidgetHostView_->render_widget_host_->WasResized();
2699   }
2701   // This call is necessary to make the window wait for a new frame at the new
2702   // size to be available before the resize completes. Calling only
2703   // setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay on
2704   // this is not sufficient.
2705   ScopedCAActionDisabler disabler;
2706   CGRect frame = NSRectToCGRect([renderWidgetHostView_->cocoa_view() bounds]);
2707   [renderWidgetHostView_->software_layer_ setFrame:frame];
2708   [renderWidgetHostView_->software_layer_ setNeedsDisplay];
2709   [renderWidgetHostView_->compositing_iosurface_layer_ setFrame:frame];
2710   [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
2713 - (void)callSetNeedsDisplayInRect {
2714   DCHECK([NSThread isMainThread]);
2715   DCHECK(renderWidgetHostView_->call_set_needs_display_in_rect_pending_);
2716   [self setNeedsDisplayInRect:renderWidgetHostView_->invalid_rect_];
2717   renderWidgetHostView_->call_set_needs_display_in_rect_pending_ = false;
2718   renderWidgetHostView_->invalid_rect_ = NSZeroRect;
2720   if (renderWidgetHostView_->compositing_iosurface_layer_)
2721     [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
2724 // Fills with white the parts of the area to the right and bottom for |rect|
2725 // that intersect |damagedRect|.
2726 - (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
2727                              dirtyRect:(gfx::Rect)damagedRect
2728                              inContext:(CGContextRef)context {
2729   if (damagedRect.right() > rect.right()) {
2730     int x = std::max(rect.right(), damagedRect.x());
2731     int y = std::min(rect.bottom(), damagedRect.bottom());
2732     int width = damagedRect.right() - x;
2733     int height = damagedRect.y() - y;
2735     // Extra fun to get around the fact that gfx::Rects can't have
2736     // negative sizes.
2737     if (width < 0) {
2738       x += width;
2739       width = -width;
2740     }
2741     if (height < 0) {
2742       y += height;
2743       height = -height;
2744     }
2746     NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
2747     CGContextSetFillColorWithColor(context,
2748                                    CGColorGetConstantColor(kCGColorWhite));
2749     CGContextFillRect(context, NSRectToCGRect(r));
2750   }
2751   if (damagedRect.bottom() > rect.bottom()) {
2752     int x = damagedRect.x();
2753     int y = damagedRect.bottom();
2754     int width = damagedRect.right() - x;
2755     int height = std::max(rect.bottom(), damagedRect.y()) - y;
2757     // Extra fun to get around the fact that gfx::Rects can't have
2758     // negative sizes.
2759     if (width < 0) {
2760       x += width;
2761       width = -width;
2762     }
2763     if (height < 0) {
2764       y += height;
2765       height = -height;
2766     }
2768     NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
2769     CGContextSetFillColorWithColor(context,
2770                                    CGColorGetConstantColor(kCGColorWhite));
2771     CGContextFillRect(context, NSRectToCGRect(r));
2772   }
2775 - (void)drawRect:(NSRect)dirtyRect {
2776   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::drawRect");
2777   CHECK(!renderWidgetHostView_->use_core_animation_);
2779   if (!renderWidgetHostView_->render_widget_host_) {
2780     // TODO(shess): Consider using something more noticable?
2781     [[NSColor whiteColor] set];
2782     NSRectFill(dirtyRect);
2783     return;
2784   }
2786   DCHECK(!renderWidgetHostView_->about_to_validate_and_paint_);
2788   // GetBackingStore works for both software and accelerated frames. If a
2789   // SwapBuffers occurs while GetBackingStore is blocking, we will continue to
2790   // blit the IOSurface below.
2791   renderWidgetHostView_->about_to_validate_and_paint_ = true;
2792   BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
2793       renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
2794   renderWidgetHostView_->about_to_validate_and_paint_ = false;
2796   const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
2798   if (renderWidgetHostView_->last_frame_was_accelerated_ &&
2799       renderWidgetHostView_->compositing_iosurface_) {
2800     if (renderWidgetHostView_->allow_overlapping_views_) {
2801       CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
2803       // If overlapping views need to be allowed, punch a hole in the window
2804       // to expose the GL underlay.
2805       TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
2806                    "h", damagedRect.height());
2807       // NSRectFill is extremely slow (15ms for a window on a fast MacPro), so
2808       // this is only done when it's a real invalidation from window damage (not
2809       // when a BuffersSwapped was received). Note that even a 1x1 NSRectFill
2810       // can take many milliseconds sometimes (!) so this is skipped completely
2811       // for drawRects that are triggered by BuffersSwapped messages.
2812       [[NSColor clearColor] set];
2813       NSRectFill(dirtyRect);
2814     }
2816     if (renderWidgetHostView_->DrawIOSurfaceWithoutCoreAnimation())
2817       return;
2819     // On error, fall back to software and fall through to the non-accelerated
2820     // drawing path.
2821     renderWidgetHostView_->GotAcceleratedCompositingError();
2822   }
2824   CGContextRef context = static_cast<CGContextRef>(
2825       [[NSGraphicsContext currentContext] graphicsPort]);
2826   [self drawBackingStore:backingStore
2827                dirtyRect:NSRectToCGRect(dirtyRect)
2828                inContext:context];
2831 - (void)drawBackingStore:(BackingStoreMac*)backingStore
2832                dirtyRect:(CGRect)dirtyRect
2833                inContext:(CGContextRef)context {
2834   content::SoftwareFrameManager* software_frame_manager =
2835       renderWidgetHostView_->software_frame_manager_.get();
2836   // There should never be both a legacy software and software composited
2837   // frame.
2838   DCHECK(!backingStore || !software_frame_manager->HasCurrentFrame());
2840   if (backingStore || software_frame_manager->HasCurrentFrame()) {
2841     // Note: All coordinates are in view units, not pixels.
2842     gfx::Rect bitmapRect(
2843         software_frame_manager->HasCurrentFrame() ?
2844             software_frame_manager->GetCurrentFrameSizeInDIP() :
2845             backingStore->size());
2847     // Specify the proper y offset to ensure that the view is rooted to the
2848     // upper left corner.  This can be negative, if the window was resized
2849     // smaller and the renderer hasn't yet repainted.
2850     int yOffset = NSHeight([self bounds]) - bitmapRect.height();
2852     NSRect nsDirtyRect = NSRectFromCGRect(dirtyRect);
2853     const gfx::Rect damagedRect([self flipNSRectToRect:nsDirtyRect]);
2855     gfx::Rect paintRect = gfx::IntersectRects(bitmapRect, damagedRect);
2856     if (!paintRect.IsEmpty()) {
2857       if (software_frame_manager->HasCurrentFrame()) {
2858         // If a software compositor framebuffer is present, draw using that.
2859         gfx::Size sizeInPixels =
2860             software_frame_manager->GetCurrentFrameSizeInPixels();
2861         base::ScopedCFTypeRef<CGDataProviderRef> dataProvider(
2862             CGDataProviderCreateWithData(
2863                 NULL,
2864                 software_frame_manager->GetCurrentFramePixels(),
2865                 4 * sizeInPixels.width() * sizeInPixels.height(),
2866                 NULL));
2867         base::ScopedCFTypeRef<CGImageRef> image(
2868             CGImageCreate(
2869                 sizeInPixels.width(),
2870                 sizeInPixels.height(),
2871                 8,
2872                 32,
2873                 4 * sizeInPixels.width(),
2874                 base::mac::GetSystemColorSpace(),
2875                 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
2876                 dataProvider,
2877                 NULL,
2878                 false,
2879                 kCGRenderingIntentDefault));
2880         CGRect imageRect = bitmapRect.ToCGRect();
2881         imageRect.origin.y = yOffset;
2882         CGContextDrawImage(context, imageRect, image);
2883       } else if (backingStore->cg_layer()) {
2884         // If we have a CGLayer, draw that into the window
2885         // TODO: add clipping to dirtyRect if it improves drawing performance.
2886         CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset),
2887                                   backingStore->cg_layer());
2888       } else {
2889         // If we haven't created a layer yet, draw the cached bitmap into
2890         // the window.  The CGLayer will be created the next time the renderer
2891         // paints.
2892         base::ScopedCFTypeRef<CGImageRef> image(
2893             CGBitmapContextCreateImage(backingStore->cg_bitmap()));
2894         CGRect imageRect = bitmapRect.ToCGRect();
2895         imageRect.origin.y = yOffset;
2896         CGContextDrawImage(context, imageRect, image);
2897       }
2898     }
2900     renderWidgetHostView_->FrameSwapped();
2902     // Fill the remaining portion of the damagedRect with white
2903     [self fillBottomRightRemainderOfRect:bitmapRect
2904                                dirtyRect:damagedRect
2905                                inContext:context];
2907     if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
2908       base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
2909           renderWidgetHostView_->whiteout_start_time_;
2910       UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
2912       // Reset the start time to 0 so that we start recording again the next
2913       // time the backing store is NULL...
2914       renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
2915     }
2916     if (!renderWidgetHostView_->web_contents_switch_paint_time_.is_null()) {
2917       base::TimeDelta web_contents_switch_paint_duration =
2918           base::TimeTicks::Now() -
2919               renderWidgetHostView_->web_contents_switch_paint_time_;
2920       UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
2921           web_contents_switch_paint_duration);
2922       // Reset contents_switch_paint_time_ to 0 so future tab selections are
2923       // recorded.
2924       renderWidgetHostView_->web_contents_switch_paint_time_ =
2925           base::TimeTicks();
2926     }
2927   } else {
2928     CGContextSetFillColorWithColor(context,
2929                                    CGColorGetConstantColor(kCGColorWhite));
2930     CGContextFillRect(context, dirtyRect);
2931     if (renderWidgetHostView_->whiteout_start_time_.is_null())
2932       renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
2933   }
2936 - (BOOL)canBecomeKeyView {
2937   if (!renderWidgetHostView_->render_widget_host_)
2938     return NO;
2940   return canBeKeyView_;
2943 - (BOOL)acceptsFirstResponder {
2944   if (!renderWidgetHostView_->render_widget_host_)
2945     return NO;
2947   return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
2950 - (BOOL)becomeFirstResponder {
2951   if (!renderWidgetHostView_->render_widget_host_)
2952     return NO;
2954   renderWidgetHostView_->render_widget_host_->Focus();
2955   renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
2956   renderWidgetHostView_->SetTextInputActive(true);
2958   // Cancel any onging composition text which was left before we lost focus.
2959   // TODO(suzhe): We should do it in -resignFirstResponder: method, but
2960   // somehow that method won't be called when switching among different tabs.
2961   // See http://crbug.com/47209
2962   [self cancelComposition];
2964   NSNumber* direction = [NSNumber numberWithUnsignedInteger:
2965       [[self window] keyViewSelectionDirection]];
2966   NSDictionary* userInfo =
2967       [NSDictionary dictionaryWithObject:direction
2968                                   forKey:kSelectionDirection];
2969   [[NSNotificationCenter defaultCenter]
2970       postNotificationName:kViewDidBecomeFirstResponder
2971                     object:self
2972                   userInfo:userInfo];
2974   return YES;
2977 - (BOOL)resignFirstResponder {
2978   renderWidgetHostView_->SetTextInputActive(false);
2979   if (!renderWidgetHostView_->render_widget_host_)
2980     return YES;
2982   if (closeOnDeactivate_)
2983     renderWidgetHostView_->KillSelf();
2985   renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
2986   renderWidgetHostView_->render_widget_host_->Blur();
2988   // We should cancel any onging composition whenever RWH's Blur() method gets
2989   // called, because in this case, webkit will confirm the ongoing composition
2990   // internally.
2991   [self cancelComposition];
2993   return YES;
2996 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
2997   if (delegate_ && [delegate_ respondsToSelector:
2998       @selector(validateUserInterfaceItem:isValidItem:)]) {
2999     BOOL valid;
3000     BOOL known = [delegate_ validateUserInterfaceItem:item
3001                                           isValidItem:&valid];
3002     if (known)
3003       return valid;
3004   }
3006   SEL action = [item action];
3008   if (action == @selector(stopSpeaking:)) {
3009     return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3010            renderWidgetHostView_->IsSpeaking();
3011   }
3012   if (action == @selector(startSpeaking:)) {
3013     return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3014            renderWidgetHostView_->SupportsSpeech();
3015   }
3017   // For now, these actions are always enabled for render view,
3018   // this is sub-optimal.
3019   // TODO(suzhe): Plumb the "can*" methods up from WebCore.
3020   if (action == @selector(undo:) ||
3021       action == @selector(redo:) ||
3022       action == @selector(cut:) ||
3023       action == @selector(copy:) ||
3024       action == @selector(copyToFindPboard:) ||
3025       action == @selector(paste:) ||
3026       action == @selector(pasteAndMatchStyle:)) {
3027     return renderWidgetHostView_->render_widget_host_->IsRenderView();
3028   }
3030   return editCommand_helper_->IsMenuItemEnabled(action, self);
3033 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
3034   return renderWidgetHostView_.get();
3037 // Determine whether we should autohide the cursor (i.e., hide it until mouse
3038 // move) for the given event. Customize here to be more selective about which
3039 // key presses to autohide on.
3040 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
3041   return ([event type] == NSKeyDown &&
3042              !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
3045 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
3046                                          index:(NSUInteger)index
3047                                       maxCount:(NSUInteger)maxCount {
3048   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3049   NSUInteger totalLength = [fullArray count];
3050   if (index >= totalLength)
3051     return nil;
3052   NSUInteger length = MIN(totalLength - index, maxCount);
3053   return [fullArray subarrayWithRange:NSMakeRange(index, length)];
3056 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
3057   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3058   return [fullArray count];
3061 - (id)accessibilityAttributeValue:(NSString *)attribute {
3062   BrowserAccessibilityManager* manager =
3063       renderWidgetHostView_->GetBrowserAccessibilityManager();
3065   // Contents specifies document view of RenderWidgetHostViewCocoa provided by
3066   // BrowserAccessibilityManager. Children includes all subviews in addition to
3067   // contents. Currently we do not have subviews besides the document view.
3068   if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
3069           [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
3070       manager) {
3071     return [NSArray arrayWithObjects:manager->
3072         GetRoot()->ToBrowserAccessibilityCocoa(), nil];
3073   } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
3074     return NSAccessibilityScrollAreaRole;
3075   }
3076   id ret = [super accessibilityAttributeValue:attribute];
3077   return ret;
3080 - (NSArray*)accessibilityAttributeNames {
3081   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
3082   [ret addObject:NSAccessibilityContentsAttribute];
3083   [ret addObjectsFromArray:[super accessibilityAttributeNames]];
3084   return ret;
3087 - (id)accessibilityHitTest:(NSPoint)point {
3088   if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
3089     return self;
3090   NSPoint pointInWindow = [[self window] convertScreenToBase:point];
3091   NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
3092   localPoint.y = NSHeight([self bounds]) - localPoint.y;
3093   BrowserAccessibilityCocoa* root = renderWidgetHostView_->
3094       GetBrowserAccessibilityManager()->
3095           GetRoot()->ToBrowserAccessibilityCocoa();
3096   id obj = [root accessibilityHitTest:localPoint];
3097   return obj;
3100 - (BOOL)accessibilityIsIgnored {
3101   return !renderWidgetHostView_->GetBrowserAccessibilityManager();
3104 - (NSUInteger)accessibilityGetIndexOf:(id)child {
3105   BrowserAccessibilityManager* manager =
3106       renderWidgetHostView_->GetBrowserAccessibilityManager();
3107   // Only child is root.
3108   if (manager &&
3109       manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
3110     return 0;
3111   } else {
3112     return NSNotFound;
3113   }
3116 - (id)accessibilityFocusedUIElement {
3117   BrowserAccessibilityManager* manager =
3118       renderWidgetHostView_->GetBrowserAccessibilityManager();
3119   if (manager) {
3120     BrowserAccessibility* focused_item = manager->GetFocus(NULL);
3121     DCHECK(focused_item);
3122     if (focused_item) {
3123       BrowserAccessibilityCocoa* focused_item_cocoa =
3124           focused_item->ToBrowserAccessibilityCocoa();
3125       DCHECK(focused_item_cocoa);
3126       if (focused_item_cocoa)
3127         return focused_item_cocoa;
3128     }
3129   }
3130   return [super accessibilityFocusedUIElement];
3133 - (void)doDefaultAction:(int32)accessibilityObjectId {
3134   RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3135   rwh->Send(new AccessibilityMsg_DoDefaultAction(
3136       rwh->GetRoutingID(), accessibilityObjectId));
3139 // VoiceOver uses this method to move the caret to the beginning of the next
3140 // word in a text field.
3141 - (void)accessibilitySetTextSelection:(int32)accId
3142                           startOffset:(int32)startOffset
3143                             endOffset:(int32)endOffset {
3144   RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3145   rwh->AccessibilitySetTextSelection(accId, startOffset, endOffset);
3148 // Convert a web accessibility's location in web coordinates into a cocoa
3149 // screen coordinate.
3150 - (NSPoint)accessibilityPointInScreen:(NSPoint)origin
3151                                  size:(NSSize)size {
3152   origin.y = NSHeight([self bounds]) - origin.y;
3153   NSPoint originInWindow = [self convertPoint:origin toView:nil];
3154   NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow];
3155   originInScreen.y = originInScreen.y - size.height;
3156   return originInScreen;
3159 - (void)setAccessibilityFocus:(BOOL)focus
3160               accessibilityId:(int32)accessibilityObjectId {
3161   if (focus) {
3162     RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3163     rwh->Send(new AccessibilityMsg_SetFocus(
3164         rwh->GetRoutingID(), accessibilityObjectId));
3166     // Immediately set the focused item even though we have not officially set
3167     // focus on it as VoiceOver expects to get the focused item after this
3168     // method returns.
3169     BrowserAccessibilityManager* manager =
3170         renderWidgetHostView_->GetBrowserAccessibilityManager();
3171     manager->SetFocus(manager->GetFromRendererID(accessibilityObjectId), false);
3172   }
3175 - (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
3176   // Performs a right click copying WebKit's
3177   // accessibilityPerformShowMenuAction.
3178   NSPoint origin = [accessibility origin];
3179   NSSize size = [[accessibility size] sizeValue];
3180   NSPoint location = [self accessibilityPointInScreen:origin size:size];
3181   location = [[self window] convertScreenToBase:location];
3182   location.x += size.width/2;
3183   location.y += size.height/2;
3185   NSEvent* fakeRightClick = [NSEvent
3186                            mouseEventWithType:NSRightMouseDown
3187                                      location:location
3188                                 modifierFlags:0
3189                                     timestamp:0
3190                                  windowNumber:[[self window] windowNumber]
3191                                       context:[NSGraphicsContext currentContext]
3192                                   eventNumber:0
3193                                    clickCount:1
3194                                      pressure:0];
3196   [self mouseEvent:fakeRightClick];
3199 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
3200 // with minor modifications for code style and commenting.
3202 //  The 'public' interface is -setToolTipAtMousePoint:. This differs from
3203 // -setToolTip: in that the updated tooltip takes effect immediately,
3204 //  without the user's having to move the mouse out of and back into the view.
3206 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
3207 // the view, which in turn requires overriding some internal tracking-rect
3208 // methods (to keep track of its owner & userdata, which need to be filled out
3209 // in the fake events.) --snej 7/6/09
3213  * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3214  *           (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
3216  * Redistribution and use in source and binary forms, with or without
3217  * modification, are permitted provided that the following conditions
3218  * are met:
3220  * 1.  Redistributions of source code must retain the above copyright
3221  *     notice, this list of conditions and the following disclaimer.
3222  * 2.  Redistributions in binary form must reproduce the above copyright
3223  *     notice, this list of conditions and the following disclaimer in the
3224  *     documentation and/or other materials provided with the distribution.
3225  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
3226  *     its contributors may be used to endorse or promote products derived
3227  *     from this software without specific prior written permission.
3229  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
3230  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3231  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3232  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
3233  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3234  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
3235  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3236  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3237  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3238  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3239  */
3241 // Any non-zero value will do, but using something recognizable might help us
3242 // debug some day.
3243 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
3245 // Override of a public NSView method, replacing the inherited functionality.
3246 // See above for rationale.
3247 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
3248                                owner:(id)owner
3249                             userData:(void *)data
3250                         assumeInside:(BOOL)assumeInside {
3251   DCHECK(trackingRectOwner_ == nil);
3252   trackingRectOwner_ = owner;
3253   trackingRectUserData_ = data;
3254   return kTrackingRectTag;
3257 // Override of (apparently) a private NSView method(!) See above for rationale.
3258 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
3259                                 owner:(id)owner
3260                              userData:(void *)data
3261                          assumeInside:(BOOL)assumeInside
3262                        useTrackingNum:(int)tag {
3263   DCHECK(tag == 0 || tag == kTrackingRectTag);
3264   DCHECK(trackingRectOwner_ == nil);
3265   trackingRectOwner_ = owner;
3266   trackingRectUserData_ = data;
3267   return kTrackingRectTag;
3270 // Override of (apparently) a private NSView method(!) See above for rationale.
3271 - (void)_addTrackingRects:(NSRect *)rects
3272                     owner:(id)owner
3273              userDataList:(void **)userDataList
3274          assumeInsideList:(BOOL *)assumeInsideList
3275              trackingNums:(NSTrackingRectTag *)trackingNums
3276                     count:(int)count {
3277   DCHECK(count == 1);
3278   DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3279   DCHECK(trackingRectOwner_ == nil);
3280   trackingRectOwner_ = owner;
3281   trackingRectUserData_ = userDataList[0];
3282   trackingNums[0] = kTrackingRectTag;
3285 // Override of a public NSView method, replacing the inherited functionality.
3286 // See above for rationale.
3287 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3288   if (tag == 0)
3289     return;
3291   if (tag == kTrackingRectTag) {
3292     trackingRectOwner_ = nil;
3293     return;
3294   }
3296   if (tag == lastToolTipTag_) {
3297     [super removeTrackingRect:tag];
3298     lastToolTipTag_ = 0;
3299     return;
3300   }
3302   // If any other tracking rect is being removed, we don't know how it was
3303   // created and it's possible there's a leak involved (see Radar 3500217).
3304   NOTREACHED();
3307 // Override of (apparently) a private NSView method(!)
3308 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3309   for (int i = 0; i < count; ++i) {
3310     int tag = tags[i];
3311     if (tag == 0)
3312       continue;
3313     DCHECK(tag == kTrackingRectTag);
3314     trackingRectOwner_ = nil;
3315   }
3318 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3319 - (void)_sendToolTipMouseExited {
3320   // Nothing matters except window, trackingNumber, and userData.
3321   int windowNumber = [[self window] windowNumber];
3322   NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3323                                               location:NSZeroPoint
3324                                          modifierFlags:0
3325                                              timestamp:0
3326                                           windowNumber:windowNumber
3327                                                context:NULL
3328                                            eventNumber:0
3329                                         trackingNumber:kTrackingRectTag
3330                                               userData:trackingRectUserData_];
3331   [trackingRectOwner_ mouseExited:fakeEvent];
3334 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3335 - (void)_sendToolTipMouseEntered {
3336   // Nothing matters except window, trackingNumber, and userData.
3337   int windowNumber = [[self window] windowNumber];
3338   NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3339                                               location:NSZeroPoint
3340                                          modifierFlags:0
3341                                              timestamp:0
3342                                           windowNumber:windowNumber
3343                                                context:NULL
3344                                            eventNumber:0
3345                                         trackingNumber:kTrackingRectTag
3346                                               userData:trackingRectUserData_];
3347   [trackingRectOwner_ mouseEntered:fakeEvent];
3350 // Sets the view's current tooltip, to be displayed at the current mouse
3351 // location. (This does not make the tooltip appear -- as usual, it only
3352 // appears after a delay.) Pass null to remove the tooltip.
3353 - (void)setToolTipAtMousePoint:(NSString *)string {
3354   NSString *toolTip = [string length] == 0 ? nil : string;
3355   if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3356       (!toolTip && !toolTip_)) {
3357     return;
3358   }
3360   if (toolTip_) {
3361     [self _sendToolTipMouseExited];
3362   }
3364   toolTip_.reset([toolTip copy]);
3366   if (toolTip) {
3367     // See radar 3500217 for why we remove all tooltips
3368     // rather than just the single one we created.
3369     [self removeAllToolTips];
3370     NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3371     lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3372                                      owner:self
3373                                   userData:NULL];
3374     [self _sendToolTipMouseEntered];
3375   }
3378 // NSView calls this to get the text when displaying the tooltip.
3379 - (NSString *)view:(NSView *)view
3380   stringForToolTip:(NSToolTipTag)tag
3381              point:(NSPoint)point
3382           userData:(void *)data {
3383   return [[toolTip_ copy] autorelease];
3386 // Below is our NSTextInputClient implementation.
3388 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3389 // functions to process this event.
3391 // [WebHTMLView keyDown] ->
3392 //     EventHandler::keyEvent() ->
3393 //     ...
3394 //     [WebEditorClient handleKeyboardEvent] ->
3395 //     [WebHTMLView _interceptEditingKeyEvent] ->
3396 //     [NSResponder interpretKeyEvents] ->
3397 //     [WebHTMLView insertText] ->
3398 //     Editor::insertText()
3400 // Unfortunately, it is hard for Chromium to use this implementation because
3401 // it causes key-typing jank.
3402 // RenderWidgetHostViewMac is running in a browser process. On the other
3403 // hand, Editor and EventHandler are running in a renderer process.
3404 // So, if we used this implementation, a NSKeyDown event is dispatched to
3405 // the following functions of Chromium.
3407 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3408 //     |Sync IPC (KeyDown)| (*1) ->
3409 //     EventHandler::keyEvent() (renderer) ->
3410 //     ...
3411 //     EditorClientImpl::handleKeyboardEvent() (renderer) ->
3412 //     |Sync IPC| (*2) ->
3413 //     [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3414 //     [self interpretKeyEvents] ->
3415 //     [RenderWidgetHostViewMac insertText] (browser) ->
3416 //     |Async IPC| ->
3417 //     Editor::insertText() (renderer)
3419 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3420 // result of EventHandler::keyEvent().
3421 // (*2) we need to wait until this call finishes since WebEditorClient uses
3422 // the result of [WebHTMLView _interceptEditingKeyEvent].
3424 // This needs many sync IPC messages sent between a browser and a renderer for
3425 // each key event, which would probably result in key-typing jank.
3426 // To avoid this problem, this implementation processes key events (and input
3427 // method events) totally in a browser process and sends asynchronous input
3428 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3429 // renderer process.
3431 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3432 //     |Async IPC (RawKeyDown)| ->
3433 //     [self interpretKeyEvents] ->
3434 //     [RenderWidgetHostViewMac insertText] (browser) ->
3435 //     |Async IPC (Char)| ->
3436 //     Editor::insertText() (renderer)
3438 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3439 // make any key-typing jank. --hbono 7/23/09
3441 extern "C" {
3442 extern NSString *NSTextInputReplacementRangeAttributeName;
3445 - (NSArray *)validAttributesForMarkedText {
3446   // This code is just copied from WebKit except renaming variables.
3447   if (!validAttributesForMarkedText_) {
3448     validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3449         NSUnderlineStyleAttributeName,
3450         NSUnderlineColorAttributeName,
3451         NSMarkedClauseSegmentAttributeName,
3452         NSTextInputReplacementRangeAttributeName,
3453         nil]);
3454   }
3455   return validAttributesForMarkedText_.get();
3458 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3459   DCHECK([self window]);
3460   // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3461   // coordinates (upper left origin). Scroll offsets will be taken care of in
3462   // the renderer.
3463   thePoint = [[self window] convertScreenToBase:thePoint];
3464   thePoint = [self convertPoint:thePoint fromView:nil];
3465   thePoint.y = NSHeight([self frame]) - thePoint.y;
3467   NSUInteger index =
3468       TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3469           renderWidgetHostView_->render_widget_host_,
3470           gfx::Point(thePoint.x, thePoint.y));
3471   return index;
3474 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3475                              actualRange:(NSRangePointer)actualRange {
3476   NSRect rect;
3477   if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3478           theRange,
3479           &rect,
3480           actualRange)) {
3481     rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3482         renderWidgetHostView_->render_widget_host_, theRange);
3484     // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3485     if (actualRange)
3486       *actualRange = theRange;
3487   }
3489   // The returned rectangle is in WebKit coordinates (upper left origin), so
3490   // flip the coordinate system.
3491   NSRect viewFrame = [self frame];
3492   rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3493   return rect;
3496 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3497                          actualRange:(NSRangePointer)actualRange {
3498   NSRect rect = [self firstViewRectForCharacterRange:theRange
3499                                          actualRange:actualRange];
3501   // Convert into screen coordinates for return.
3502   rect = [self convertRect:rect toView:nil];
3503   rect.origin = [[self window] convertBaseToScreen:rect.origin];
3504   return rect;
3507 - (NSRange)markedRange {
3508   // An input method calls this method to check if an application really has
3509   // a text being composed when hasMarkedText call returns true.
3510   // Returns the range saved in the setMarkedText method so the input method
3511   // calls the setMarkedText method and we can update the composition node
3512   // there. (When this method returns an empty range, the input method doesn't
3513   // call the setMarkedText method.)
3514   return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3517 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3518     actualRange:(NSRangePointer)actualRange {
3519   // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3520   if (actualRange)
3521     *actualRange = range;
3522   NSAttributedString* str =
3523       TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3524           renderWidgetHostView_->render_widget_host_, range);
3525   return str;
3528 - (NSInteger)conversationIdentifier {
3529   return reinterpret_cast<NSInteger>(self);
3532 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3533 // nil when the caret is in non-editable content or password box to avoid
3534 // making input methods do their work.
3535 - (NSTextInputContext *)inputContext {
3536   if (focusedPluginIdentifier_ != -1)
3537     return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3539   switch(renderWidgetHostView_->text_input_type_) {
3540     case ui::TEXT_INPUT_TYPE_NONE:
3541     case ui::TEXT_INPUT_TYPE_PASSWORD:
3542       return nil;
3543     default:
3544       return [super inputContext];
3545   }
3548 - (BOOL)hasMarkedText {
3549   // An input method calls this function to figure out whether or not an
3550   // application is really composing a text. If it is composing, it calls
3551   // the markedRange method, and maybe calls the setMarkedText method.
3552   // It seems an input method usually calls this function when it is about to
3553   // cancel an ongoing composition. If an application has a non-empty marked
3554   // range, it calls the setMarkedText method to delete the range.
3555   return hasMarkedText_;
3558 - (void)unmarkText {
3559   // Delete the composition node of the renderer and finish an ongoing
3560   // composition.
3561   // It seems an input method calls the setMarkedText method and set an empty
3562   // text when it cancels an ongoing composition, i.e. I have never seen an
3563   // input method calls this method.
3564   hasMarkedText_ = NO;
3565   markedText_.clear();
3566   underlines_.clear();
3568   // If we are handling a key down event, then ConfirmComposition() will be
3569   // called in keyEvent: method.
3570   if (!handlingKeyDown_) {
3571     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3572         base::string16(), gfx::Range::InvalidRange(), false);
3573   } else {
3574     unmarkTextCalled_ = YES;
3575   }
3578 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3579                               replacementRange:(NSRange)replacementRange {
3580   // An input method updates the composition string.
3581   // We send the given text and range to the renderer so it can update the
3582   // composition node of WebKit.
3583   // TODO(suzhe): It's hard for us to support replacementRange without accessing
3584   // the full web content.
3585   BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3586   NSString* im_text = isAttributedString ? [string string] : string;
3587   int length = [im_text length];
3589   // |markedRange_| will get set on a callback from ImeSetComposition().
3590   selectedRange_ = newSelRange;
3591   markedText_ = base::SysNSStringToUTF16(im_text);
3592   hasMarkedText_ = (length > 0);
3594   underlines_.clear();
3595   if (isAttributedString) {
3596     ExtractUnderlines(string, &underlines_);
3597   } else {
3598     // Use a thin black underline by default.
3599     underlines_.push_back(
3600         blink::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
3601   }
3603   // If we are handling a key down event, then SetComposition() will be
3604   // called in keyEvent: method.
3605   // Input methods of Mac use setMarkedText calls with an empty text to cancel
3606   // an ongoing composition. So, we should check whether or not the given text
3607   // is empty to update the input method state. (Our input method backend can
3608   // automatically cancels an ongoing composition when we send an empty text.
3609   // So, it is OK to send an empty text to the renderer.)
3610   if (!handlingKeyDown_) {
3611     renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3612         markedText_, underlines_,
3613         newSelRange.location, NSMaxRange(newSelRange));
3614   }
3617 - (void)doCommandBySelector:(SEL)selector {
3618   // An input method calls this function to dispatch an editing command to be
3619   // handled by this view.
3620   if (selector == @selector(noop:))
3621     return;
3623   std::string command(
3624       [RenderWidgetHostViewMacEditCommandHelper::
3625           CommandNameForSelector(selector) UTF8String]);
3627   // If this method is called when handling a key down event, then we need to
3628   // handle the command in the key event handler. Otherwise we can just handle
3629   // it here.
3630   if (handlingKeyDown_) {
3631     hasEditCommands_ = YES;
3632     // We ignore commands that insert characters, because this was causing
3633     // strange behavior (e.g. tab always inserted a tab rather than moving to
3634     // the next field on the page).
3635     if (!StartsWithASCII(command, "insert", false))
3636       editCommands_.push_back(EditCommand(command, ""));
3637   } else {
3638     RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3639     rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
3640                                               command, ""));
3641   }
3644 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
3645   // An input method has characters to be inserted.
3646   // Same as Linux, Mac calls this method not only:
3647   // * when an input method finishs composing text, but also;
3648   // * when we type an ASCII character (without using input methods).
3649   // When we aren't using input methods, we should send the given character as
3650   // a Char event so it is dispatched to an onkeypress() event handler of
3651   // JavaScript.
3652   // On the other hand, when we are using input methods, we should send the
3653   // given characters as an input method event and prevent the characters from
3654   // being dispatched to onkeypress() event handlers.
3655   // Text inserting might be initiated by other source instead of keyboard
3656   // events, such as the Characters dialog. In this case the text should be
3657   // sent as an input method event as well.
3658   // TODO(suzhe): It's hard for us to support replacementRange without accessing
3659   // the full web content.
3660   BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3661   NSString* im_text = isAttributedString ? [string string] : string;
3662   if (handlingKeyDown_) {
3663     textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
3664   } else {
3665     gfx::Range replacement_range(replacementRange);
3666     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3667         base::SysNSStringToUTF16(im_text), replacement_range, false);
3668   }
3670   // Inserting text will delete all marked text automatically.
3671   hasMarkedText_ = NO;
3674 - (void)insertText:(id)string {
3675   [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
3678 - (void)viewDidMoveToWindow {
3679   if ([self window])
3680     [self updateTabBackingStoreScaleFactor];
3682   if (canBeKeyView_) {
3683     NSWindow* newWindow = [self window];
3684     // Pointer comparison only, since we don't know if lastWindow_ is still
3685     // valid.
3686     if (newWindow) {
3687       // If we move into a new window, refresh the frame information. We
3688       // don't need to do it if it was the same window as it used to be in,
3689       // since that case is covered by WasShown(). We only want to do this for
3690       // real browser views, not popups.
3691       if (newWindow != lastWindow_) {
3692         lastWindow_ = newWindow;
3693         renderWidgetHostView_->WindowFrameChanged();
3694       }
3695     }
3696   }
3698   // If we switch windows (or are removed from the view hierarchy), cancel any
3699   // open mouse-downs.
3700   if (hasOpenMouseDown_) {
3701     WebMouseEvent event;
3702     event.type = WebInputEvent::MouseUp;
3703     event.button = WebMouseEvent::ButtonLeft;
3704     renderWidgetHostView_->ForwardMouseEvent(event);
3706     hasOpenMouseDown_ = NO;
3707   }
3709   // Resize the view's layers to match the new window size.
3710   ScopedCAActionDisabler disabler;
3711   [renderWidgetHostView_->software_layer_
3712       setFrame:NSRectToCGRect([self bounds])];
3713   [renderWidgetHostView_->compositing_iosurface_layer_
3714       setFrame:NSRectToCGRect([self bounds])];
3717 - (void)undo:(id)sender {
3718   if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3719     static_cast<RenderViewHostImpl*>(
3720         renderWidgetHostView_->render_widget_host_)->Undo();
3721   }
3724 - (void)redo:(id)sender {
3725   if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3726     static_cast<RenderViewHostImpl*>(
3727         renderWidgetHostView_->render_widget_host_)->Redo();
3728   }
3731 - (void)cut:(id)sender {
3732   if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3733     static_cast<RenderViewHostImpl*>(
3734         renderWidgetHostView_->render_widget_host_)->Cut();
3735   }
3738 - (void)copy:(id)sender {
3739   if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3740     static_cast<RenderViewHostImpl*>(
3741         renderWidgetHostView_->render_widget_host_)->Copy();
3742   }
3745 - (void)copyToFindPboard:(id)sender {
3746   if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3747     static_cast<RenderViewHostImpl*>(
3748         renderWidgetHostView_->render_widget_host_)->CopyToFindPboard();
3749   }
3752 - (void)paste:(id)sender {
3753   if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3754     static_cast<RenderViewHostImpl*>(
3755         renderWidgetHostView_->render_widget_host_)->Paste();
3756   }
3759 - (void)pasteAndMatchStyle:(id)sender {
3760   if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3761     static_cast<RenderViewHostImpl*>(
3762         renderWidgetHostView_->render_widget_host_)->PasteAndMatchStyle();
3763   }
3766 - (void)selectAll:(id)sender {
3767   // editCommand_helper_ adds implementations for most NSResponder methods
3768   // dynamically. But the renderer side only sends selection results back to
3769   // the browser if they were triggered by a keyboard event or went through
3770   // one of the Select methods on RWH. Since selectAll: is called from the
3771   // menu handler, neither is true.
3772   // Explicitly call SelectAll() here to make sure the renderer returns
3773   // selection results.
3774   if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3775     static_cast<RenderViewHostImpl*>(
3776         renderWidgetHostView_->render_widget_host_)->SelectAll();
3777   }
3780 - (void)startSpeaking:(id)sender {
3781   renderWidgetHostView_->SpeakSelection();
3784 - (void)stopSpeaking:(id)sender {
3785   renderWidgetHostView_->StopSpeaking();
3788 - (void)cancelComposition {
3789   if (!hasMarkedText_)
3790     return;
3792   // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
3793   // doesn't call any NSTextInput functions, such as setMarkedText or
3794   // insertText. So, we need to send an IPC message to a renderer so it can
3795   // delete the composition node.
3796   NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3797   [currentInputManager markedTextAbandoned:self];
3799   hasMarkedText_ = NO;
3800   // Should not call [self unmarkText] here, because it'll send unnecessary
3801   // cancel composition IPC message to the renderer.
3804 - (void)confirmComposition {
3805   if (!hasMarkedText_)
3806     return;
3808   if (renderWidgetHostView_->render_widget_host_)
3809     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3810         base::string16(), gfx::Range::InvalidRange(), false);
3812   [self cancelComposition];
3815 - (void)setPluginImeActive:(BOOL)active {
3816   if (active == pluginImeActive_)
3817     return;
3819   pluginImeActive_ = active;
3820   if (!active) {
3821     [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
3822     renderWidgetHostView_->PluginImeCompositionCompleted(
3823         base::string16(), focusedPluginIdentifier_);
3824   }
3827 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
3828   if (focused)
3829     focusedPluginIdentifier_ = pluginId;
3830   else if (focusedPluginIdentifier_ == pluginId)
3831     focusedPluginIdentifier_ = -1;
3833   // Whenever plugin focus changes, plugin IME resets.
3834   [self setPluginImeActive:NO];
3837 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
3838   if (!pluginImeActive_)
3839     return false;
3841   ComplexTextInputPanel* inputPanel =
3842       [ComplexTextInputPanel sharedComplexTextInputPanel];
3843   NSString* composited_string = nil;
3844   BOOL handled = [inputPanel interpretKeyEvent:event
3845                                         string:&composited_string];
3846   if (composited_string) {
3847     renderWidgetHostView_->PluginImeCompositionCompleted(
3848         base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
3849     pluginImeActive_ = NO;
3850   }
3851   return handled;
3854 - (void)checkForPluginImeCancellation {
3855   if (pluginImeActive_ &&
3856       ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
3857     renderWidgetHostView_->PluginImeCompositionCompleted(
3858         base::string16(), focusedPluginIdentifier_);
3859     pluginImeActive_ = NO;
3860   }
3863 // Overriding a NSResponder method to support application services.
3865 - (id)validRequestorForSendType:(NSString*)sendType
3866                      returnType:(NSString*)returnType {
3867   id requestor = nil;
3868   BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
3869   BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
3870   BOOL hasText = !renderWidgetHostView_->selected_text().empty();
3871   BOOL takesText =
3872       renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
3874   if (sendTypeIsString && hasText && !returnType) {
3875     requestor = self;
3876   } else if (!sendType && returnTypeIsString && takesText) {
3877     requestor = self;
3878   } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
3879     requestor = self;
3880   } else {
3881     requestor = [super validRequestorForSendType:sendType
3882                                       returnType:returnType];
3883   }
3884   return requestor;
3887 - (void)viewWillStartLiveResize {
3888   [super viewWillStartLiveResize];
3889   RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3890   if (widget)
3891     widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
3894 - (void)viewDidEndLiveResize {
3895   [super viewDidEndLiveResize];
3896   RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3897   if (widget)
3898     widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
3901 - (void)updateCursor:(NSCursor*)cursor {
3902   if (currentCursor_ == cursor)
3903     return;
3905   currentCursor_.reset([cursor retain]);
3906   [[self window] invalidateCursorRectsForView:self];
3909 - (void)popupWindowWillClose:(NSNotification *)notification {
3910   renderWidgetHostView_->KillSelf();
3913 - (void)updateSoftwareLayerScaleFactor {
3914   if (![renderWidgetHostView_->software_layer_
3915           respondsToSelector:@selector(setContentsScale:)])
3916     return;
3918   ScopedCAActionDisabler disabler;
3919   [renderWidgetHostView_->software_layer_ setContentsScale:deviceScaleFactor_];
3922 // Delegate methods for the software CALayer
3923 - (void)drawLayer:(CALayer*)layer
3924         inContext:(CGContextRef)context {
3925   TRACE_EVENT0("browser", "CompositingIOSurfaceLayer::drawLayer");
3927   DCHECK(renderWidgetHostView_->use_core_animation_);
3928   DCHECK([layer isEqual:renderWidgetHostView_->software_layer_]);
3930   CGRect clipRect = CGContextGetClipBoundingBox(context);
3932   if (!renderWidgetHostView_->render_widget_host_ ||
3933       renderWidgetHostView_->render_widget_host_->is_hidden()) {
3934     CGContextSetFillColorWithColor(context,
3935                                    CGColorGetConstantColor(kCGColorWhite));
3936     CGContextFillRect(context, clipRect);
3937     return;
3938   }
3940   renderWidgetHostView_->about_to_validate_and_paint_ = true;
3941   BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
3942       renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
3943   renderWidgetHostView_->about_to_validate_and_paint_ = false;
3945   [self drawBackingStore:backingStore
3946                dirtyRect:clipRect
3947                inContext:context];
3950 - (void)setNeedsDisplay:(BOOL)flag {
3951   [renderWidgetHostView_->software_layer_ setNeedsDisplay];
3952   [super setNeedsDisplay:flag];
3955 - (void)setNeedsDisplayInRect:(NSRect)rect {
3956   [renderWidgetHostView_->software_layer_
3957       setNeedsDisplayInRect:NSRectToCGRect(rect)];
3958   [super setNeedsDisplayInRect:rect];
3961 @end
3964 // Supporting application services
3966 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
3968 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
3969                              types:(NSArray*)types {
3970   const std::string& str = renderWidgetHostView_->selected_text();
3971   if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
3973   base::scoped_nsobject<NSString> text(
3974       [[NSString alloc] initWithUTF8String:str.c_str()]);
3975   NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
3976   [pboard declareTypes:toDeclare owner:nil];
3977   return [pboard setString:text forType:NSStringPboardType];
3980 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
3981   NSString *string = [pboard stringForType:NSStringPboardType];
3982   if (!string) return NO;
3984   // If the user is currently using an IME, confirm the IME input,
3985   // and then insert the text from the service, the same as TextEdit and Safari.
3986   [self confirmComposition];
3987   [self insertText:string];
3988   return YES;
3991 - (BOOL)isOpaque {
3992   if (renderWidgetHostView_->use_core_animation_)
3993     return YES;
3994   return [super isOpaque];
3997 @end