SkBitmap::Config is deprecated, use SkColorType instead
[chromium-blink-merge.git] / content / browser / renderer_host / render_widget_host_view_mac.mm
bloba4395d10be18a32e5ceea1b163596c453e90252e
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 <OpenGL/gl.h>
9 #include <QuartzCore/QuartzCore.h>
11 #include "base/basictypes.h"
12 #include "base/bind.h"
13 #include "base/callback_helpers.h"
14 #include "base/command_line.h"
15 #include "base/debug/crash_logging.h"
16 #include "base/debug/trace_event.h"
17 #include "base/logging.h"
18 #include "base/mac/mac_util.h"
19 #include "base/mac/scoped_cftyperef.h"
20 #import "base/mac/scoped_nsobject.h"
21 #include "base/mac/sdk_forward_declarations.h"
22 #include "base/message_loop/message_loop.h"
23 #include "base/metrics/histogram.h"
24 #include "base/strings/string_util.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/strings/sys_string_conversions.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/sys_info.h"
29 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
30 #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
31 #include "content/browser/compositor/resize_lock.h"
32 #include "content/browser/frame_host/frame_tree.h"
33 #include "content/browser/frame_host/frame_tree_node.h"
34 #include "content/browser/frame_host/render_frame_host_impl.h"
35 #include "content/browser/gpu/compositor_util.h"
36 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
37 #include "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
38 #include "content/browser/renderer_host/compositing_iosurface_mac.h"
39 #include "content/browser/renderer_host/render_widget_helper.h"
40 #include "content/browser/renderer_host/render_view_host_impl.h"
41 #import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
42 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
43 #import "content/browser/renderer_host/software_layer_mac.h"
44 #import "content/browser/renderer_host/text_input_client_mac.h"
45 #include "content/common/accessibility_messages.h"
46 #include "content/common/edit_command.h"
47 #include "content/common/gpu/gpu_messages.h"
48 #include "content/common/gpu/surface_handle_types_mac.h"
49 #include "content/common/input_messages.h"
50 #include "content/common/view_messages.h"
51 #include "content/common/webplugin_geometry.h"
52 #include "content/public/browser/browser_thread.h"
53 #include "content/public/browser/native_web_keyboard_event.h"
54 #include "content/public/browser/notification_service.h"
55 #include "content/public/browser/notification_types.h"
56 #include "content/public/browser/render_widget_host_view_frame_subscriber.h"
57 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
58 #include "content/public/browser/user_metrics.h"
59 #include "content/public/browser/web_contents.h"
60 #include "skia/ext/platform_canvas.h"
61 #include "third_party/WebKit/public/platform/WebScreenInfo.h"
62 #include "third_party/WebKit/public/web/WebInputEvent.h"
63 #include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
64 #import "third_party/mozilla/ComplexTextInputPanel.h"
65 #include "ui/base/cocoa/animation_utils.h"
66 #import "ui/base/cocoa/fullscreen_window_manager.h"
67 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
68 #include "ui/events/keycodes/keyboard_codes.h"
69 #include "ui/base/layout.h"
70 #include "ui/compositor/compositor.h"
71 #include "ui/compositor/layer.h"
72 #include "ui/gfx/display.h"
73 #include "ui/gfx/point.h"
74 #include "ui/gfx/rect_conversions.h"
75 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
76 #include "ui/gfx/screen.h"
77 #include "ui/gfx/size_conversions.h"
78 #include "ui/gl/gl_switches.h"
80 using content::BrowserAccessibility;
81 using content::BrowserAccessibilityManager;
82 using content::EditCommand;
83 using content::FrameTreeNode;
84 using content::NativeWebKeyboardEvent;
85 using content::RenderFrameHost;
86 using content::RenderViewHost;
87 using content::RenderViewHostImpl;
88 using content::RenderWidgetHostImpl;
89 using content::RenderWidgetHostViewMac;
90 using content::RenderWidgetHostViewMacEditCommandHelper;
91 using content::TextInputClientMac;
92 using content::WebContents;
93 using blink::WebInputEvent;
94 using blink::WebInputEventFactory;
95 using blink::WebMouseEvent;
96 using blink::WebMouseWheelEvent;
97 using blink::WebGestureEvent;
99 // These are not documented, so use only after checking -respondsToSelector:.
100 @interface NSApplication (UndocumentedSpeechMethods)
101 - (void)speakString:(NSString*)string;
102 - (void)stopSpeaking:(id)sender;
103 - (BOOL)isSpeaking;
104 @end
106 // Declare things that are part of the 10.7 SDK.
107 #if !defined(MAC_OS_X_VERSION_10_7) || \
108     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
110 static NSString* const NSWindowDidChangeBackingPropertiesNotification =
111     @"NSWindowDidChangeBackingPropertiesNotification";
113 #endif  // 10.7
115 // This method will return YES for OS X versions 10.7.3 and later, and NO
116 // otherwise.
117 // Used to prevent a crash when building with the 10.7 SDK and accessing the
118 // notification below. See: http://crbug.com/260595.
119 static BOOL SupportsBackingPropertiesChangedNotification() {
120   // windowDidChangeBackingProperties: method has been added to the
121   // NSWindowDelegate protocol in 10.7.3, at the same time as the
122   // NSWindowDidChangeBackingPropertiesNotification notification was added.
123   // If the protocol contains this method description, the notification should
124   // be supported as well.
125   Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
126   struct objc_method_description methodDescription =
127       protocol_getMethodDescription(
128           windowDelegateProtocol,
129           @selector(windowDidChangeBackingProperties:),
130           NO,
131           YES);
133   // If the protocol does not contain the method, the returned method
134   // description is {NULL, NULL}
135   return methodDescription.name != NULL || methodDescription.types != NULL;
138 // Private methods:
139 @interface RenderWidgetHostViewCocoa ()
140 @property(nonatomic, assign) NSRange selectedRange;
141 @property(nonatomic, assign) NSRange markedRange;
143 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
144 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
145 - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event
146                    consumed:(BOOL)consumed;
148 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
149 - (void)windowDidChangeBackingProperties:(NSNotification*)notification;
150 - (void)windowChangedGlobalFrame:(NSNotification*)notification;
151 - (void)checkForPluginImeCancellation;
152 - (void)updateScreenProperties;
153 - (void)setResponderDelegate:
154         (NSObject<RenderWidgetHostViewMacDelegate>*)delegate;
155 @end
157 // A window subclass that allows the fullscreen window to become main and gain
158 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
159 // handled by the browser.
160 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
161 @end
163 @implementation PepperFlashFullscreenWindow
165 - (BOOL)canBecomeKeyWindow {
166   return YES;
169 - (BOOL)canBecomeMainWindow {
170   return YES;
173 @end
175 @interface RenderWidgetPopupWindow : NSWindow {
176    // The event tap that allows monitoring of all events, to properly close with
177    // a click outside the bounds of the window.
178   id clickEventTap_;
180 @end
182 @implementation RenderWidgetPopupWindow
184 - (id)initWithContentRect:(NSRect)contentRect
185                 styleMask:(NSUInteger)windowStyle
186                   backing:(NSBackingStoreType)bufferingType
187                     defer:(BOOL)deferCreation {
188   if (self = [super initWithContentRect:contentRect
189                               styleMask:windowStyle
190                                 backing:bufferingType
191                                   defer:deferCreation]) {
192     [self setOpaque:NO];
193     [self setBackgroundColor:[NSColor clearColor]];
194     [self startObservingClicks];
195   }
196   return self;
199 - (void)close {
200   [self stopObservingClicks];
201   [super close];
204 // Gets called when the menubar is clicked.
205 // Needed because the local event monitor doesn't see the click on the menubar.
206 - (void)beganTracking:(NSNotification*)notification {
207   [self close];
210 // Install the callback.
211 - (void)startObservingClicks {
212   clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
213       handler:^NSEvent* (NSEvent* event) {
214           if ([event window] == self)
215             return event;
216           NSEventType eventType = [event type];
217           if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
218             [self close];
219           return event;
220   }];
222   NSNotificationCenter* notificationCenter =
223       [NSNotificationCenter defaultCenter];
224   [notificationCenter addObserver:self
225          selector:@selector(beganTracking:)
226              name:NSMenuDidBeginTrackingNotification
227            object:[NSApp mainMenu]];
230 // Remove the callback.
231 - (void)stopObservingClicks {
232   if (!clickEventTap_)
233     return;
235   [NSEvent removeMonitor:clickEventTap_];
236    clickEventTap_ = nil;
238   NSNotificationCenter* notificationCenter =
239       [NSNotificationCenter defaultCenter];
240   [notificationCenter removeObserver:self
241                 name:NSMenuDidBeginTrackingNotification
242               object:[NSApp mainMenu]];
245 @end
247 namespace {
249 // Maximum number of characters we allow in a tooltip.
250 const size_t kMaxTooltipLength = 1024;
252 // TODO(suzhe): Upstream this function.
253 blink::WebColor WebColorFromNSColor(NSColor *color) {
254   CGFloat r, g, b, a;
255   [color getRed:&r green:&g blue:&b alpha:&a];
257   return
258       std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
259       std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
260       std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8  |
261       std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
264 // Extract underline information from an attributed string. Mostly copied from
265 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
266 void ExtractUnderlines(
267     NSAttributedString* string,
268     std::vector<blink::WebCompositionUnderline>* underlines) {
269   int length = [[string string] length];
270   int i = 0;
271   while (i < length) {
272     NSRange range;
273     NSDictionary* attrs = [string attributesAtIndex:i
274                               longestEffectiveRange:&range
275                                             inRange:NSMakeRange(i, length - i)];
276     if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
277       blink::WebColor color = SK_ColorBLACK;
278       if (NSColor *colorAttr =
279           [attrs objectForKey:NSUnderlineColorAttributeName]) {
280         color = WebColorFromNSColor(
281             [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
282       }
283       underlines->push_back(
284           blink::WebCompositionUnderline(range.location,
285                                          NSMaxRange(range),
286                                          color,
287                                          [style intValue] > 1,
288                                          SK_ColorTRANSPARENT));
289     }
290     i = range.location + range.length;
291   }
294 // EnablePasswordInput() and DisablePasswordInput() are copied from
295 // enableSecureTextInput() and disableSecureTextInput() functions in
296 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
297 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
298 // here, because they are already called in webkit and they are system wide
299 // functions.
300 void EnablePasswordInput() {
301   CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
302   TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
303                          sizeof(CFArrayRef), &inputSources);
304   CFRelease(inputSources);
307 void DisablePasswordInput() {
308   TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
311 // Calls to [NSScreen screens], required by FlipYFromRectToScreen and
312 // FlipNSRectToRectScreen, can take several milliseconds. Only re-compute this
313 // value when screen info changes.
314 // TODO(ccameron): An observer on every RWHVCocoa will set this to false
315 // on NSApplicationDidChangeScreenParametersNotification. Only one observer
316 // is necessary.
317 bool g_screen_info_up_to_date = false;
319 float FlipYFromRectToScreen(float y, float rect_height) {
320   TRACE_EVENT0("browser", "FlipYFromRectToScreen");
321   static CGFloat screen_zero_height = 0;
322   if (!g_screen_info_up_to_date) {
323     if ([[NSScreen screens] count] > 0) {
324       screen_zero_height =
325           [[[NSScreen screens] objectAtIndex:0] frame].size.height;
326       g_screen_info_up_to_date = true;
327     } else {
328       return y;
329     }
330   }
331   return screen_zero_height - y - rect_height;
334 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
335 // left of the primary screen (Carbon coordinates), and stuffs it into a
336 // gfx::Rect.
337 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
338   gfx::Rect new_rect(NSRectToCGRect(rect));
339   new_rect.set_y(FlipYFromRectToScreen(new_rect.y(), new_rect.height()));
340   return new_rect;
343 // Returns the window that visually contains the given view. This is different
344 // from [view window] in the case of tab dragging, where the view's owning
345 // window is a floating panel attached to the actual browser window that the tab
346 // is visually part of.
347 NSWindow* ApparentWindowForView(NSView* view) {
348   // TODO(shess): In case of !window, the view has been removed from
349   // the view hierarchy because the tab isn't main.  Could retrieve
350   // the information from the main tab for our window.
351   NSWindow* enclosing_window = [view window];
353   // See if this is a tab drag window. The width check is to distinguish that
354   // case from extension popup windows.
355   NSWindow* ancestor_window = [enclosing_window parentWindow];
356   if (ancestor_window && (NSWidth([enclosing_window frame]) ==
357                           NSWidth([ancestor_window frame]))) {
358     enclosing_window = ancestor_window;
359   }
361   return enclosing_window;
364 blink::WebScreenInfo GetWebScreenInfo(NSView* view) {
365   gfx::Display display =
366       gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
368   NSScreen* screen = [NSScreen deepestScreen];
370   blink::WebScreenInfo results;
372   results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
373   results.depth = NSBitsPerPixelFromDepth([screen depth]);
374   results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
375   results.isMonochrome =
376       [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
377   results.rect = display.bounds();
378   results.availableRect = display.work_area();
379   results.orientationAngle = display.RotationAsDegree();
380   results.orientationType =
381       content::RenderWidgetHostViewBase::GetOrientationTypeForDesktop(display);
383   return results;
386 void RemoveLayerFromSuperlayer(
387     base::scoped_nsobject<CompositingIOSurfaceLayer> layer) {
388   // Disable the fade-out animation as the layer is removed.
389   ScopedCAActionDisabler disabler;
390   [layer removeFromSuperlayer];
393 }  // namespace
395 namespace content {
397 ////////////////////////////////////////////////////////////////////////////////
398 // DelegatedFrameHost, public:
400 ui::Compositor* RenderWidgetHostViewMac::GetCompositor() const {
401   if (browser_compositor_view_)
402     return browser_compositor_view_->GetCompositor();
403   return NULL;
406 ui::Layer* RenderWidgetHostViewMac::GetLayer() {
407   return root_layer_.get();
410 RenderWidgetHostImpl* RenderWidgetHostViewMac::GetHost() {
411   return render_widget_host_;
414 void RenderWidgetHostViewMac::SchedulePaintInRect(
415     const gfx::Rect& damage_rect_in_dip) {
416   if (browser_compositor_view_)
417     browser_compositor_view_->GetCompositor()->ScheduleFullRedraw();
420 bool RenderWidgetHostViewMac::IsVisible() {
421   return !render_widget_host_->is_hidden();
424 gfx::Size RenderWidgetHostViewMac::DesiredFrameSize() {
425   return GetViewBounds().size();
428 float RenderWidgetHostViewMac::CurrentDeviceScaleFactor() {
429   return ViewScaleFactor();
432 gfx::Size RenderWidgetHostViewMac::ConvertViewSizeToPixel(
433     const gfx::Size& size) {
434   return gfx::ToEnclosingRect(gfx::ScaleRect(gfx::Rect(size),
435                                              ViewScaleFactor())).size();
438 scoped_ptr<ResizeLock> RenderWidgetHostViewMac::CreateResizeLock(
439     bool defer_compositor_lock) {
440   NOTREACHED();
441   ResizeLock* lock = NULL;
442   return scoped_ptr<ResizeLock>(lock);
445 DelegatedFrameHost* RenderWidgetHostViewMac::GetDelegatedFrameHost() const {
446   return delegated_frame_host_.get();
449 ////////////////////////////////////////////////////////////////////////////////
450 // BrowserCompositorViewMacClient, public:
452 void RenderWidgetHostViewMac::BrowserCompositorViewFrameSwapped(
453     const std::vector<ui::LatencyInfo>& all_latency_info) {
454   if (!render_widget_host_)
455     return;
456   for (auto latency_info : all_latency_info) {
457     latency_info.AddLatencyNumber(
458         ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
459     render_widget_host_->FrameSwapped(latency_info);
460   }
463 NSView* RenderWidgetHostViewMac::BrowserCompositorSuperview() {
464   return cocoa_view_;
467 ui::Layer* RenderWidgetHostViewMac::BrowserCompositorRootLayer() {
468   return root_layer_.get();
471 ///////////////////////////////////////////////////////////////////////////////
472 // RenderWidgetHostViewBase, public:
474 // static
475 void RenderWidgetHostViewBase::GetDefaultScreenInfo(
476     blink::WebScreenInfo* results) {
477   *results = GetWebScreenInfo(NULL);
480 ///////////////////////////////////////////////////////////////////////////////
481 // RenderWidgetHostViewMac, public:
483 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
484     : render_widget_host_(RenderWidgetHostImpl::From(widget)),
485       text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
486       can_compose_inline_(true),
487       browser_compositor_view_placeholder_(
488           new BrowserCompositorViewPlaceholderMac),
489       backing_store_scale_factor_(1),
490       is_loading_(false),
491       weak_factory_(this),
492       fullscreen_parent_host_view_(NULL),
493       overlay_view_weak_factory_(this),
494       software_frame_weak_ptr_factory_(this) {
495   software_frame_manager_.reset(new SoftwareFrameManager(
496       software_frame_weak_ptr_factory_.GetWeakPtr()));
497   // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
498   // goes away.  Since we autorelease it, our caller must put
499   // |GetNativeView()| into the view hierarchy right after calling us.
500   cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
501                   initWithRenderWidgetHostViewMac:this] autorelease];
503   background_layer_.reset([[CALayer alloc] init]);
504   [background_layer_
505       setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
506   [background_layer_ setGeometryFlipped:YES];
507   [background_layer_ setContentsGravity:kCAGravityTopLeft];
508   [cocoa_view_ setLayer:background_layer_];
509   [cocoa_view_ setWantsLayer:YES];
511   if (IsDelegatedRendererEnabled()) {
512     root_layer_.reset(new ui::Layer(ui::LAYER_TEXTURED));
513     delegated_frame_host_.reset(new DelegatedFrameHost(this));
514   }
516   render_widget_host_->SetView(this);
519 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
520   // This is being called from |cocoa_view_|'s destructor, so invalidate the
521   // pointer.
522   cocoa_view_ = nil;
524   UnlockMouse();
526   // Make sure that the layer doesn't reach into the now-invalid object.
527   DestroyCompositedIOSurfaceAndLayer();
528   DestroySoftwareLayer();
530   // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
531   // RenderWidgetHost does we need to tell it not to hold a stale pointer to
532   // us.
533   if (render_widget_host_)
534     render_widget_host_->SetView(NULL);
537 void RenderWidgetHostViewMac::SetDelegate(
538     NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
539   [cocoa_view_ setResponderDelegate:delegate];
542 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
543   // TODO(ccameron): Remove callers of this function.
546 ///////////////////////////////////////////////////////////////////////////////
547 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
549 bool RenderWidgetHostViewMac::EnsureCompositedIOSurface() {
550   // If the context or the IOSurface's context has had an error, re-build
551   // everything from scratch.
552   if (compositing_iosurface_context_ &&
553       compositing_iosurface_context_->HasBeenPoisoned()) {
554     LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
555                << "context was poisoned";
556     return false;
557   }
558   if (compositing_iosurface_ &&
559       compositing_iosurface_->HasBeenPoisoned()) {
560     LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
561                << "surface was poisoned";
562     return false;
563   }
565   int current_window_number =
566       CompositingIOSurfaceContext::kOffscreenContextWindowNumber;
567   bool new_surface_needed = !compositing_iosurface_;
568   bool new_context_needed =
569     !compositing_iosurface_context_ ||
570         (compositing_iosurface_context_ &&
571             compositing_iosurface_context_->window_number() !=
572                 current_window_number);
574   if (!new_surface_needed && !new_context_needed)
575     return true;
577   // Create the GL context and shaders.
578   if (new_context_needed) {
579     scoped_refptr<CompositingIOSurfaceContext> new_context =
580         CompositingIOSurfaceContext::Get(current_window_number);
581     // Un-bind the GL context from this view before binding the new GL
582     // context. Having two GL contexts bound to a view will result in
583     // crashes and corruption.
584     // http://crbug.com/230883
585     if (!new_context) {
586       LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
587       return false;
588     }
589     compositing_iosurface_context_ = new_context;
590   }
592   // Create the IOSurface texture.
593   if (new_surface_needed) {
594     compositing_iosurface_ = CompositingIOSurfaceMac::Create();
595     if (!compositing_iosurface_) {
596       LOG(ERROR) << "Failed to create CompositingIOSurface";
597       return false;
598     }
599   }
601   return true;
604 void RenderWidgetHostViewMac::EnsureBrowserCompositorView() {
605   if (!delegated_frame_host_)
606     return;
607   if (browser_compositor_view_)
608     return;
610   TRACE_EVENT0("browser",
611                "RenderWidgetHostViewMac::EnsureBrowserCompositorView");
613   browser_compositor_view_.reset(new BrowserCompositorViewMac(this));
614   delegated_frame_host_->AddedToWindow();
615   delegated_frame_host_->WasShown();
618 void RenderWidgetHostViewMac::DestroyBrowserCompositorView() {
619   TRACE_EVENT0("browser",
620                "RenderWidgetHostViewMac::DestroyBrowserCompositorView");
621   if (!browser_compositor_view_)
622     return;
624   delegated_frame_host_->WasHidden();
625   delegated_frame_host_->RemovingFromWindow();
626   browser_compositor_view_.reset();
629 void RenderWidgetHostViewMac::EnsureSoftwareLayer() {
630   TRACE_EVENT0("browser", "RenderWidgetHostViewMac::EnsureSoftwareLayer");
631   if (software_layer_)
632     return;
634   software_layer_.reset([[SoftwareLayer alloc] init]);
635   DCHECK(software_layer_);
637   // Disable the fade-in animation as the layer is added.
638   ScopedCAActionDisabler disabler;
639   [background_layer_ addSublayer:software_layer_];
642 void RenderWidgetHostViewMac::DestroySoftwareLayer() {
643   if (!software_layer_)
644     return;
646   // Disable the fade-out animation as the layer is removed.
647   ScopedCAActionDisabler disabler;
648   [software_layer_ removeFromSuperlayer];
649   software_layer_.reset();
652 void RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer() {
653   TRACE_EVENT0("browser",
654                "RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer");
655   DCHECK(compositing_iosurface_context_);
656   if (compositing_iosurface_layer_)
657     return;
659   compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
660       initWithIOSurface:compositing_iosurface_
661         withScaleFactor:compositing_iosurface_->scale_factor()
662              withClient:this]);
663   DCHECK(compositing_iosurface_layer_);
665   // Disable the fade-in animation as the layer is added.
666   ScopedCAActionDisabler disabler;
667   [background_layer_ addSublayer:compositing_iosurface_layer_];
670 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceLayer(
671     DestroyCompositedIOSurfaceLayerBehavior destroy_layer_behavior) {
672   if (!compositing_iosurface_layer_)
673     return;
675   if (destroy_layer_behavior == kRemoveLayerFromHierarchy) {
676     // Disable the fade-out animation as the layer is removed.
677     ScopedCAActionDisabler disabler;
678     [compositing_iosurface_layer_ removeFromSuperlayer];
679   }
680   [compositing_iosurface_layer_ resetClient];
681   compositing_iosurface_layer_.reset();
684 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer() {
685   // Any pending frames will not be displayed, so ack them now.
686   SendPendingSwapAck();
688   DestroyCompositedIOSurfaceLayer(kRemoveLayerFromHierarchy);
689   compositing_iosurface_ = NULL;
690   compositing_iosurface_context_ = NULL;
693 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
694   bool handled = true;
695   IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
696     IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
697     IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
698     IPC_MESSAGE_UNHANDLED(handled = false)
699   IPC_END_MESSAGE_MAP()
700   return handled;
703 void RenderWidgetHostViewMac::InitAsChild(
704     gfx::NativeView parent_view) {
707 void RenderWidgetHostViewMac::InitAsPopup(
708     RenderWidgetHostView* parent_host_view,
709     const gfx::Rect& pos) {
710   bool activatable = popup_type_ == blink::WebPopupTypeNone;
711   [cocoa_view_ setCloseOnDeactivate:YES];
712   [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
714   NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
715   origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height());
717   popup_window_.reset([[RenderWidgetPopupWindow alloc]
718       initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
719                                      pos.width(), pos.height())
720                 styleMask:NSBorderlessWindowMask
721                   backing:NSBackingStoreBuffered
722                     defer:NO]);
723   [popup_window_ setLevel:NSPopUpMenuWindowLevel];
724   [popup_window_ setReleasedWhenClosed:NO];
725   [popup_window_ makeKeyAndOrderFront:nil];
726   [[popup_window_ contentView] addSubview:cocoa_view_];
727   [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
728   [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
729   [[NSNotificationCenter defaultCenter]
730       addObserver:cocoa_view_
731          selector:@selector(popupWindowWillClose:)
732              name:NSWindowWillCloseNotification
733            object:popup_window_];
736 // This function creates the fullscreen window and hides the dock and menubar if
737 // necessary. Note, this codepath is only used for pepper flash when
738 // pp::FlashFullScreen::SetFullscreen() is called. If
739 // pp::FullScreen::SetFullscreen() is called then the entire browser window
740 // will enter fullscreen instead.
741 void RenderWidgetHostViewMac::InitAsFullscreen(
742     RenderWidgetHostView* reference_host_view) {
743   fullscreen_parent_host_view_ =
744       static_cast<RenderWidgetHostViewMac*>(reference_host_view);
745   NSWindow* parent_window = nil;
746   if (reference_host_view)
747     parent_window = [reference_host_view->GetNativeView() window];
748   NSScreen* screen = [parent_window screen];
749   if (!screen)
750     screen = [NSScreen mainScreen];
752   pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
753       initWithContentRect:[screen frame]
754                 styleMask:NSBorderlessWindowMask
755                   backing:NSBackingStoreBuffered
756                     defer:NO]);
757   [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
758   [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
759   [cocoa_view_ setCanBeKeyView:YES];
760   [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
761   [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
762   // If the pepper fullscreen window isn't opaque then there are performance
763   // issues when it's on the discrete GPU and the Chrome window is being drawn
764   // to. http://crbug.com/171911
765   [pepper_fullscreen_window_ setOpaque:YES];
767   // Note that this forms a reference cycle between the fullscreen window and
768   // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
769   // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
770   // This cycle is normally broken when -keyEvent: receives an <esc> key, which
771   // explicitly calls Shutdown on the render_widget_host_, which calls
772   // Destroy() on RWHVMac, which drops the reference to
773   // pepper_fullscreen_window_.
774   [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
776   // Note that this keeps another reference to pepper_fullscreen_window_.
777   fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
778       initWithWindow:pepper_fullscreen_window_.get()
779        desiredScreen:screen]);
780   [fullscreen_window_manager_ enterFullscreenMode];
781   [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
784 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
785   // See comment in InitAsFullscreen(): There is a reference cycle between
786   // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
787   // Tests that test pepper fullscreen mode without sending an <esc> event
788   // need to call this method to break the reference cycle.
789   [fullscreen_window_manager_ exitFullscreenMode];
790   fullscreen_window_manager_.reset();
791   [pepper_fullscreen_window_ close];
792   pepper_fullscreen_window_.reset();
795 int RenderWidgetHostViewMac::window_number() const {
796   NSWindow* window = [cocoa_view_ window];
797   if (!window)
798     return -1;
799   return [window windowNumber];
802 float RenderWidgetHostViewMac::ViewScaleFactor() const {
803   return ui::GetScaleFactorForNativeView(cocoa_view_);
806 void RenderWidgetHostViewMac::UpdateDisplayLink() {
807   static bool is_vsync_disabled =
808       CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync);
809   if (is_vsync_disabled)
810     return;
812   NSScreen* screen = [[cocoa_view_ window] screen];
813   NSDictionary* screen_description = [screen deviceDescription];
814   NSNumber* screen_number = [screen_description objectForKey:@"NSScreenNumber"];
815   CGDirectDisplayID display_id = [screen_number unsignedIntValue];
817   display_link_ = DisplayLinkMac::GetForDisplay(display_id);
818   if (!display_link_) {
819     // Note that on some headless systems, the display link will fail to be
820     // created, so this should not be a fatal error.
821     LOG(ERROR) << "Failed to create display link.";
822   }
825 void RenderWidgetHostViewMac::SendVSyncParametersToRenderer() {
826   if (!render_widget_host_ || !display_link_)
827     return;
829   base::TimeTicks timebase;
830   base::TimeDelta interval;
831   if (!display_link_->GetVSyncParameters(&timebase, &interval))
832     return;
834   render_widget_host_->UpdateVSyncParameters(timebase, interval);
837 void RenderWidgetHostViewMac::UpdateBackingStoreScaleFactor() {
838   if (!render_widget_host_)
839     return;
841   float new_scale_factor = ui::GetScaleFactorForNativeView(cocoa_view_);
842   if (new_scale_factor == backing_store_scale_factor_)
843     return;
844   backing_store_scale_factor_ = new_scale_factor;
846   render_widget_host_->NotifyScreenInfoChanged();
849 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
850   return render_widget_host_;
853 void RenderWidgetHostViewMac::WasShown() {
854   if (!render_widget_host_->is_hidden())
855     return;
857   render_widget_host_->WasShown();
858   software_frame_manager_->SetVisibility(true);
860   // Call setNeedsDisplay before pausing for new frames to come in -- if any
861   // do, and are drawn, then the needsDisplay bit will be cleared.
862   [compositing_iosurface_layer_ setNeedsDisplay];
863   PauseForPendingResizeOrRepaintsAndDraw();
866 void RenderWidgetHostViewMac::WasHidden() {
867   if (render_widget_host_->is_hidden())
868     return;
870   // Any pending frames will not be displayed until this is shown again. Ack
871   // them now.
872   SendPendingSwapAck();
874   DestroyBrowserCompositorView();
876   // If we have a renderer, then inform it that we are being hidden so it can
877   // reduce its resource utilization.
878   render_widget_host_->WasHidden();
879   software_frame_manager_->SetVisibility(false);
882 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
883   gfx::Rect rect = GetViewBounds();
884   rect.set_size(size);
885   SetBounds(rect);
888 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
889   // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
890   // TODO(thakis): fix, http://crbug.com/73362
891   if (render_widget_host_->is_hidden())
892     return;
894   // During the initial creation of the RenderWidgetHostView in
895   // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
896   // an empty size. In the Windows code flow, it is not ignored because
897   // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
898   // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
899   // flags to keep things sized properly. On the other hand, if the size is not
900   // empty then this is a valid request for a pop-up.
901   if (rect.size().IsEmpty())
902     return;
904   // Ignore the position of |rect| for non-popup rwhvs. This is because
905   // background tabs do not have a window, but the window is required for the
906   // coordinate conversions. Popups are always for a visible tab.
907   //
908   // Note: If |cocoa_view_| has been removed from the view hierarchy, it's still
909   // valid for resizing to be requested (e.g., during tab capture, to size the
910   // view to screen-capture resolution). In this case, simply treat the view as
911   // relative to the screen.
912   BOOL isRelativeToScreen = IsPopup() ||
913       ![[cocoa_view_ superview] isKindOfClass:[BaseView class]];
914   if (isRelativeToScreen) {
915     // The position of |rect| is screen coordinate system and we have to
916     // consider Cocoa coordinate system is upside-down and also multi-screen.
917     NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
918     NSSize size = NSMakeSize(rect.width(), rect.height());
919     size = [cocoa_view_ convertSize:size toView:nil];
920     origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height);
921     NSRect frame = NSMakeRect(origin_global.x, origin_global.y,
922                               size.width, size.height);
923     if (IsPopup())
924       [popup_window_ setFrame:frame display:YES];
925     else
926       [cocoa_view_ setFrame:frame];
927   } else {
928     BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
929     gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
930     rect2.set_width(rect.width());
931     rect2.set_height(rect.height());
932     [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
933   }
936 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
937   return cocoa_view_;
940 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
941   return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
944 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
945   NOTIMPLEMENTED();
946   return static_cast<gfx::NativeViewAccessible>(NULL);
949 void RenderWidgetHostViewMac::MovePluginWindows(
950     const std::vector<WebPluginGeometry>& moves) {
951   // Must be overridden, but unused on this platform. Core Animation
952   // plugins are drawn by the GPU process (through the compositor),
953   // and Core Graphics plugins are drawn by the renderer process.
954   DCHECK_CURRENTLY_ON(BrowserThread::UI);
957 void RenderWidgetHostViewMac::Focus() {
958   [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
961 void RenderWidgetHostViewMac::Blur() {
962   UnlockMouse();
963   [[cocoa_view_ window] makeFirstResponder:nil];
966 bool RenderWidgetHostViewMac::HasFocus() const {
967   return [[cocoa_view_ window] firstResponder] == cocoa_view_;
970 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
971   if (delegated_frame_host_)
972     return delegated_frame_host_->CanCopyToBitmap();
974   return software_frame_manager_->HasCurrentFrame() ||
975          (compositing_iosurface_ && compositing_iosurface_->HasIOSurface());
978 void RenderWidgetHostViewMac::Show() {
979   [cocoa_view_ setHidden:NO];
981   WasShown();
984 void RenderWidgetHostViewMac::Hide() {
985   [cocoa_view_ setHidden:YES];
987   WasHidden();
990 bool RenderWidgetHostViewMac::IsShowing() {
991   return ![cocoa_view_ isHidden];
994 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
995   NSRect bounds = [cocoa_view_ bounds];
996   // TODO(shess): In case of !window, the view has been removed from
997   // the view hierarchy because the tab isn't main.  Could retrieve
998   // the information from the main tab for our window.
999   NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1000   if (!enclosing_window)
1001     return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
1003   bounds = [cocoa_view_ convertRect:bounds toView:nil];
1004   bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
1005   return FlipNSRectToRectScreen(bounds);
1008 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
1009   WebCursor web_cursor = cursor;
1010   [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
1013 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
1014   is_loading_ = is_loading;
1015   // If we ever decide to show the waiting cursor while the page is loading
1016   // like Chrome does on Windows, call |UpdateCursor()| here.
1019 void RenderWidgetHostViewMac::TextInputStateChanged(
1020     const ViewHostMsg_TextInputState_Params& params) {
1021   if (text_input_type_ != params.type ||
1022       can_compose_inline_ != params.can_compose_inline) {
1023     text_input_type_ = params.type;
1024     can_compose_inline_ = params.can_compose_inline;
1025     if (HasFocus()) {
1026       SetTextInputActive(true);
1028       // Let AppKit cache the new input context to make IMEs happy.
1029       // See http://crbug.com/73039.
1030       [NSApp updateWindows];
1032 #ifndef __LP64__
1033       UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
1034 #endif
1035     }
1036   }
1039 void RenderWidgetHostViewMac::ImeCancelComposition() {
1040   [cocoa_view_ cancelComposition];
1043 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
1044     const gfx::Range& range,
1045     const std::vector<gfx::Rect>& character_bounds) {
1046   // The RangeChanged message is only sent with valid values. The current
1047   // caret position (start == end) will be sent if there is no IME range.
1048   [cocoa_view_ setMarkedRange:range.ToNSRange()];
1049   composition_range_ = range;
1050   composition_bounds_ = character_bounds;
1053 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
1054                                                 int error_code) {
1055   Destroy();
1058 void RenderWidgetHostViewMac::Destroy() {
1059   [[NSNotificationCenter defaultCenter]
1060       removeObserver:cocoa_view_
1061                 name:NSWindowWillCloseNotification
1062               object:popup_window_];
1064   // We've been told to destroy.
1065   [cocoa_view_ retain];
1066   [cocoa_view_ removeFromSuperview];
1067   [cocoa_view_ autorelease];
1069   [popup_window_ close];
1070   popup_window_.autorelease();
1072   [fullscreen_window_manager_ exitFullscreenMode];
1073   fullscreen_window_manager_.reset();
1074   [pepper_fullscreen_window_ close];
1076   // This can be called as part of processing the window's responder
1077   // chain, for instance |-performKeyEquivalent:|.  In that case the
1078   // object needs to survive until the stack unwinds.
1079   pepper_fullscreen_window_.autorelease();
1081   // Delete the delegated frame state, which will reach back into
1082   // render_widget_host_.
1083   DestroyBrowserCompositorView();
1084   delegated_frame_host_.reset();
1085   root_layer_.reset();
1086   browser_compositor_view_placeholder_.reset();
1088   // We get this call just before |render_widget_host_| deletes
1089   // itself.  But we are owned by |cocoa_view_|, which may be retained
1090   // by some other code.  Examples are WebContentsViewMac's
1091   // |latent_focus_view_| and TabWindowController's
1092   // |cachedContentView_|.
1093   render_widget_host_ = NULL;
1096 // Called from the renderer to tell us what the tooltip text should be. It
1097 // calls us frequently so we need to cache the value to prevent doing a lot
1098 // of repeat work.
1099 void RenderWidgetHostViewMac::SetTooltipText(
1100     const base::string16& tooltip_text) {
1101   if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1102     tooltip_text_ = tooltip_text;
1104     // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1105     // Windows; we're just trying to be polite. Don't persist the trimmed
1106     // string, as then the comparison above will always fail and we'll try to
1107     // set it again every single time the mouse moves.
1108     base::string16 display_text = tooltip_text_;
1109     if (tooltip_text_.length() > kMaxTooltipLength)
1110       display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1112     NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1113     [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1114   }
1117 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1118   return [NSApp respondsToSelector:@selector(speakString:)] &&
1119          [NSApp respondsToSelector:@selector(stopSpeaking:)];
1122 void RenderWidgetHostViewMac::SpeakSelection() {
1123   if ([NSApp respondsToSelector:@selector(speakString:)])
1124     [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
1127 bool RenderWidgetHostViewMac::IsSpeaking() const {
1128   return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1129          [NSApp isSpeaking];
1132 void RenderWidgetHostViewMac::StopSpeaking() {
1133   if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1134     [NSApp stopSpeaking:cocoa_view_];
1138 // RenderWidgetHostViewCocoa uses the stored selection text,
1139 // which implements NSServicesRequests protocol.
1141 void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text,
1142                                                size_t offset,
1143                                                const gfx::Range& range) {
1144   if (range.is_empty() || text.empty()) {
1145     selected_text_.clear();
1146   } else {
1147     size_t pos = range.GetMin() - offset;
1148     size_t n = range.length();
1150     DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1151     if (pos >= text.length()) {
1152       DCHECK(false) << "The text can not cover range.";
1153       return;
1154     }
1155     selected_text_ = base::UTF16ToUTF8(text.substr(pos, n));
1156   }
1158   [cocoa_view_ setSelectedRange:range.ToNSRange()];
1159   // Updates markedRange when there is no marked text so that retrieving
1160   // markedRange immediately after calling setMarkdText: returns the current
1161   // caret position.
1162   if (![cocoa_view_ hasMarkedText]) {
1163     [cocoa_view_ setMarkedRange:range.ToNSRange()];
1164   }
1166   RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1169 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1170     const ViewHostMsg_SelectionBounds_Params& params) {
1171   if (params.anchor_rect == params.focus_rect)
1172     caret_rect_ = params.anchor_rect;
1175 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1178 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1179   RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1181   // Create a fake mouse event to inform the render widget that the mouse
1182   // left or entered.
1183   NSWindow* window = [cocoa_view_ window];
1184   // TODO(asvitkine): If the location outside of the event stream doesn't
1185   // correspond to the current event (due to delayed event processing), then
1186   // this may result in a cursor flicker if there are later mouse move events
1187   // in the pipeline. Find a way to use the mouse location from the event that
1188   // dismissed the context menu.
1189   NSPoint location = [window mouseLocationOutsideOfEventStream];
1190   NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1191                                       location:location
1192                                  modifierFlags:0
1193                                      timestamp:0
1194                                   windowNumber:window_number()
1195                                        context:nil
1196                                    eventNumber:0
1197                                     clickCount:0
1198                                       pressure:0];
1199   WebMouseEvent web_event =
1200       WebInputEventFactory::mouseEvent(event, cocoa_view_);
1201   if (showing)
1202     web_event.type = WebInputEvent::MouseLeave;
1203   ForwardMouseEvent(web_event);
1206 bool RenderWidgetHostViewMac::IsPopup() const {
1207   return popup_type_ != blink::WebPopupTypeNone;
1210 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1211     const gfx::Rect& src_subrect,
1212     const gfx::Size& dst_size,
1213     const base::Callback<void(bool, const SkBitmap&)>& callback,
1214     const SkColorType color_type) {
1215   if (delegated_frame_host_) {
1216     delegated_frame_host_->CopyFromCompositingSurface(
1217         src_subrect, dst_size, callback, color_type);
1218     return;
1219   }
1221   if (color_type != kN32_SkColorType) {
1222     NOTIMPLEMENTED();
1223     callback.Run(false, SkBitmap());
1224   }
1225   base::ScopedClosureRunner scoped_callback_runner(
1226       base::Bind(callback, false, SkBitmap()));
1227   float scale = ui::GetScaleFactorForNativeView(cocoa_view_);
1228   gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1229       gfx::ScaleSize(dst_size, scale));
1230   if (compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) {
1231     ignore_result(scoped_callback_runner.Release());
1232     compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1233                                    dst_pixel_size,
1234                                    callback);
1235   } else if (software_frame_manager_->HasCurrentFrame()) {
1236     gfx::Rect src_pixel_rect = gfx::ToEnclosingRect(gfx::ScaleRect(
1237         src_subrect,
1238         software_frame_manager_->GetCurrentFrameDeviceScaleFactor()));
1239     SkBitmap source_bitmap;
1240     SkImageInfo source_info = SkImageInfo::MakeN32(
1241         software_frame_manager_->GetCurrentFrameSizeInPixels().width(),
1242         software_frame_manager_->GetCurrentFrameSizeInPixels().height(),
1243         kOpaque_SkAlphaType);
1244     source_bitmap.installPixels(
1245         source_info,
1246         software_frame_manager_->GetCurrentFramePixels(),
1247         source_info.minRowBytes());
1249     SkBitmap target_bitmap;
1250     if (!target_bitmap.allocN32Pixels(
1251             dst_pixel_size.width(), dst_pixel_size.height(), true))
1252       return;
1254     SkCanvas target_canvas(target_bitmap);
1255     SkRect src_pixel_skrect = SkRect::MakeXYWH(
1256         src_pixel_rect.x(), src_pixel_rect.y(),
1257         src_pixel_rect.width(), src_pixel_rect.height());
1258     target_canvas.drawBitmapRectToRect(
1259         source_bitmap,
1260         &src_pixel_skrect,
1261         SkRect::MakeXYWH(0, 0, dst_pixel_size.width(), dst_pixel_size.height()),
1262         NULL,
1263         SkCanvas::kNone_DrawBitmapRectFlag);
1265     ignore_result(scoped_callback_runner.Release());
1266     callback.Run(true, target_bitmap);
1267   } else {
1268     callback.Run(false, SkBitmap());
1269   }
1272 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1273       const gfx::Rect& src_subrect,
1274       const scoped_refptr<media::VideoFrame>& target,
1275       const base::Callback<void(bool)>& callback) {
1276   if (delegated_frame_host_) {
1277     delegated_frame_host_->CopyFromCompositingSurfaceToVideoFrame(
1278         src_subrect, target, callback);
1279     return;
1280   }
1282   base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1283   if (!compositing_iosurface_ || !compositing_iosurface_->HasIOSurface())
1284     return;
1286   if (!target.get()) {
1287     NOTREACHED();
1288     return;
1289   }
1291   if (target->format() != media::VideoFrame::YV12 &&
1292       target->format() != media::VideoFrame::I420) {
1293     NOTREACHED();
1294     return;
1295   }
1297   if (src_subrect.IsEmpty())
1298     return;
1300   ignore_result(scoped_callback_runner.Release());
1301   compositing_iosurface_->CopyToVideoFrame(
1302       GetScaledOpenGLPixelRect(src_subrect),
1303       target,
1304       callback);
1307 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1308   if (delegated_frame_host_)
1309     return delegated_frame_host_->CanCopyToVideoFrame();
1311   return (!software_frame_manager_->HasCurrentFrame() &&
1312           compositing_iosurface_ &&
1313           compositing_iosurface_->HasIOSurface());
1316 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1317   if (delegated_frame_host_)
1318     return delegated_frame_host_->CanSubscribeFrame();
1320   return !software_frame_manager_->HasCurrentFrame();
1323 void RenderWidgetHostViewMac::BeginFrameSubscription(
1324     scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1325   if (delegated_frame_host_) {
1326     delegated_frame_host_->BeginFrameSubscription(subscriber.Pass());
1327     return;
1328   }
1329   frame_subscriber_ = subscriber.Pass();
1332 void RenderWidgetHostViewMac::EndFrameSubscription() {
1333   if (delegated_frame_host_) {
1334     delegated_frame_host_->EndFrameSubscription();
1335     return;
1336   }
1338   frame_subscriber_.reset();
1341 // Sets whether or not to accept first responder status.
1342 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1343   [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1346 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1347   if (render_widget_host_)
1348     render_widget_host_->ForwardMouseEvent(event);
1350   if (event.type == WebInputEvent::MouseLeave) {
1351     [cocoa_view_ setToolTipAtMousePoint:nil];
1352     tooltip_text_.clear();
1353   }
1356 void RenderWidgetHostViewMac::KillSelf() {
1357   if (!weak_factory_.HasWeakPtrs()) {
1358     [cocoa_view_ setHidden:YES];
1359     base::MessageLoop::current()->PostTask(FROM_HERE,
1360         base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1361                    weak_factory_.GetWeakPtr()));
1362   }
1365 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1366     const NativeWebKeyboardEvent& event) {
1367   // Check WebInputEvent type since multiple types of events can be sent into
1368   // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1369   // necessary to avoid double processing.
1370   // Also check the native type, since NSFlagsChanged is considered a key event
1371   // for WebKit purposes, but isn't considered a key event by the OS.
1372   if (event.type == WebInputEvent::RawKeyDown &&
1373       [event.os_event type] == NSKeyDown)
1374     return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1375   return false;
1378 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1379     const base::string16& text, int plugin_id) {
1380   if (render_widget_host_) {
1381     render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1382         render_widget_host_->GetRoutingID(), text, plugin_id));
1383   }
1386 void RenderWidgetHostViewMac::CompositorSwapBuffers(
1387     IOSurfaceID surface_handle,
1388     const gfx::Size& size,
1389     float surface_scale_factor,
1390     const std::vector<ui::LatencyInfo>& latency_info) {
1391   // Ensure that the frame be acked unless it is explicitly passed to a
1392   // display function.
1393   base::ScopedClosureRunner scoped_ack(
1394       base::Bind(&RenderWidgetHostViewMac::SendPendingSwapAck,
1395                  weak_factory_.GetWeakPtr()));
1397   if (render_widget_host_->is_hidden())
1398     return;
1400   // Ensure that if this function exits before the frame is set up (but not
1401   // necessarily drawn) then it is treated as an error.
1402   base::ScopedClosureRunner scoped_error(
1403       base::Bind(&RenderWidgetHostViewMac::GotAcceleratedCompositingError,
1404                  weak_factory_.GetWeakPtr()));
1406   AddPendingLatencyInfo(latency_info);
1408   // If compositing_iosurface_ exists and has been poisoned, destroy it
1409   // and allow EnsureCompositedIOSurface to recreate it below. Keep a
1410   // reference to the destroyed layer around until after the below call
1411   // to LayoutLayers, to avoid flickers.
1412   base::ScopedClosureRunner scoped_layer_remover;
1413   if (compositing_iosurface_context_ &&
1414       compositing_iosurface_context_->HasBeenPoisoned()) {
1415     scoped_layer_remover.Reset(
1416         base::Bind(RemoveLayerFromSuperlayer, compositing_iosurface_layer_));
1417     DestroyCompositedIOSurfaceLayer(kLeaveLayerInHierarchy);
1418     DestroyCompositedIOSurfaceAndLayer();
1419   }
1421   // Ensure compositing_iosurface_ and compositing_iosurface_context_ be
1422   // allocated.
1423   if (!EnsureCompositedIOSurface()) {
1424     LOG(ERROR) << "Failed EnsureCompositingIOSurface";
1425     return;
1426   }
1428   // Make the context current and update the IOSurface with the handle
1429   // passed in by the swap command.
1430   {
1431     gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1432         compositing_iosurface_context_->cgl_context());
1433     if (!compositing_iosurface_->SetIOSurfaceWithContextCurrent(
1434             compositing_iosurface_context_, surface_handle, size,
1435             surface_scale_factor)) {
1436       LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1437       return;
1438     }
1439   }
1441   // Grab video frames now that the IOSurface has been set up. Note that this
1442   // will be done in an offscreen context, so it is necessary to re-set the
1443   // current context afterward.
1444   bool frame_was_captured = false;
1445   if (frame_subscriber_) {
1446     const base::TimeTicks present_time = base::TimeTicks::Now();
1447     scoped_refptr<media::VideoFrame> frame;
1448     RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1449     if (frame_subscriber_->ShouldCaptureFrame(present_time,
1450                                               &frame, &callback)) {
1451       // Flush the context that updated the IOSurface, to ensure that the
1452       // context that does the copy picks up the correct version.
1453       {
1454         gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1455             compositing_iosurface_context_->cgl_context());
1456         glFlush();
1457       }
1458       compositing_iosurface_->CopyToVideoFrame(
1459           gfx::Rect(size), frame,
1460           base::Bind(callback, present_time));
1461       frame_was_captured = true;
1462     }
1463   }
1465   // At this point the surface, its context, and its layer have been set up, so
1466   // don't generate an error (one may be generated when drawing).
1467   ignore_result(scoped_error.Release());
1469   GotAcceleratedFrame();
1471   gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1472   if (window_size.IsEmpty()) {
1473     // setNeedsDisplay will never display and we'll never ack if the window is
1474     // empty, so ack now and don't bother calling setNeedsDisplay below.
1475     return;
1476   }
1477   if (window_number() <= 0) {
1478     // It's normal for a backgrounded tab that is being captured to have no
1479     // window but not be hidden. Immediately ack the frame, and don't try to
1480     // draw it.
1481     if (frame_was_captured)
1482       return;
1484     // If this frame was not captured, there is likely some sort of bug. Ack
1485     // the frame and hope for the best. Because the IOSurface and layer are
1486     // populated, it will likely be displayed when the view is added to a
1487     // window's hierarchy.
1489     // TODO(shess) If the view does not have a window, or the window
1490     // does not have backing, the IOSurface will log "invalid drawable"
1491     // in -setView:.  It is not clear how this code is reached with such
1492     // a case, so record some info into breakpad (some subset of
1493     // browsers are likely to crash later for unrelated reasons).
1494     // http://crbug.com/148882
1495     const char* const kCrashKey = "rwhvm_window";
1496     NSWindow* window = [cocoa_view_ window];
1497     if (!window) {
1498       base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1499     } else {
1500       std::string value =
1501           base::StringPrintf("window %s delegate %s controller %s",
1502               object_getClassName(window),
1503               object_getClassName([window delegate]),
1504               object_getClassName([window windowController]));
1505       base::debug::SetCrashKeyValue(kCrashKey, value);
1506     }
1507     return;
1508   }
1510   // If the window is occluded, then this frame's display call may be severely
1511   // throttled. This is a good thing, unless tab capture may be active,
1512   // because the broadcast will be inappropriately throttled.
1513   // http://crbug.com/350410
1514   NSWindow* window = [cocoa_view_ window];
1515   if (window && [window respondsToSelector:@selector(occlusionState)]) {
1516     bool window_is_occluded =
1517         !([window occlusionState] & NSWindowOcclusionStateVisible);
1518     // Note that we aggressively ack even if this particular frame is not being
1519     // captured.
1520     if (window_is_occluded && frame_subscriber_)
1521       scoped_ack.Reset();
1522   }
1524   // If we reach here, then the frame will be displayed by a future draw
1525   // call, so don't make the callback.
1526   ignore_result(scoped_ack.Release());
1527   DCHECK(compositing_iosurface_layer_);
1528   [compositing_iosurface_layer_ gotNewFrame];
1530   // Try to finish previous copy requests after draw to get better pipelining.
1531   if (compositing_iosurface_)
1532     compositing_iosurface_->CheckIfAllCopiesAreFinished(false);
1534   // The IOSurface's size may have changed, so re-layout the layers to take
1535   // this into account. This may force an immediate draw.
1536   LayoutLayers();
1539 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1540   LOG(ERROR) << "Encountered accelerated compositing error";
1541   base::MessageLoop::current()->PostTask(
1542       FROM_HERE,
1543       base::Bind(&RenderWidgetHostViewMac::DestroyCompositingStateOnError,
1544                  weak_factory_.GetWeakPtr()));
1547 void RenderWidgetHostViewMac::DestroyCompositingStateOnError() {
1548   // This should be called with a clean stack. Make sure that no context is
1549   // current.
1550   DCHECK(!CGLGetCurrentContext());
1552   // The existing GL contexts may be in a bad state, so don't re-use any of the
1553   // existing ones anymore, rather, allocate new ones.
1554   if (compositing_iosurface_context_)
1555     compositing_iosurface_context_->PoisonContextAndSharegroup();
1557   DestroyCompositedIOSurfaceAndLayer();
1559   // Request that a new frame be generated and dirty the view.
1560   if (render_widget_host_)
1561     render_widget_host_->ScheduleComposite();
1562   [cocoa_view_ setNeedsDisplay:YES];
1564   // TODO(ccameron): It may be a good idea to request that the renderer recreate
1565   // its GL context as well, and fall back to software if this happens
1566   // repeatedly.
1569 void RenderWidgetHostViewMac::SetOverlayView(
1570     RenderWidgetHostViewMac* overlay, const gfx::Point& offset) {
1571   if (overlay_view_)
1572     overlay_view_->underlay_view_.reset();
1574   overlay_view_ = overlay->overlay_view_weak_factory_.GetWeakPtr();
1575   overlay_view_->underlay_view_ = overlay_view_weak_factory_.GetWeakPtr();
1578 void RenderWidgetHostViewMac::RemoveOverlayView() {
1579   if (overlay_view_) {
1580     overlay_view_->underlay_view_.reset();
1581     overlay_view_.reset();
1582   }
1585 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1586     const std::vector<gfx::Rect>& bounds,
1587     const gfx::Range& range,
1588     size_t* line_break_point) {
1589   DCHECK(line_break_point);
1590   if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1591     return false;
1593   // We can't check line breaking completely from only rectangle array. Thus we
1594   // assume the line breaking as the next character's y offset is larger than
1595   // a threshold. Currently the threshold is determined as minimum y offset plus
1596   // 75% of maximum height.
1597   // TODO(nona): Check the threshold is reliable or not.
1598   // TODO(nona): Bidi support.
1599   const size_t loop_end_idx = std::min(bounds.size(), range.end());
1600   int max_height = 0;
1601   int min_y_offset = kint32max;
1602   for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1603     max_height = std::max(max_height, bounds[idx].height());
1604     min_y_offset = std::min(min_y_offset, bounds[idx].y());
1605   }
1606   int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1607   for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1608     if (bounds[idx].y() > line_break_threshold) {
1609       *line_break_point = idx;
1610       return true;
1611     }
1612   }
1613   return false;
1616 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1617     const gfx::Range& range,
1618     gfx::Range* actual_range) {
1619   DCHECK(actual_range);
1620   DCHECK(!composition_bounds_.empty());
1621   DCHECK(range.start() <= composition_bounds_.size());
1622   DCHECK(range.end() <= composition_bounds_.size());
1624   if (range.is_empty()) {
1625     *actual_range = range;
1626     if (range.start() == composition_bounds_.size()) {
1627       return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1628                        composition_bounds_[range.start() - 1].y(),
1629                        0,
1630                        composition_bounds_[range.start() - 1].height());
1631     } else {
1632       return gfx::Rect(composition_bounds_[range.start()].x(),
1633                        composition_bounds_[range.start()].y(),
1634                        0,
1635                        composition_bounds_[range.start()].height());
1636     }
1637   }
1639   size_t end_idx;
1640   if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1641     end_idx = range.end();
1642   }
1643   *actual_range = gfx::Range(range.start(), end_idx);
1644   gfx::Rect rect = composition_bounds_[range.start()];
1645   for (size_t i = range.start() + 1; i < end_idx; ++i) {
1646     rect.Union(composition_bounds_[i]);
1647   }
1648   return rect;
1651 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1652     const gfx::Range& request_range) {
1653   if (composition_range_.is_empty())
1654     return gfx::Range::InvalidRange();
1656   if (request_range.is_reversed())
1657     return gfx::Range::InvalidRange();
1659   if (request_range.start() < composition_range_.start() ||
1660       request_range.start() > composition_range_.end() ||
1661       request_range.end() > composition_range_.end()) {
1662     return gfx::Range::InvalidRange();
1663   }
1665   return gfx::Range(
1666       request_range.start() - composition_range_.start(),
1667       request_range.end() - composition_range_.start());
1670 WebContents* RenderWidgetHostViewMac::GetWebContents() {
1671   if (!render_widget_host_->IsRenderView())
1672     return NULL;
1674   return WebContents::FromRenderViewHost(
1675       RenderViewHost::From(render_widget_host_));
1678 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1679     NSRange range,
1680     NSRect* rect,
1681     NSRange* actual_range) {
1682   DCHECK(rect);
1683   // This exists to make IMEs more responsive, see http://crbug.com/115920
1684   TRACE_EVENT0("browser",
1685                "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1687   // If requested range is same as caret location, we can just return it.
1688   if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1689     if (actual_range)
1690       *actual_range = range;
1691     *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1692     return true;
1693   }
1695   const gfx::Range request_range_in_composition =
1696       ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1697   if (request_range_in_composition == gfx::Range::InvalidRange())
1698     return false;
1700   // If firstRectForCharacterRange in WebFrame is failed in renderer,
1701   // ImeCompositionRangeChanged will be sent with empty vector.
1702   if (composition_bounds_.empty())
1703     return false;
1704   DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1706   gfx::Range ui_actual_range;
1707   *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1708                                request_range_in_composition,
1709                                &ui_actual_range).ToCGRect());
1710   if (actual_range) {
1711     *actual_range = gfx::Range(
1712         composition_range_.start() + ui_actual_range.start(),
1713         composition_range_.start() + ui_actual_range.end()).ToNSRange();
1714   }
1715   return true;
1718 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1719     const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1720     int gpu_host_id) {
1721   TRACE_EVENT0("browser",
1722       "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1723   DCHECK_CURRENTLY_ON(BrowserThread::UI);
1725   AddPendingSwapAck(params.route_id,
1726                     gpu_host_id,
1727                     compositing_iosurface_ ?
1728                         compositing_iosurface_->GetRendererID() : 0);
1730   switch (GetSurfaceHandleType(params.surface_handle)) {
1731     case kSurfaceHandleTypeIOSurface: {
1732       IOSurfaceID io_surface_id = IOSurfaceIDFromSurfaceHandle(
1733           params.surface_handle);
1735       CompositorSwapBuffers(io_surface_id,
1736                             params.size,
1737                             params.scale_factor,
1738                             params.latency_info);
1739     } break;
1740     case kSurfaceHandleTypeCAContext: {
1741       // Disable the fade-out animation as the layer is added.
1742       ScopedCAActionDisabler disabler;
1744       CAContextID context_id = CAContextIDFromSurfaceHandle(
1745           params.surface_handle);
1747       // If if the layer has changed put the new layer in the hierarchy and
1748       // take the old one out.
1749       if ([remote_layer_host_ contextId] != context_id) {
1750         [remote_layer_host_ removeFromSuperlayer];
1752         remote_layer_host_.reset([[CALayerHost alloc] init]);
1753         [remote_layer_host_ setContextId:context_id];
1754         [remote_layer_host_
1755             setAutoresizingMask:kCALayerMaxXMargin|kCALayerMaxYMargin];
1756         [background_layer_ addSublayer:remote_layer_host_];
1757       }
1759       // Ack the frame immediately. Any GPU back pressure will be applied by
1760       // the remote layer from within the GPU process.
1761       SendPendingSwapAck();
1762     } break;
1763     default:
1764       LOG(ERROR) << "Invalid surface handle type.";
1765       break;
1766   }
1769 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1770     const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1771     int gpu_host_id) {
1772   TRACE_EVENT0("browser",
1773       "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1774   DCHECK_CURRENTLY_ON(BrowserThread::UI);
1776   AddPendingSwapAck(params.route_id,
1777                     gpu_host_id,
1778                     compositing_iosurface_ ?
1779                         compositing_iosurface_->GetRendererID() : 0);
1780   CompositorSwapBuffers(IOSurfaceIDFromSurfaceHandle(params.surface_handle),
1781                         params.surface_size,
1782                         params.surface_scale_factor,
1783                         params.latency_info);
1786 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1787   if (compositing_iosurface_)
1788     compositing_iosurface_->UnrefIOSurface();
1791 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1792   DestroyCompositedIOSurfaceAndLayer();
1795 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1796       const gfx::Size& desired_size) {
1797   if (compositing_iosurface_) {
1798     return compositing_iosurface_->HasIOSurface() &&
1799            (desired_size.IsEmpty() ||
1800                compositing_iosurface_->dip_io_surface_size() == desired_size);
1801   }
1802   if (software_frame_manager_->HasCurrentFrame()) {
1803     return (desired_size.IsEmpty() ||
1804                software_frame_manager_->GetCurrentFrameSizeInDIP() ==
1805                    desired_size);
1806   }
1807   return false;
1810 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1811     uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1812   TRACE_EVENT0("browser", "RenderWidgetHostViewMac::OnSwapCompositorFrame");
1814   if (frame->delegated_frame_data) {
1815     float scale_factor = frame->metadata.device_scale_factor;
1817     // Compute the frame size based on the root render pass rect size.
1818     cc::RenderPass* root_pass =
1819         frame->delegated_frame_data->render_pass_list.back();
1820     gfx::Size pixel_size = root_pass->output_rect.size();
1821     gfx::Size dip_size =
1822         ConvertSizeToDIP(scale_factor, pixel_size);
1824     root_layer_->SetBounds(gfx::Rect(dip_size));
1825     if (!render_widget_host_->is_hidden()) {
1826       EnsureBrowserCompositorView();
1827       browser_compositor_view_->GetCompositor()->SetScaleAndSize(
1828           scale_factor, pixel_size);
1829     }
1831     SendVSyncParametersToRenderer();
1833     delegated_frame_host_->SwapDelegatedFrame(
1834         output_surface_id,
1835         frame->delegated_frame_data.Pass(),
1836         frame->metadata.device_scale_factor,
1837         frame->metadata.latency_info);
1838   } else if (frame->software_frame_data) {
1839     if (!software_frame_manager_->SwapToNewFrame(
1840             output_surface_id,
1841             frame->software_frame_data.get(),
1842             frame->metadata.device_scale_factor,
1843             render_widget_host_->GetProcess()->GetHandle())) {
1844       render_widget_host_->GetProcess()->ReceivedBadMessage();
1845       return;
1846     }
1848     // Add latency info to report when the frame finishes drawing.
1849     AddPendingLatencyInfo(frame->metadata.latency_info);
1851     const void* pixels = software_frame_manager_->GetCurrentFramePixels();
1852     gfx::Size size_in_pixels =
1853         software_frame_manager_->GetCurrentFrameSizeInPixels();
1855     EnsureSoftwareLayer();
1856     [software_layer_ setContentsToData:pixels
1857                           withRowBytes:4 * size_in_pixels.width()
1858                          withPixelSize:size_in_pixels
1859                        withScaleFactor:frame->metadata.device_scale_factor];
1861     // Send latency information to the host immediately, as there will be no
1862     // subsequent draw call in which to do so.
1863     SendPendingLatencyInfoToHost();
1865     GotSoftwareFrame();
1867     cc::CompositorFrameAck ack;
1868     RenderWidgetHostImpl::SendSwapCompositorFrameAck(
1869         render_widget_host_->GetRoutingID(),
1870         software_frame_manager_->GetCurrentFrameOutputSurfaceId(),
1871         render_widget_host_->GetProcess()->GetID(),
1872         ack);
1873     software_frame_manager_->SwapToNewFrameComplete(
1874         !render_widget_host_->is_hidden());
1876     // Notify observers, tab capture observers in particular, that a new
1877     // software frame has come in.
1878     NotificationService::current()->Notify(
1879         NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
1880         Source<RenderWidgetHost>(render_widget_host_),
1881         NotificationService::NoDetails());
1882   } else {
1883     DLOG(ERROR) << "Received unexpected frame type.";
1884     RecordAction(
1885         base::UserMetricsAction("BadMessageTerminate_UnexpectedFrameType"));
1886     render_widget_host_->GetProcess()->ReceivedBadMessage();
1887   }
1890 void RenderWidgetHostViewMac::AcceleratedSurfaceInitialized(int host_id,
1891                                                             int route_id) {
1894 void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) {
1895   *results = GetWebScreenInfo(GetNativeView());
1898 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1899   // TODO(shess): In case of !window, the view has been removed from
1900   // the view hierarchy because the tab isn't main.  Could retrieve
1901   // the information from the main tab for our window.
1902   NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1903   if (!enclosing_window)
1904     return gfx::Rect();
1906   NSRect bounds = [enclosing_window frame];
1907   return FlipNSRectToRectScreen(bounds);
1910 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1911   // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1912   // completely on Mac OS.
1913   return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
1916 bool RenderWidgetHostViewMac::LockMouse() {
1917   if (mouse_locked_)
1918     return true;
1920   mouse_locked_ = true;
1922   // Lock position of mouse cursor and hide it.
1923   CGAssociateMouseAndMouseCursorPosition(NO);
1924   [NSCursor hide];
1926   // Clear the tooltip window.
1927   SetTooltipText(base::string16());
1929   return true;
1932 void RenderWidgetHostViewMac::UnlockMouse() {
1933   if (!mouse_locked_)
1934     return;
1935   mouse_locked_ = false;
1937   // Unlock position of mouse cursor and unhide it.
1938   CGAssociateMouseAndMouseCursorPosition(YES);
1939   [NSCursor unhide];
1941   if (render_widget_host_)
1942     render_widget_host_->LostMouseLock();
1945 void RenderWidgetHostViewMac::WheelEventAck(
1946     const blink::WebMouseWheelEvent& event,
1947     InputEventAckState ack_result) {
1948   bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
1949   // Only record a wheel event as unhandled if JavaScript handlers got a chance
1950   // to see it (no-op wheel events are ignored by the event dispatcher)
1951   if (event.deltaX || event.deltaY)
1952     [cocoa_view_ processedWheelEvent:event consumed:consumed];
1955 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
1956   if (render_widget_host_)
1957     return render_widget_host_->Send(message);
1958   delete message;
1959   return false;
1962 void RenderWidgetHostViewMac::SoftwareFrameWasFreed(
1963     uint32 output_surface_id, unsigned frame_id) {
1964   if (!render_widget_host_)
1965     return;
1966   cc::CompositorFrameAck ack;
1967   ack.last_software_frame_id = frame_id;
1968   RenderWidgetHostImpl::SendReclaimCompositorResources(
1969       render_widget_host_->GetRoutingID(),
1970       output_surface_id,
1971       render_widget_host_->GetProcess()->GetID(),
1972       ack);
1975 void RenderWidgetHostViewMac::ReleaseReferencesToSoftwareFrame() {
1976   DestroySoftwareLayer();
1979 void RenderWidgetHostViewMac::ShutdownHost() {
1980   weak_factory_.InvalidateWeakPtrs();
1981   render_widget_host_->Shutdown();
1982   // Do not touch any members at this point, |this| has been deleted.
1985 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
1986   EnsureCompositedIOSurfaceLayer();
1987   SendVSyncParametersToRenderer();
1989   // Delete software backingstore and layer.
1990   software_frame_manager_->DiscardCurrentFrame();
1991   DestroySoftwareLayer();
1994 void RenderWidgetHostViewMac::GotSoftwareFrame() {
1995   TRACE_EVENT0("browser", "RenderWidgetHostViewMac::GotSoftwareFrame");
1997   if (!render_widget_host_)
1998     return;
2000   EnsureSoftwareLayer();
2001   LayoutLayers();
2002   SendVSyncParametersToRenderer();
2004   // Draw the contents of the frame immediately. It is critical that this
2005   // happen before the frame be acked, otherwise the new frame will likely be
2006   // ready before the drawing is complete, thrashing the browser main thread.
2007   [software_layer_ displayIfNeeded];
2009   DestroyCompositedIOSurfaceAndLayer();
2012 void RenderWidgetHostViewMac::SetActive(bool active) {
2013   if (render_widget_host_) {
2014     render_widget_host_->SetActive(active);
2015     if (active) {
2016       if (HasFocus())
2017         render_widget_host_->Focus();
2018     } else {
2019       render_widget_host_->Blur();
2020     }
2021   }
2022   if (HasFocus())
2023     SetTextInputActive(active);
2024   if (!active) {
2025     [cocoa_view_ setPluginImeActive:NO];
2026     UnlockMouse();
2027   }
2030 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
2031   if (render_widget_host_) {
2032     render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
2033         render_widget_host_->GetRoutingID(), visible));
2034   }
2037 void RenderWidgetHostViewMac::WindowFrameChanged() {
2038   if (render_widget_host_) {
2039     render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
2040         render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
2041         GetViewBounds()));
2042   }
2045 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
2046   RenderWidgetHostViewMacDictionaryHelper helper(this);
2047   helper.ShowDefinitionForSelection();
2050 void RenderWidgetHostViewMac::SetBackgroundOpaque(bool opaque) {
2051   RenderWidgetHostViewBase::SetBackgroundOpaque(opaque);
2052   if (render_widget_host_)
2053     render_widget_host_->SetBackgroundOpaque(opaque);
2056 void RenderWidgetHostViewMac::CreateBrowserAccessibilityManagerIfNeeded() {
2057   if (!GetBrowserAccessibilityManager()) {
2058     SetBrowserAccessibilityManager(
2059         new BrowserAccessibilityManagerMac(
2060             cocoa_view_,
2061             BrowserAccessibilityManagerMac::GetEmptyDocument(),
2062             render_widget_host_));
2063   }
2066 gfx::Point RenderWidgetHostViewMac::AccessibilityOriginInScreen(
2067     const gfx::Rect& bounds) {
2068   NSPoint origin = NSMakePoint(bounds.x(), bounds.y());
2069   NSSize size = NSMakeSize(bounds.width(), bounds.height());
2070   origin.y = NSHeight([cocoa_view_ bounds]) - origin.y;
2071   NSPoint originInWindow = [cocoa_view_ convertPoint:origin toView:nil];
2072   NSPoint originInScreen =
2073       [[cocoa_view_ window] convertBaseToScreen:originInWindow];
2074   originInScreen.y = originInScreen.y - size.height;
2075   return gfx::Point(originInScreen.x, originInScreen.y);
2078 void RenderWidgetHostViewMac::OnAccessibilitySetFocus(int accObjId) {
2079   // Immediately set the focused item even though we have not officially set
2080   // focus on it as VoiceOver expects to get the focused item after this
2081   // method returns.
2082   BrowserAccessibilityManager* manager = GetBrowserAccessibilityManager();
2083   if (manager)
2084     manager->SetFocus(manager->GetFromID(accObjId), false);
2087 void RenderWidgetHostViewMac::AccessibilityShowMenu(int accObjId) {
2088   BrowserAccessibilityManager* manager = GetBrowserAccessibilityManager();
2089   if (!manager)
2090     return;
2091   BrowserAccessibilityCocoa* obj =
2092       manager->GetFromID(accObjId)->ToBrowserAccessibilityCocoa();
2094   // Performs a right click copying WebKit's
2095   // accessibilityPerformShowMenuAction.
2096   NSPoint objOrigin = [obj origin];
2097   NSSize size = [[obj size] sizeValue];
2098   gfx::Point origin = AccessibilityOriginInScreen(
2099       gfx::Rect(objOrigin.x, objOrigin.y, size.width, size.height));
2100   NSPoint location = NSMakePoint(origin.x(), origin.y());
2101   location = [[cocoa_view_ window] convertScreenToBase:location];
2102   location.x += size.width/2;
2103   location.y += size.height/2;
2105   NSEvent* fakeRightClick = [NSEvent
2106                           mouseEventWithType:NSRightMouseDown
2107                                     location:location
2108                                modifierFlags:0
2109                                    timestamp:0
2110                                 windowNumber:[[cocoa_view_ window] windowNumber]
2111                                      context:[NSGraphicsContext currentContext]
2112                                  eventNumber:0
2113                                   clickCount:1
2114                                     pressure:0];
2116   [cocoa_view_ mouseEvent:fakeRightClick];
2121 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
2122   if (active) {
2123     if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2124       EnablePasswordInput();
2125     else
2126       DisablePasswordInput();
2127   } else {
2128     if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2129       DisablePasswordInput();
2130   }
2133 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
2134                                                    int plugin_id) {
2135   [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
2138 void RenderWidgetHostViewMac::OnStartPluginIme() {
2139   [cocoa_view_ setPluginImeActive:YES];
2142 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
2143     const gfx::Rect& rect) {
2144   gfx::Rect src_gl_subrect = rect;
2145   src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
2147   return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
2148                                              ViewScaleFactor()));
2151 void RenderWidgetHostViewMac::AddPendingLatencyInfo(
2152     const std::vector<ui::LatencyInfo>& latency_info) {
2153   for (size_t i = 0; i < latency_info.size(); i++) {
2154     pending_latency_info_.push_back(latency_info[i]);
2155   }
2158 void RenderWidgetHostViewMac::SendPendingLatencyInfoToHost() {
2159   for (size_t i = 0; i < pending_latency_info_.size(); i++) {
2160     pending_latency_info_[i].AddLatencyNumber(
2161         ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
2162     render_widget_host_->FrameSwapped(pending_latency_info_[i]);
2163   }
2164   pending_latency_info_.clear();
2167 void RenderWidgetHostViewMac::AddPendingSwapAck(
2168     int32 route_id, int gpu_host_id, int32 renderer_id) {
2169   // Note that multiple un-acked swaps can come in the event of a GPU process
2170   // loss. Drop the old acks.
2171   pending_swap_ack_.reset(new PendingSwapAck(
2172       route_id, gpu_host_id, renderer_id));
2175 void RenderWidgetHostViewMac::SendPendingSwapAck() {
2176   if (!pending_swap_ack_)
2177     return;
2179   AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
2180   ack_params.sync_point = 0;
2181   ack_params.renderer_id = pending_swap_ack_->renderer_id;
2182   RenderWidgetHostImpl::AcknowledgeBufferPresent(pending_swap_ack_->route_id,
2183                                                  pending_swap_ack_->gpu_host_id,
2184                                                  ack_params);
2185   pending_swap_ack_.reset();
2188 void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() {
2189   if (!render_widget_host_ || render_widget_host_->is_hidden())
2190     return;
2192   // Synchronized resizing does not yet work with browser compositor.
2193   // http://crbug.com/388005
2194   if (delegated_frame_host_)
2195     return;
2197   // Pausing for the overlay/underlay view prevents the other one from receiving
2198   // frames. This may lead to large delays, causing overlaps.
2199   // See crbug.com/352020.
2200   if (underlay_view_ || overlay_view_)
2201     return;
2203   // Ensure that all frames are acked before waiting for a frame to come in.
2204   // Note that we will draw a frame at the end of this function, so it is safe
2205   // to ack a never-drawn frame here.
2206   SendPendingSwapAck();
2208   // Wait for a frame of the right size to come in.
2209   render_widget_host_->PauseForPendingResizeOrRepaints();
2211   // Immediately draw any frames that haven't been drawn yet. This is necessary
2212   // to keep the window and the window's contents in sync.
2213   [cocoa_view_ displayIfNeeded];
2214   [software_layer_ displayIfNeeded];
2215   [compositing_iosurface_layer_ displayIfNeeded];
2218 void RenderWidgetHostViewMac::LayoutLayers() {
2219   if (delegated_frame_host_) {
2220     return;
2221   }
2223   // Disable animation of the layer's resizing or change in contents scale.
2224   ScopedCAActionDisabler disabler;
2226   // Dynamically calling setContentsScale on a CAOpenGLLayer for which
2227   // setAsynchronous is dynamically toggled can result in flashes of corrupt
2228   // content. Work around this by replacing the entire layer when the scale
2229   // factor changes.
2230   if (compositing_iosurface_ &&
2231       [compositing_iosurface_layer_
2232           respondsToSelector:(@selector(contentsScale))]) {
2233     if (compositing_iosurface_->scale_factor() !=
2234         [compositing_iosurface_layer_ contentsScale]) {
2235       DestroyCompositedIOSurfaceLayer(kRemoveLayerFromHierarchy);
2236       EnsureCompositedIOSurfaceLayer();
2237     }
2238   }
2239   if (compositing_iosurface_ &&
2240       compositing_iosurface_->HasIOSurface() &&
2241       compositing_iosurface_layer_) {
2242     CGRect layer_bounds = CGRectMake(
2243       0,
2244       0,
2245       compositing_iosurface_->dip_io_surface_size().width(),
2246       compositing_iosurface_->dip_io_surface_size().height());
2247     bool bounds_changed = !CGRectEqualToRect(
2248         layer_bounds, [compositing_iosurface_layer_ bounds]);
2249     [compositing_iosurface_layer_ setBounds:layer_bounds];
2251     // If the bounds changed, then draw the frame immediately, to ensure that
2252     // content displayed is in sync with the window size.
2253     if (bounds_changed) {
2254       // Also, sometimes, especially when infobars are being removed, the
2255       // setNeedsDisplay calls are dropped on the floor, and stale content is
2256       // displayed. Calling displayIfNeeded will ensure that the right size
2257       // frame is drawn to the screen.
2258       // http://crbug.com/350817
2259       [compositing_iosurface_layer_ setNeedsDisplay];
2260       [compositing_iosurface_layer_ displayIfNeeded];
2261     }
2262   }
2265 SkColorType RenderWidgetHostViewMac::PreferredReadbackFormat() {
2266   return kN32_SkColorType;
2269 ////////////////////////////////////////////////////////////////////////////////
2270 // CompositingIOSurfaceLayerClient, public:
2272 void RenderWidgetHostViewMac::AcceleratedLayerDidDrawFrame(bool succeeded) {
2273   if (!render_widget_host_)
2274     return;
2276   SendPendingLatencyInfoToHost();
2277   SendPendingSwapAck();
2278   if (!succeeded)
2279     GotAcceleratedCompositingError();
2282 }  // namespace content
2284 // RenderWidgetHostViewCocoa ---------------------------------------------------
2286 @implementation RenderWidgetHostViewCocoa
2287 @synthesize selectedRange = selectedRange_;
2288 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
2289 @synthesize markedRange = markedRange_;
2291 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
2292   self = [super initWithFrame:NSZeroRect];
2293   if (self) {
2294     self.acceptsTouchEvents = YES;
2295     editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
2296     editCommand_helper_->AddEditingSelectorsToClass([self class]);
2298     renderWidgetHostView_.reset(r);
2299     canBeKeyView_ = YES;
2300     focusedPluginIdentifier_ = -1;
2301     renderWidgetHostView_->backing_store_scale_factor_ =
2302         ui::GetScaleFactorForNativeView(self);
2304     // OpenGL support:
2305     if ([self respondsToSelector:
2306         @selector(setWantsBestResolutionOpenGLSurface:)]) {
2307       [self setWantsBestResolutionOpenGLSurface:YES];
2308     }
2309     handlingGlobalFrameDidChange_ = NO;
2310     [[NSNotificationCenter defaultCenter]
2311         addObserver:self
2312            selector:@selector(didChangeScreenParameters:)
2313                name:NSApplicationDidChangeScreenParametersNotification
2314              object:nil];
2315   }
2316   return self;
2319 - (void)dealloc {
2320   // Unbind the GL context from this view. If this is not done before super's
2321   // dealloc is called then the GL context will crash when it reaches into
2322   // the view in its destructor.
2323   // http://crbug.com/255608
2324   if (renderWidgetHostView_)
2325     renderWidgetHostView_->AcceleratedSurfaceRelease();
2327   if (responderDelegate_ &&
2328       [responderDelegate_ respondsToSelector:@selector(viewGone:)])
2329     [responderDelegate_ viewGone:self];
2330   responderDelegate_.reset();
2332   [[NSNotificationCenter defaultCenter] removeObserver:self];
2334   [super dealloc];
2337 - (void)didChangeScreenParameters:(NSNotification*)notify {
2338   g_screen_info_up_to_date = false;
2341 - (void)setResponderDelegate:
2342             (NSObject<RenderWidgetHostViewMacDelegate>*)delegate {
2343   DCHECK(!responderDelegate_);
2344   responderDelegate_.reset([delegate retain]);
2347 - (void)resetCursorRects {
2348   if (currentCursor_) {
2349     [self addCursorRect:[self visibleRect] cursor:currentCursor_];
2350     [currentCursor_ setOnMouseEntered:YES];
2351   }
2354 - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event
2355                    consumed:(BOOL)consumed {
2356   [responderDelegate_ rendererHandledWheelEvent:event consumed:consumed];
2359 - (BOOL)respondsToSelector:(SEL)selector {
2360   // Trickiness: this doesn't mean "does this object's superclass respond to
2361   // this selector" but rather "does the -respondsToSelector impl from the
2362   // superclass say that this class responds to the selector".
2363   if ([super respondsToSelector:selector])
2364     return YES;
2366   if (responderDelegate_)
2367     return [responderDelegate_ respondsToSelector:selector];
2369   return NO;
2372 - (id)forwardingTargetForSelector:(SEL)selector {
2373   if ([responderDelegate_ respondsToSelector:selector])
2374     return responderDelegate_.get();
2376   return [super forwardingTargetForSelector:selector];
2379 - (void)setCanBeKeyView:(BOOL)can {
2380   canBeKeyView_ = can;
2383 - (BOOL)acceptsMouseEventsWhenInactive {
2384   // Some types of windows (balloons, always-on-top panels) want to accept mouse
2385   // clicks w/o the first click being treated as 'activation'. Same applies to
2386   // mouse move events.
2387   return [[self window] level] > NSNormalWindowLevel;
2390 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
2391   return [self acceptsMouseEventsWhenInactive];
2394 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
2395   takesFocusOnlyOnMouseDown_ = b;
2398 - (void)setCloseOnDeactivate:(BOOL)b {
2399   closeOnDeactivate_ = b;
2402 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
2403   NSWindow* window = [self window];
2404   // If this is a background window, don't handle mouse movement events. This
2405   // is the expected behavior on the Mac as evidenced by other applications.
2406   if ([theEvent type] == NSMouseMoved &&
2407       ![self acceptsMouseEventsWhenInactive] &&
2408       ![window isKeyWindow]) {
2409     return YES;
2410   }
2412   // Use hitTest to check whether the mouse is over a nonWebContentView - in
2413   // which case the mouse event should not be handled by the render host.
2414   const SEL nonWebContentViewSelector = @selector(nonWebContentView);
2415   NSView* contentView = [window contentView];
2416   NSView* view = [contentView hitTest:[theEvent locationInWindow]];
2417   // Traverse the superview hierarchy as the hitTest will return the frontmost
2418   // view, such as an NSTextView, while nonWebContentView may be specified by
2419   // its parent view.
2420   while (view) {
2421     if ([view respondsToSelector:nonWebContentViewSelector] &&
2422         [view performSelector:nonWebContentViewSelector]) {
2423       // The cursor is over a nonWebContentView - ignore this mouse event.
2424       return YES;
2425     }
2426     if ([view isKindOfClass:[self class]] && ![view isEqual:self] &&
2427         !hasOpenMouseDown_) {
2428       // The cursor is over an overlapping render widget. This check is done by
2429       // both views so the one that's returned by -hitTest: will end up
2430       // processing the event.
2431       // Note that while dragging, we only get events for the render view where
2432       // drag started, even if mouse is  actually over another view or outside
2433       // the window. Cocoa does this for us. We should handle these events and
2434       // not ignore (since there is no other render view to handle them). Thus
2435       // the |!hasOpenMouseDown_| check above.
2436       return YES;
2437     }
2438     view = [view superview];
2439   }
2440   return NO;
2443 - (void)mouseEvent:(NSEvent*)theEvent {
2444   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
2445   if (responderDelegate_ &&
2446       [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2447     BOOL handled = [responderDelegate_ handleEvent:theEvent];
2448     if (handled)
2449       return;
2450   }
2452   if ([self shouldIgnoreMouseEvent:theEvent]) {
2453     // If this is the first such event, send a mouse exit to the host view.
2454     if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
2455       WebMouseEvent exitEvent =
2456           WebInputEventFactory::mouseEvent(theEvent, self);
2457       exitEvent.type = WebInputEvent::MouseLeave;
2458       exitEvent.button = WebMouseEvent::ButtonNone;
2459       renderWidgetHostView_->ForwardMouseEvent(exitEvent);
2460     }
2461     mouseEventWasIgnored_ = YES;
2462     return;
2463   }
2465   if (mouseEventWasIgnored_) {
2466     // If this is the first mouse event after a previous event that was ignored
2467     // due to the hitTest, send a mouse enter event to the host view.
2468     if (renderWidgetHostView_->render_widget_host_) {
2469       WebMouseEvent enterEvent =
2470           WebInputEventFactory::mouseEvent(theEvent, self);
2471       enterEvent.type = WebInputEvent::MouseMove;
2472       enterEvent.button = WebMouseEvent::ButtonNone;
2473       renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2474     }
2475   }
2476   mouseEventWasIgnored_ = NO;
2478   // TODO(rohitrao): Probably need to handle other mouse down events here.
2479   if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2480     if (renderWidgetHostView_->render_widget_host_)
2481       renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2483     // Manually take focus after the click but before forwarding it to the
2484     // renderer.
2485     [[self window] makeFirstResponder:self];
2486   }
2488   // Don't cancel child popups; killing them on a mouse click would prevent the
2489   // user from positioning the insertion point in the text field spawning the
2490   // popup. A click outside the text field would cause the text field to drop
2491   // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2492   // the popup anyway, so we're OK.
2494   NSEventType type = [theEvent type];
2495   if (type == NSLeftMouseDown)
2496     hasOpenMouseDown_ = YES;
2497   else if (type == NSLeftMouseUp)
2498     hasOpenMouseDown_ = NO;
2500   // TODO(suzhe): We should send mouse events to the input method first if it
2501   // wants to handle them. But it won't work without implementing method
2502   // - (NSUInteger)characterIndexForPoint:.
2503   // See: http://code.google.com/p/chromium/issues/detail?id=47141
2504   // Instead of sending mouse events to the input method first, we now just
2505   // simply confirm all ongoing composition here.
2506   if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2507       type == NSOtherMouseDown) {
2508     [self confirmComposition];
2509   }
2511   const WebMouseEvent event =
2512       WebInputEventFactory::mouseEvent(theEvent, self);
2513   renderWidgetHostView_->ForwardMouseEvent(event);
2516 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2517   // |performKeyEquivalent:| is sent to all views of a window, not only down the
2518   // responder chain (cf. "Handling Key Equivalents" in
2519   // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2520   // ). We only want to handle key equivalents if we're first responder.
2521   if ([[self window] firstResponder] != self)
2522     return NO;
2524   // If we return |NO| from this function, cocoa will send the key event to
2525   // the menu and only if the menu does not process the event to |keyDown:|. We
2526   // want to send the event to a renderer _before_ sending it to the menu, so
2527   // we need to return |YES| for all events that might be swallowed by the menu.
2528   // We do not return |YES| for every keypress because we don't get |keyDown:|
2529   // events for keys that we handle this way.
2530   NSUInteger modifierFlags = [theEvent modifierFlags];
2531   if ((modifierFlags & NSCommandKeyMask) == 0) {
2532     // Make sure the menu does not contain key equivalents that don't
2533     // contain cmd.
2534     DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2535     return NO;
2536   }
2538   // Command key combinations are sent via performKeyEquivalent rather than
2539   // keyDown:. We just forward this on and if WebCore doesn't want to handle
2540   // it, we let the WebContentsView figure out how to reinject it.
2541   [self keyEvent:theEvent wasKeyEquivalent:YES];
2542   return YES;
2545 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2546   // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2547   // returned NO. If this function returns |YES|, Cocoa sends the event to
2548   // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2549   // to us instead of doing key view loop control, ctrl-left/right get handled
2550   // correctly, etc.
2551   // (However, there are still some keys that Cocoa swallows, e.g. the key
2552   // equivalent that Cocoa uses for toggling the input language. In this case,
2553   // that's actually a good thing, though -- see http://crbug.com/26115 .)
2554   return YES;
2557 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2558   if (responderDelegate_ &&
2559       [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2560     BOOL handled = [responderDelegate_ handleEvent:theEvent];
2561     if (handled)
2562       return kEventHandled;
2563   }
2565   [self keyEvent:theEvent wasKeyEquivalent:NO];
2566   return kEventHandled;
2569 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2570   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2571   DCHECK([theEvent type] != NSKeyDown ||
2572          !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2574   if ([theEvent type] == NSFlagsChanged) {
2575     // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2576     // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2577     int keyCode = [theEvent keyCode];
2578     if (!keyCode || keyCode == 10 || keyCode == 63)
2579       return;
2580   }
2582   // Don't cancel child popups; the key events are probably what's triggering
2583   // the popup in the first place.
2585   RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2586   DCHECK(widgetHost);
2588   NativeWebKeyboardEvent event(theEvent);
2590   // Force fullscreen windows to close on Escape so they won't keep the keyboard
2591   // grabbed or be stuck onscreen if the renderer is hanging.
2592   if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2593       event.windowsKeyCode == ui::VKEY_ESCAPE &&
2594       renderWidgetHostView_->pepper_fullscreen_window()) {
2595     RenderWidgetHostViewMac* parent =
2596         renderWidgetHostView_->fullscreen_parent_host_view();
2597     if (parent)
2598       parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2599     widgetHost->Shutdown();
2600     return;
2601   }
2603   // Suppress the escape key up event if necessary.
2604   if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2605     if (event.type == NativeWebKeyboardEvent::KeyUp)
2606       suppressNextEscapeKeyUp_ = NO;
2607     return;
2608   }
2610   // We only handle key down events and just simply forward other events.
2611   if ([theEvent type] != NSKeyDown) {
2612     widgetHost->ForwardKeyboardEvent(event);
2614     // Possibly autohide the cursor.
2615     if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2616       [NSCursor setHiddenUntilMouseMoves:YES];
2618     return;
2619   }
2621   base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2623   // Records the current marked text state, so that we can know if the marked
2624   // text was deleted or not after handling the key down event.
2625   BOOL oldHasMarkedText = hasMarkedText_;
2627   // This method should not be called recursively.
2628   DCHECK(!handlingKeyDown_);
2630   // Tells insertText: and doCommandBySelector: that we are handling a key
2631   // down event.
2632   handlingKeyDown_ = YES;
2634   // These variables might be set when handling the keyboard event.
2635   // Clear them here so that we can know whether they have changed afterwards.
2636   textToBeInserted_.clear();
2637   markedText_.clear();
2638   underlines_.clear();
2639   unmarkTextCalled_ = NO;
2640   hasEditCommands_ = NO;
2641   editCommands_.clear();
2643   // Before doing anything with a key down, check to see if plugin IME has been
2644   // cancelled, since the plugin host needs to be informed of that before
2645   // receiving the keydown.
2646   if ([theEvent type] == NSKeyDown)
2647     [self checkForPluginImeCancellation];
2649   // Sends key down events to input method first, then we can decide what should
2650   // be done according to input method's feedback.
2651   // If a plugin is active, bypass this step since events are forwarded directly
2652   // to the plugin IME.
2653   if (focusedPluginIdentifier_ == -1)
2654     [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2656   handlingKeyDown_ = NO;
2658   // Indicates if we should send the key event and corresponding editor commands
2659   // after processing the input method result.
2660   BOOL delayEventUntilAfterImeCompostion = NO;
2662   // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2663   // while an input method is composing or inserting a text.
2664   // Gmail checks this code in its onkeydown handler to stop auto-completing
2665   // e-mail addresses while composing a CJK text.
2666   // If the text to be inserted has only one character, then we don't need this
2667   // trick, because we'll send the text as a key press event instead.
2668   if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2669     NativeWebKeyboardEvent fakeEvent = event;
2670     fakeEvent.windowsKeyCode = 0xE5;  // VKEY_PROCESSKEY
2671     fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2672     fakeEvent.skip_in_browser = true;
2673     widgetHost->ForwardKeyboardEvent(fakeEvent);
2674     // If this key event was handled by the input method, but
2675     // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2676     // enqueued edit commands, then in order to let webkit handle them
2677     // correctly, we need to send the real key event and corresponding edit
2678     // commands after processing the input method result.
2679     // We shouldn't do this if a new marked text was set by the input method,
2680     // otherwise the new marked text might be cancelled by webkit.
2681     if (hasEditCommands_ && !hasMarkedText_)
2682       delayEventUntilAfterImeCompostion = YES;
2683   } else {
2684     if (!editCommands_.empty()) {
2685       widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2686           widgetHost->GetRoutingID(), editCommands_));
2687     }
2688     widgetHost->ForwardKeyboardEvent(event);
2689   }
2691   // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2692   // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2693   // be set to NULL. So we check it here and return immediately if it's NULL.
2694   if (!renderWidgetHostView_->render_widget_host_)
2695     return;
2697   // Then send keypress and/or composition related events.
2698   // If there was a marked text or the text to be inserted is longer than 1
2699   // character, then we send the text by calling ConfirmComposition().
2700   // Otherwise, if the text to be inserted only contains 1 character, then we
2701   // can just send a keypress event which is fabricated by changing the type of
2702   // the keydown event, so that we can retain all necessary informations, such
2703   // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2704   // prevent the browser from handling it again.
2705   // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2706   // handle BMP characters here, as we can always insert non-BMP characters as
2707   // text.
2708   BOOL textInserted = NO;
2709   if (textToBeInserted_.length() >
2710       ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2711     widgetHost->ImeConfirmComposition(
2712         textToBeInserted_, gfx::Range::InvalidRange(), false);
2713     textInserted = YES;
2714   }
2716   // Updates or cancels the composition. If some text has been inserted, then
2717   // we don't need to cancel the composition explicitly.
2718   if (hasMarkedText_ && markedText_.length()) {
2719     // Sends the updated marked text to the renderer so it can update the
2720     // composition node in WebKit.
2721     // When marked text is available, |selectedRange_| will be the range being
2722     // selected inside the marked text.
2723     widgetHost->ImeSetComposition(markedText_, underlines_,
2724                                   selectedRange_.location,
2725                                   NSMaxRange(selectedRange_));
2726   } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2727     if (unmarkTextCalled_) {
2728       widgetHost->ImeConfirmComposition(
2729           base::string16(), gfx::Range::InvalidRange(), false);
2730     } else {
2731       widgetHost->ImeCancelComposition();
2732     }
2733   }
2735   // If the key event was handled by the input method but it also generated some
2736   // edit commands, then we need to send the real key event and corresponding
2737   // edit commands here. This usually occurs when the input method wants to
2738   // finish current composition session but still wants the application to
2739   // handle the key event. See http://crbug.com/48161 for reference.
2740   if (delayEventUntilAfterImeCompostion) {
2741     // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2742     // with windowsKeyCode == 0xE5 has already been sent to webkit.
2743     // So before sending the real key down event, we need to send a fake key up
2744     // event to balance it.
2745     NativeWebKeyboardEvent fakeEvent = event;
2746     fakeEvent.type = blink::WebInputEvent::KeyUp;
2747     fakeEvent.skip_in_browser = true;
2748     widgetHost->ForwardKeyboardEvent(fakeEvent);
2749     // Not checking |renderWidgetHostView_->render_widget_host_| here because
2750     // a key event with |skip_in_browser| == true won't be handled by browser,
2751     // thus it won't destroy the widget.
2753     if (!editCommands_.empty()) {
2754       widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2755           widgetHost->GetRoutingID(), editCommands_));
2756     }
2757     widgetHost->ForwardKeyboardEvent(event);
2759     // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2760     // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2761     // be set to NULL. So we check it here and return immediately if it's NULL.
2762     if (!renderWidgetHostView_->render_widget_host_)
2763       return;
2764   }
2766   const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2767   // Only send a corresponding key press event if there is no marked text.
2768   if (!hasMarkedText_) {
2769     if (!textInserted && textToBeInserted_.length() == 1) {
2770       // If a single character was inserted, then we just send it as a keypress
2771       // event.
2772       event.type = blink::WebInputEvent::Char;
2773       event.text[0] = textToBeInserted_[0];
2774       event.text[1] = 0;
2775       event.skip_in_browser = true;
2776       widgetHost->ForwardKeyboardEvent(event);
2777     } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2778                [[theEvent characters] length] > 0 &&
2779                (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2780                 (hasEditCommands_ && editCommands_.empty()))) {
2781       // We don't get insertText: calls if ctrl or cmd is down, or the key event
2782       // generates an insert command. So synthesize a keypress event for these
2783       // cases, unless the key event generated any other command.
2784       event.type = blink::WebInputEvent::Char;
2785       event.skip_in_browser = true;
2786       widgetHost->ForwardKeyboardEvent(event);
2787     }
2788   }
2790   // Possibly autohide the cursor.
2791   if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2792     [NSCursor setHiddenUntilMouseMoves:YES];
2795 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2796   DCHECK(base::mac::IsOSLionOrLater());
2798   if ([event phase] != NSEventPhaseEnded &&
2799       [event phase] != NSEventPhaseCancelled) {
2800     return;
2801   }
2803   if (renderWidgetHostView_->render_widget_host_) {
2804     // History-swiping is not possible if the logic reaches this point.
2805     // Allow rubber-banding in both directions.
2806     bool canRubberbandLeft = true;
2807     bool canRubberbandRight = true;
2808     const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
2809         event, self, canRubberbandLeft, canRubberbandRight);
2810     renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2811   }
2813   if (endWheelMonitor_) {
2814     [NSEvent removeMonitor:endWheelMonitor_];
2815     endWheelMonitor_ = nil;
2816   }
2819 - (void)beginGestureWithEvent:(NSEvent*)event {
2820   [responderDelegate_ beginGestureWithEvent:event];
2822 - (void)endGestureWithEvent:(NSEvent*)event {
2823   [responderDelegate_ endGestureWithEvent:event];
2825 - (void)touchesMovedWithEvent:(NSEvent*)event {
2826   [responderDelegate_ touchesMovedWithEvent:event];
2828 - (void)touchesBeganWithEvent:(NSEvent*)event {
2829   [responderDelegate_ touchesBeganWithEvent:event];
2831 - (void)touchesCancelledWithEvent:(NSEvent*)event {
2832   [responderDelegate_ touchesCancelledWithEvent:event];
2834 - (void)touchesEndedWithEvent:(NSEvent*)event {
2835   [responderDelegate_ touchesEndedWithEvent:event];
2838 // This is invoked only on 10.8 or newer when the user taps a word using
2839 // three fingers.
2840 - (void)quickLookWithEvent:(NSEvent*)event {
2841   NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
2842   TextInputClientMac::GetInstance()->GetStringAtPoint(
2843       renderWidgetHostView_->render_widget_host_,
2844       gfx::Point(point.x, NSHeight([self frame]) - point.y),
2845       ^(NSAttributedString* string, NSPoint baselinePoint) {
2846           if (string && [string length] > 0) {
2847             dispatch_async(dispatch_get_main_queue(), ^{
2848                 [self showDefinitionForAttributedString:string
2849                                                 atPoint:baselinePoint];
2850             });
2851           }
2852       }
2853   );
2856 // This method handles 2 different types of hardware events.
2857 // (Apple does not distinguish between them).
2858 //  a. Scrolling the middle wheel of a mouse.
2859 //  b. Swiping on the track pad.
2861 // This method is responsible for 2 types of behavior:
2862 //  a. Scrolling the content of window.
2863 //  b. Navigating forwards/backwards in history.
2865 // This is a brief description of the logic:
2866 //  1. If the content can be scrolled, scroll the content.
2867 //     (This requires a roundtrip to blink to determine whether the content
2868 //      can be scrolled.)
2869 //     Once this logic is triggered, the navigate logic cannot be triggered
2870 //     until the gesture finishes.
2871 //  2. If the user is making a horizontal swipe, start the navigate
2872 //     forward/backwards UI.
2873 //     Once this logic is triggered, the user can either cancel or complete
2874 //     the gesture. If the user completes the gesture, all remaining touches
2875 //     are swallowed, and not allowed to scroll the content. If the user
2876 //     cancels the gesture, all remaining touches are forwarded to the content
2877 //     scroll logic. The user cannot trigger the navigation logic again.
2878 - (void)scrollWheel:(NSEvent*)event {
2879   if (responderDelegate_ &&
2880       [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2881     BOOL handled = [responderDelegate_ handleEvent:event];
2882     if (handled)
2883       return;
2884   }
2886   // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
2887   // the event is received even when the mouse cursor is no longer over the view
2888   // when the scrolling ends (e.g. if the tab was switched). This is necessary
2889   // for ending rubber-banding in such cases.
2890   if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
2891       !endWheelMonitor_) {
2892     endWheelMonitor_ =
2893       [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
2894       handler:^(NSEvent* blockEvent) {
2895           [self shortCircuitScrollWheelEvent:blockEvent];
2896           return blockEvent;
2897       }];
2898   }
2900   // This is responsible for content scrolling!
2901   if (renderWidgetHostView_->render_widget_host_) {
2902     BOOL canRubberbandLeft = [responderDelegate_ canRubberbandLeft:self];
2903     BOOL canRubberbandRight = [responderDelegate_ canRubberbandRight:self];
2904     const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
2905         event, self, canRubberbandLeft, canRubberbandRight);
2906     renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2907   }
2910 // Called repeatedly during a pinch gesture, with incremental change values.
2911 - (void)magnifyWithEvent:(NSEvent*)event {
2912   if (renderWidgetHostView_->render_widget_host_) {
2913     // Send a GesturePinchUpdate event.
2914     // Note that we don't attempt to bracket these by GesturePinchBegin/End (or
2915     // GestureSrollBegin/End) as is done for touchscreen.  Keeping track of when
2916     // a pinch is active would take a little more work here, and we don't need
2917     // it for anything yet.
2918     const WebGestureEvent& webEvent =
2919         WebInputEventFactory::gestureEvent(event, self);
2920     renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(webEvent);
2921   }
2924 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
2925   NSWindow* oldWindow = [self window];
2927   NSNotificationCenter* notificationCenter =
2928       [NSNotificationCenter defaultCenter];
2930   // Backing property notifications crash on 10.6 when building with the 10.7
2931   // SDK, see http://crbug.com/260595.
2932   static BOOL supportsBackingPropertiesNotification =
2933       SupportsBackingPropertiesChangedNotification();
2935   if (oldWindow) {
2936     if (supportsBackingPropertiesNotification) {
2937       [notificationCenter
2938           removeObserver:self
2939                     name:NSWindowDidChangeBackingPropertiesNotification
2940                   object:oldWindow];
2941     }
2942     [notificationCenter
2943         removeObserver:self
2944                   name:NSWindowDidMoveNotification
2945                 object:oldWindow];
2946     [notificationCenter
2947         removeObserver:self
2948                   name:NSWindowDidEndLiveResizeNotification
2949                 object:oldWindow];
2950   }
2951   if (newWindow) {
2952     if (supportsBackingPropertiesNotification) {
2953       [notificationCenter
2954           addObserver:self
2955              selector:@selector(windowDidChangeBackingProperties:)
2956                  name:NSWindowDidChangeBackingPropertiesNotification
2957                object:newWindow];
2958     }
2959     [notificationCenter
2960         addObserver:self
2961            selector:@selector(windowChangedGlobalFrame:)
2962                name:NSWindowDidMoveNotification
2963              object:newWindow];
2964     [notificationCenter
2965         addObserver:self
2966            selector:@selector(windowChangedGlobalFrame:)
2967                name:NSWindowDidEndLiveResizeNotification
2968              object:newWindow];
2969   }
2972 - (void)updateScreenProperties{
2973   renderWidgetHostView_->UpdateBackingStoreScaleFactor();
2974   renderWidgetHostView_->UpdateDisplayLink();
2977 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
2978 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
2979   // Background tabs check if their scale factor or vsync properties changed
2980   // when they are added to a window.
2982   // Allocating a CGLayerRef with the current scale factor immediately from
2983   // this handler doesn't work. Schedule the backing store update on the
2984   // next runloop cycle, then things are read for CGLayerRef allocations to
2985   // work.
2986   [self performSelector:@selector(updateScreenProperties)
2987              withObject:nil
2988              afterDelay:0];
2991 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
2992   renderWidgetHostView_->UpdateScreenInfo(
2993       renderWidgetHostView_->GetNativeView());
2996 - (void)setFrameSize:(NSSize)newSize {
2997   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
2999   // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
3000   // -setFrame: isn't neccessary.
3001   [super setFrameSize:newSize];
3003   if (!renderWidgetHostView_->render_widget_host_)
3004     return;
3006   // Move the CALayers to their positions in the new view size. Note that
3007   // this will not draw anything because the non-background layers' sizes
3008   // didn't actually change.
3009   renderWidgetHostView_->LayoutLayers();
3011   renderWidgetHostView_->render_widget_host_->SendScreenRects();
3012   renderWidgetHostView_->render_widget_host_->WasResized();
3013   if (renderWidgetHostView_->delegated_frame_host_)
3014     renderWidgetHostView_->delegated_frame_host_->WasResized();
3016   // Wait for the frame that WasResize might have requested. If the view is
3017   // being made visible at a new size, then this call will have no effect
3018   // because the view widget is still hidden, and the pause call in WasShown
3019   // will have this effect for us.
3020   renderWidgetHostView_->PauseForPendingResizeOrRepaintsAndDraw();
3023 - (BOOL)canBecomeKeyView {
3024   if (!renderWidgetHostView_->render_widget_host_)
3025     return NO;
3027   return canBeKeyView_;
3030 - (BOOL)acceptsFirstResponder {
3031   if (!renderWidgetHostView_->render_widget_host_)
3032     return NO;
3034   return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
3037 - (BOOL)becomeFirstResponder {
3038   if (!renderWidgetHostView_->render_widget_host_)
3039     return NO;
3041   renderWidgetHostView_->render_widget_host_->Focus();
3042   renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
3043   renderWidgetHostView_->SetTextInputActive(true);
3045   // Cancel any onging composition text which was left before we lost focus.
3046   // TODO(suzhe): We should do it in -resignFirstResponder: method, but
3047   // somehow that method won't be called when switching among different tabs.
3048   // See http://crbug.com/47209
3049   [self cancelComposition];
3051   NSNumber* direction = [NSNumber numberWithUnsignedInteger:
3052       [[self window] keyViewSelectionDirection]];
3053   NSDictionary* userInfo =
3054       [NSDictionary dictionaryWithObject:direction
3055                                   forKey:kSelectionDirection];
3056   [[NSNotificationCenter defaultCenter]
3057       postNotificationName:kViewDidBecomeFirstResponder
3058                     object:self
3059                   userInfo:userInfo];
3061   return YES;
3064 - (BOOL)resignFirstResponder {
3065   renderWidgetHostView_->SetTextInputActive(false);
3066   if (!renderWidgetHostView_->render_widget_host_)
3067     return YES;
3069   if (closeOnDeactivate_)
3070     renderWidgetHostView_->KillSelf();
3072   renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
3073   renderWidgetHostView_->render_widget_host_->Blur();
3075   // We should cancel any onging composition whenever RWH's Blur() method gets
3076   // called, because in this case, webkit will confirm the ongoing composition
3077   // internally.
3078   [self cancelComposition];
3080   return YES;
3083 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
3084   if (responderDelegate_ &&
3085       [responderDelegate_
3086           respondsToSelector:@selector(validateUserInterfaceItem:
3087                                                      isValidItem:)]) {
3088     BOOL valid;
3089     BOOL known =
3090         [responderDelegate_ validateUserInterfaceItem:item isValidItem:&valid];
3091     if (known)
3092       return valid;
3093   }
3095   SEL action = [item action];
3097   if (action == @selector(stopSpeaking:)) {
3098     return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3099            renderWidgetHostView_->IsSpeaking();
3100   }
3101   if (action == @selector(startSpeaking:)) {
3102     return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3103            renderWidgetHostView_->SupportsSpeech();
3104   }
3106   // For now, these actions are always enabled for render view,
3107   // this is sub-optimal.
3108   // TODO(suzhe): Plumb the "can*" methods up from WebCore.
3109   if (action == @selector(undo:) ||
3110       action == @selector(redo:) ||
3111       action == @selector(cut:) ||
3112       action == @selector(copy:) ||
3113       action == @selector(copyToFindPboard:) ||
3114       action == @selector(paste:) ||
3115       action == @selector(pasteAndMatchStyle:)) {
3116     return renderWidgetHostView_->render_widget_host_->IsRenderView();
3117   }
3119   return editCommand_helper_->IsMenuItemEnabled(action, self);
3122 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
3123   return renderWidgetHostView_.get();
3126 // Determine whether we should autohide the cursor (i.e., hide it until mouse
3127 // move) for the given event. Customize here to be more selective about which
3128 // key presses to autohide on.
3129 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
3130   return ([event type] == NSKeyDown &&
3131              !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
3134 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
3135                                          index:(NSUInteger)index
3136                                       maxCount:(NSUInteger)maxCount {
3137   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3138   NSUInteger totalLength = [fullArray count];
3139   if (index >= totalLength)
3140     return nil;
3141   NSUInteger length = MIN(totalLength - index, maxCount);
3142   return [fullArray subarrayWithRange:NSMakeRange(index, length)];
3145 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
3146   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3147   return [fullArray count];
3150 - (id)accessibilityAttributeValue:(NSString *)attribute {
3151   BrowserAccessibilityManager* manager =
3152       renderWidgetHostView_->GetBrowserAccessibilityManager();
3154   // Contents specifies document view of RenderWidgetHostViewCocoa provided by
3155   // BrowserAccessibilityManager. Children includes all subviews in addition to
3156   // contents. Currently we do not have subviews besides the document view.
3157   if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
3158           [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
3159       manager) {
3160     return [NSArray arrayWithObjects:manager->
3161         GetRoot()->ToBrowserAccessibilityCocoa(), nil];
3162   } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
3163     return NSAccessibilityScrollAreaRole;
3164   }
3165   id ret = [super accessibilityAttributeValue:attribute];
3166   return ret;
3169 - (NSArray*)accessibilityAttributeNames {
3170   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
3171   [ret addObject:NSAccessibilityContentsAttribute];
3172   [ret addObjectsFromArray:[super accessibilityAttributeNames]];
3173   return ret;
3176 - (id)accessibilityHitTest:(NSPoint)point {
3177   if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
3178     return self;
3179   NSPoint pointInWindow = [[self window] convertScreenToBase:point];
3180   NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
3181   localPoint.y = NSHeight([self bounds]) - localPoint.y;
3182   BrowserAccessibilityCocoa* root = renderWidgetHostView_->
3183       GetBrowserAccessibilityManager()->
3184           GetRoot()->ToBrowserAccessibilityCocoa();
3185   id obj = [root accessibilityHitTest:localPoint];
3186   return obj;
3189 - (BOOL)accessibilityIsIgnored {
3190   return !renderWidgetHostView_->GetBrowserAccessibilityManager();
3193 - (NSUInteger)accessibilityGetIndexOf:(id)child {
3194   BrowserAccessibilityManager* manager =
3195       renderWidgetHostView_->GetBrowserAccessibilityManager();
3196   // Only child is root.
3197   if (manager &&
3198       manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
3199     return 0;
3200   } else {
3201     return NSNotFound;
3202   }
3205 - (id)accessibilityFocusedUIElement {
3206   BrowserAccessibilityManager* manager =
3207       renderWidgetHostView_->GetBrowserAccessibilityManager();
3208   if (manager) {
3209     BrowserAccessibility* focused_item = manager->GetFocus(NULL);
3210     DCHECK(focused_item);
3211     if (focused_item) {
3212       BrowserAccessibilityCocoa* focused_item_cocoa =
3213           focused_item->ToBrowserAccessibilityCocoa();
3214       DCHECK(focused_item_cocoa);
3215       if (focused_item_cocoa)
3216         return focused_item_cocoa;
3217     }
3218   }
3219   return [super accessibilityFocusedUIElement];
3222 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
3223 // with minor modifications for code style and commenting.
3225 //  The 'public' interface is -setToolTipAtMousePoint:. This differs from
3226 // -setToolTip: in that the updated tooltip takes effect immediately,
3227 //  without the user's having to move the mouse out of and back into the view.
3229 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
3230 // the view, which in turn requires overriding some internal tracking-rect
3231 // methods (to keep track of its owner & userdata, which need to be filled out
3232 // in the fake events.) --snej 7/6/09
3236  * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3237  *           (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
3239  * Redistribution and use in source and binary forms, with or without
3240  * modification, are permitted provided that the following conditions
3241  * are met:
3243  * 1.  Redistributions of source code must retain the above copyright
3244  *     notice, this list of conditions and the following disclaimer.
3245  * 2.  Redistributions in binary form must reproduce the above copyright
3246  *     notice, this list of conditions and the following disclaimer in the
3247  *     documentation and/or other materials provided with the distribution.
3248  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
3249  *     its contributors may be used to endorse or promote products derived
3250  *     from this software without specific prior written permission.
3252  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
3253  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3254  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3255  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
3256  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3257  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
3258  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3259  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3260  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3261  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3262  */
3264 // Any non-zero value will do, but using something recognizable might help us
3265 // debug some day.
3266 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
3268 // Override of a public NSView method, replacing the inherited functionality.
3269 // See above for rationale.
3270 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
3271                                owner:(id)owner
3272                             userData:(void *)data
3273                         assumeInside:(BOOL)assumeInside {
3274   DCHECK(trackingRectOwner_ == nil);
3275   trackingRectOwner_ = owner;
3276   trackingRectUserData_ = data;
3277   return kTrackingRectTag;
3280 // Override of (apparently) a private NSView method(!) See above for rationale.
3281 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
3282                                 owner:(id)owner
3283                              userData:(void *)data
3284                          assumeInside:(BOOL)assumeInside
3285                        useTrackingNum:(int)tag {
3286   DCHECK(tag == 0 || tag == kTrackingRectTag);
3287   DCHECK(trackingRectOwner_ == nil);
3288   trackingRectOwner_ = owner;
3289   trackingRectUserData_ = data;
3290   return kTrackingRectTag;
3293 // Override of (apparently) a private NSView method(!) See above for rationale.
3294 - (void)_addTrackingRects:(NSRect *)rects
3295                     owner:(id)owner
3296              userDataList:(void **)userDataList
3297          assumeInsideList:(BOOL *)assumeInsideList
3298              trackingNums:(NSTrackingRectTag *)trackingNums
3299                     count:(int)count {
3300   DCHECK(count == 1);
3301   DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3302   DCHECK(trackingRectOwner_ == nil);
3303   trackingRectOwner_ = owner;
3304   trackingRectUserData_ = userDataList[0];
3305   trackingNums[0] = kTrackingRectTag;
3308 // Override of a public NSView method, replacing the inherited functionality.
3309 // See above for rationale.
3310 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3311   if (tag == 0)
3312     return;
3314   if (tag == kTrackingRectTag) {
3315     trackingRectOwner_ = nil;
3316     return;
3317   }
3319   if (tag == lastToolTipTag_) {
3320     [super removeTrackingRect:tag];
3321     lastToolTipTag_ = 0;
3322     return;
3323   }
3325   // If any other tracking rect is being removed, we don't know how it was
3326   // created and it's possible there's a leak involved (see Radar 3500217).
3327   NOTREACHED();
3330 // Override of (apparently) a private NSView method(!)
3331 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3332   for (int i = 0; i < count; ++i) {
3333     int tag = tags[i];
3334     if (tag == 0)
3335       continue;
3336     DCHECK(tag == kTrackingRectTag);
3337     trackingRectOwner_ = nil;
3338   }
3341 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3342 - (void)_sendToolTipMouseExited {
3343   // Nothing matters except window, trackingNumber, and userData.
3344   int windowNumber = [[self window] windowNumber];
3345   NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3346                                               location:NSZeroPoint
3347                                          modifierFlags:0
3348                                              timestamp:0
3349                                           windowNumber:windowNumber
3350                                                context:NULL
3351                                            eventNumber:0
3352                                         trackingNumber:kTrackingRectTag
3353                                               userData:trackingRectUserData_];
3354   [trackingRectOwner_ mouseExited:fakeEvent];
3357 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3358 - (void)_sendToolTipMouseEntered {
3359   // Nothing matters except window, trackingNumber, and userData.
3360   int windowNumber = [[self window] windowNumber];
3361   NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3362                                               location:NSZeroPoint
3363                                          modifierFlags:0
3364                                              timestamp:0
3365                                           windowNumber:windowNumber
3366                                                context:NULL
3367                                            eventNumber:0
3368                                         trackingNumber:kTrackingRectTag
3369                                               userData:trackingRectUserData_];
3370   [trackingRectOwner_ mouseEntered:fakeEvent];
3373 // Sets the view's current tooltip, to be displayed at the current mouse
3374 // location. (This does not make the tooltip appear -- as usual, it only
3375 // appears after a delay.) Pass null to remove the tooltip.
3376 - (void)setToolTipAtMousePoint:(NSString *)string {
3377   NSString *toolTip = [string length] == 0 ? nil : string;
3378   if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3379       (!toolTip && !toolTip_)) {
3380     return;
3381   }
3383   if (toolTip_) {
3384     [self _sendToolTipMouseExited];
3385   }
3387   toolTip_.reset([toolTip copy]);
3389   if (toolTip) {
3390     // See radar 3500217 for why we remove all tooltips
3391     // rather than just the single one we created.
3392     [self removeAllToolTips];
3393     NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3394     lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3395                                      owner:self
3396                                   userData:NULL];
3397     [self _sendToolTipMouseEntered];
3398   }
3401 // NSView calls this to get the text when displaying the tooltip.
3402 - (NSString *)view:(NSView *)view
3403   stringForToolTip:(NSToolTipTag)tag
3404              point:(NSPoint)point
3405           userData:(void *)data {
3406   return [[toolTip_ copy] autorelease];
3409 // Below is our NSTextInputClient implementation.
3411 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3412 // functions to process this event.
3414 // [WebHTMLView keyDown] ->
3415 //     EventHandler::keyEvent() ->
3416 //     ...
3417 //     [WebEditorClient handleKeyboardEvent] ->
3418 //     [WebHTMLView _interceptEditingKeyEvent] ->
3419 //     [NSResponder interpretKeyEvents] ->
3420 //     [WebHTMLView insertText] ->
3421 //     Editor::insertText()
3423 // Unfortunately, it is hard for Chromium to use this implementation because
3424 // it causes key-typing jank.
3425 // RenderWidgetHostViewMac is running in a browser process. On the other
3426 // hand, Editor and EventHandler are running in a renderer process.
3427 // So, if we used this implementation, a NSKeyDown event is dispatched to
3428 // the following functions of Chromium.
3430 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3431 //     |Sync IPC (KeyDown)| (*1) ->
3432 //     EventHandler::keyEvent() (renderer) ->
3433 //     ...
3434 //     EditorClientImpl::handleKeyboardEvent() (renderer) ->
3435 //     |Sync IPC| (*2) ->
3436 //     [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3437 //     [self interpretKeyEvents] ->
3438 //     [RenderWidgetHostViewMac insertText] (browser) ->
3439 //     |Async IPC| ->
3440 //     Editor::insertText() (renderer)
3442 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3443 // result of EventHandler::keyEvent().
3444 // (*2) we need to wait until this call finishes since WebEditorClient uses
3445 // the result of [WebHTMLView _interceptEditingKeyEvent].
3447 // This needs many sync IPC messages sent between a browser and a renderer for
3448 // each key event, which would probably result in key-typing jank.
3449 // To avoid this problem, this implementation processes key events (and input
3450 // method events) totally in a browser process and sends asynchronous input
3451 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3452 // renderer process.
3454 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3455 //     |Async IPC (RawKeyDown)| ->
3456 //     [self interpretKeyEvents] ->
3457 //     [RenderWidgetHostViewMac insertText] (browser) ->
3458 //     |Async IPC (Char)| ->
3459 //     Editor::insertText() (renderer)
3461 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3462 // make any key-typing jank. --hbono 7/23/09
3464 extern "C" {
3465 extern NSString *NSTextInputReplacementRangeAttributeName;
3468 - (NSArray *)validAttributesForMarkedText {
3469   // This code is just copied from WebKit except renaming variables.
3470   if (!validAttributesForMarkedText_) {
3471     validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3472         NSUnderlineStyleAttributeName,
3473         NSUnderlineColorAttributeName,
3474         NSMarkedClauseSegmentAttributeName,
3475         NSTextInputReplacementRangeAttributeName,
3476         nil]);
3477   }
3478   return validAttributesForMarkedText_.get();
3481 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3482   DCHECK([self window]);
3483   // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3484   // coordinates (upper left origin). Scroll offsets will be taken care of in
3485   // the renderer.
3486   thePoint = [[self window] convertScreenToBase:thePoint];
3487   thePoint = [self convertPoint:thePoint fromView:nil];
3488   thePoint.y = NSHeight([self frame]) - thePoint.y;
3490   NSUInteger index =
3491       TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3492           renderWidgetHostView_->render_widget_host_,
3493           gfx::Point(thePoint.x, thePoint.y));
3494   return index;
3497 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3498                              actualRange:(NSRangePointer)actualRange {
3499   NSRect rect;
3500   if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3501           theRange,
3502           &rect,
3503           actualRange)) {
3504     rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3505         renderWidgetHostView_->render_widget_host_, theRange);
3507     // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3508     if (actualRange)
3509       *actualRange = theRange;
3510   }
3512   // The returned rectangle is in WebKit coordinates (upper left origin), so
3513   // flip the coordinate system.
3514   NSRect viewFrame = [self frame];
3515   rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3516   return rect;
3519 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3520                          actualRange:(NSRangePointer)actualRange {
3521   NSRect rect = [self firstViewRectForCharacterRange:theRange
3522                                          actualRange:actualRange];
3524   // Convert into screen coordinates for return.
3525   rect = [self convertRect:rect toView:nil];
3526   rect.origin = [[self window] convertBaseToScreen:rect.origin];
3527   return rect;
3530 - (NSRange)markedRange {
3531   // An input method calls this method to check if an application really has
3532   // a text being composed when hasMarkedText call returns true.
3533   // Returns the range saved in the setMarkedText method so the input method
3534   // calls the setMarkedText method and we can update the composition node
3535   // there. (When this method returns an empty range, the input method doesn't
3536   // call the setMarkedText method.)
3537   return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3540 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3541     actualRange:(NSRangePointer)actualRange {
3542   // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3543   if (actualRange)
3544     *actualRange = range;
3545   NSAttributedString* str =
3546       TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3547           renderWidgetHostView_->render_widget_host_, range);
3548   return str;
3551 - (NSInteger)conversationIdentifier {
3552   return reinterpret_cast<NSInteger>(self);
3555 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3556 // nil when the caret is in non-editable content or password box to avoid
3557 // making input methods do their work.
3558 - (NSTextInputContext *)inputContext {
3559   if (focusedPluginIdentifier_ != -1)
3560     return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3562   switch(renderWidgetHostView_->text_input_type_) {
3563     case ui::TEXT_INPUT_TYPE_NONE:
3564     case ui::TEXT_INPUT_TYPE_PASSWORD:
3565       return nil;
3566     default:
3567       return [super inputContext];
3568   }
3571 - (BOOL)hasMarkedText {
3572   // An input method calls this function to figure out whether or not an
3573   // application is really composing a text. If it is composing, it calls
3574   // the markedRange method, and maybe calls the setMarkedText method.
3575   // It seems an input method usually calls this function when it is about to
3576   // cancel an ongoing composition. If an application has a non-empty marked
3577   // range, it calls the setMarkedText method to delete the range.
3578   return hasMarkedText_;
3581 - (void)unmarkText {
3582   // Delete the composition node of the renderer and finish an ongoing
3583   // composition.
3584   // It seems an input method calls the setMarkedText method and set an empty
3585   // text when it cancels an ongoing composition, i.e. I have never seen an
3586   // input method calls this method.
3587   hasMarkedText_ = NO;
3588   markedText_.clear();
3589   underlines_.clear();
3591   // If we are handling a key down event, then ConfirmComposition() will be
3592   // called in keyEvent: method.
3593   if (!handlingKeyDown_) {
3594     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3595         base::string16(), gfx::Range::InvalidRange(), false);
3596   } else {
3597     unmarkTextCalled_ = YES;
3598   }
3601 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3602                               replacementRange:(NSRange)replacementRange {
3603   // An input method updates the composition string.
3604   // We send the given text and range to the renderer so it can update the
3605   // composition node of WebKit.
3606   // TODO(suzhe): It's hard for us to support replacementRange without accessing
3607   // the full web content.
3608   BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3609   NSString* im_text = isAttributedString ? [string string] : string;
3610   int length = [im_text length];
3612   // |markedRange_| will get set on a callback from ImeSetComposition().
3613   selectedRange_ = newSelRange;
3614   markedText_ = base::SysNSStringToUTF16(im_text);
3615   hasMarkedText_ = (length > 0);
3617   underlines_.clear();
3618   if (isAttributedString) {
3619     ExtractUnderlines(string, &underlines_);
3620   } else {
3621     // Use a thin black underline by default.
3622     underlines_.push_back(blink::WebCompositionUnderline(
3623         0, length, SK_ColorBLACK, false, SK_ColorTRANSPARENT));
3624   }
3626   // If we are handling a key down event, then SetComposition() will be
3627   // called in keyEvent: method.
3628   // Input methods of Mac use setMarkedText calls with an empty text to cancel
3629   // an ongoing composition. So, we should check whether or not the given text
3630   // is empty to update the input method state. (Our input method backend can
3631   // automatically cancels an ongoing composition when we send an empty text.
3632   // So, it is OK to send an empty text to the renderer.)
3633   if (!handlingKeyDown_) {
3634     renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3635         markedText_, underlines_,
3636         newSelRange.location, NSMaxRange(newSelRange));
3637   }
3640 - (void)doCommandBySelector:(SEL)selector {
3641   // An input method calls this function to dispatch an editing command to be
3642   // handled by this view.
3643   if (selector == @selector(noop:))
3644     return;
3646   std::string command(
3647       [RenderWidgetHostViewMacEditCommandHelper::
3648           CommandNameForSelector(selector) UTF8String]);
3650   // If this method is called when handling a key down event, then we need to
3651   // handle the command in the key event handler. Otherwise we can just handle
3652   // it here.
3653   if (handlingKeyDown_) {
3654     hasEditCommands_ = YES;
3655     // We ignore commands that insert characters, because this was causing
3656     // strange behavior (e.g. tab always inserted a tab rather than moving to
3657     // the next field on the page).
3658     if (!StartsWithASCII(command, "insert", false))
3659       editCommands_.push_back(EditCommand(command, ""));
3660   } else {
3661     RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3662     rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
3663                                               command, ""));
3664   }
3667 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
3668   // An input method has characters to be inserted.
3669   // Same as Linux, Mac calls this method not only:
3670   // * when an input method finishs composing text, but also;
3671   // * when we type an ASCII character (without using input methods).
3672   // When we aren't using input methods, we should send the given character as
3673   // a Char event so it is dispatched to an onkeypress() event handler of
3674   // JavaScript.
3675   // On the other hand, when we are using input methods, we should send the
3676   // given characters as an input method event and prevent the characters from
3677   // being dispatched to onkeypress() event handlers.
3678   // Text inserting might be initiated by other source instead of keyboard
3679   // events, such as the Characters dialog. In this case the text should be
3680   // sent as an input method event as well.
3681   // TODO(suzhe): It's hard for us to support replacementRange without accessing
3682   // the full web content.
3683   BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3684   NSString* im_text = isAttributedString ? [string string] : string;
3685   if (handlingKeyDown_) {
3686     textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
3687   } else {
3688     gfx::Range replacement_range(replacementRange);
3689     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3690         base::SysNSStringToUTF16(im_text), replacement_range, false);
3691   }
3693   // Inserting text will delete all marked text automatically.
3694   hasMarkedText_ = NO;
3697 - (void)insertText:(id)string {
3698   [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
3701 - (void)viewDidMoveToWindow {
3702   if ([self window])
3703     [self updateScreenProperties];
3705   if (canBeKeyView_) {
3706     NSWindow* newWindow = [self window];
3707     // Pointer comparison only, since we don't know if lastWindow_ is still
3708     // valid.
3709     if (newWindow) {
3710       // If we move into a new window, refresh the frame information. We
3711       // don't need to do it if it was the same window as it used to be in,
3712       // since that case is covered by WasShown(). We only want to do this for
3713       // real browser views, not popups.
3714       if (newWindow != lastWindow_) {
3715         lastWindow_ = newWindow;
3716         renderWidgetHostView_->WindowFrameChanged();
3717       }
3718     }
3719   }
3721   // If we switch windows (or are removed from the view hierarchy), cancel any
3722   // open mouse-downs.
3723   if (hasOpenMouseDown_) {
3724     WebMouseEvent event;
3725     event.type = WebInputEvent::MouseUp;
3726     event.button = WebMouseEvent::ButtonLeft;
3727     renderWidgetHostView_->ForwardMouseEvent(event);
3729     hasOpenMouseDown_ = NO;
3730   }
3733 - (void)undo:(id)sender {
3734   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3735   if (web_contents)
3736     web_contents->Undo();
3739 - (void)redo:(id)sender {
3740   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3741   if (web_contents)
3742     web_contents->Redo();
3745 - (void)cut:(id)sender {
3746   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3747   if (web_contents)
3748     web_contents->Cut();
3751 - (void)copy:(id)sender {
3752   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3753   if (web_contents)
3754     web_contents->Copy();
3757 - (void)copyToFindPboard:(id)sender {
3758   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3759   if (web_contents)
3760     web_contents->CopyToFindPboard();
3763 - (void)paste:(id)sender {
3764   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3765   if (web_contents)
3766     web_contents->Paste();
3769 - (void)pasteAndMatchStyle:(id)sender {
3770   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3771   if (web_contents)
3772     web_contents->PasteAndMatchStyle();
3775 - (void)selectAll:(id)sender {
3776   // editCommand_helper_ adds implementations for most NSResponder methods
3777   // dynamically. But the renderer side only sends selection results back to
3778   // the browser if they were triggered by a keyboard event or went through
3779   // one of the Select methods on RWH. Since selectAll: is called from the
3780   // menu handler, neither is true.
3781   // Explicitly call SelectAll() here to make sure the renderer returns
3782   // selection results.
3783   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3784   if (web_contents)
3785     web_contents->SelectAll();
3788 - (void)startSpeaking:(id)sender {
3789   renderWidgetHostView_->SpeakSelection();
3792 - (void)stopSpeaking:(id)sender {
3793   renderWidgetHostView_->StopSpeaking();
3796 - (void)cancelComposition {
3797   if (!hasMarkedText_)
3798     return;
3800   // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
3801   // doesn't call any NSTextInput functions, such as setMarkedText or
3802   // insertText. So, we need to send an IPC message to a renderer so it can
3803   // delete the composition node.
3804   NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3805   [currentInputManager markedTextAbandoned:self];
3807   hasMarkedText_ = NO;
3808   // Should not call [self unmarkText] here, because it'll send unnecessary
3809   // cancel composition IPC message to the renderer.
3812 - (void)confirmComposition {
3813   if (!hasMarkedText_)
3814     return;
3816   if (renderWidgetHostView_->render_widget_host_)
3817     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3818         base::string16(), gfx::Range::InvalidRange(), false);
3820   [self cancelComposition];
3823 - (void)setPluginImeActive:(BOOL)active {
3824   if (active == pluginImeActive_)
3825     return;
3827   pluginImeActive_ = active;
3828   if (!active) {
3829     [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
3830     renderWidgetHostView_->PluginImeCompositionCompleted(
3831         base::string16(), focusedPluginIdentifier_);
3832   }
3835 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
3836   if (focused)
3837     focusedPluginIdentifier_ = pluginId;
3838   else if (focusedPluginIdentifier_ == pluginId)
3839     focusedPluginIdentifier_ = -1;
3841   // Whenever plugin focus changes, plugin IME resets.
3842   [self setPluginImeActive:NO];
3845 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
3846   if (!pluginImeActive_)
3847     return false;
3849   ComplexTextInputPanel* inputPanel =
3850       [ComplexTextInputPanel sharedComplexTextInputPanel];
3851   NSString* composited_string = nil;
3852   BOOL handled = [inputPanel interpretKeyEvent:event
3853                                         string:&composited_string];
3854   if (composited_string) {
3855     renderWidgetHostView_->PluginImeCompositionCompleted(
3856         base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
3857     pluginImeActive_ = NO;
3858   }
3859   return handled;
3862 - (void)checkForPluginImeCancellation {
3863   if (pluginImeActive_ &&
3864       ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
3865     renderWidgetHostView_->PluginImeCompositionCompleted(
3866         base::string16(), focusedPluginIdentifier_);
3867     pluginImeActive_ = NO;
3868   }
3871 // Overriding a NSResponder method to support application services.
3873 - (id)validRequestorForSendType:(NSString*)sendType
3874                      returnType:(NSString*)returnType {
3875   id requestor = nil;
3876   BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
3877   BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
3878   BOOL hasText = !renderWidgetHostView_->selected_text().empty();
3879   BOOL takesText =
3880       renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
3882   if (sendTypeIsString && hasText && !returnType) {
3883     requestor = self;
3884   } else if (!sendType && returnTypeIsString && takesText) {
3885     requestor = self;
3886   } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
3887     requestor = self;
3888   } else {
3889     requestor = [super validRequestorForSendType:sendType
3890                                       returnType:returnType];
3891   }
3892   return requestor;
3895 - (void)viewWillStartLiveResize {
3896   [super viewWillStartLiveResize];
3897   RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3898   if (widget)
3899     widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
3902 - (void)viewDidEndLiveResize {
3903   [super viewDidEndLiveResize];
3904   RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3905   if (widget)
3906     widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
3909 - (void)updateCursor:(NSCursor*)cursor {
3910   if (currentCursor_ == cursor)
3911     return;
3913   currentCursor_.reset([cursor retain]);
3914   [[self window] invalidateCursorRectsForView:self];
3917 - (void)popupWindowWillClose:(NSNotification *)notification {
3918   renderWidgetHostView_->KillSelf();
3921 @end
3924 // Supporting application services
3926 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
3928 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
3929                              types:(NSArray*)types {
3930   const std::string& str = renderWidgetHostView_->selected_text();
3931   if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
3933   base::scoped_nsobject<NSString> text(
3934       [[NSString alloc] initWithUTF8String:str.c_str()]);
3935   NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
3936   [pboard declareTypes:toDeclare owner:nil];
3937   return [pboard setString:text forType:NSStringPboardType];
3940 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
3941   NSString *string = [pboard stringForType:NSStringPboardType];
3942   if (!string) return NO;
3944   // If the user is currently using an IME, confirm the IME input,
3945   // and then insert the text from the service, the same as TextEdit and Safari.
3946   [self confirmComposition];
3947   [self insertText:string];
3948   return YES;
3951 - (BOOL)isOpaque {
3952   return YES;
3955 // "-webkit-app-region: drag | no-drag" is implemented on Mac by excluding
3956 // regions that are not draggable. (See ControlRegionView in
3957 // native_app_window_cocoa.mm). This requires the render host view to be
3958 // draggable by default.
3959 - (BOOL)mouseDownCanMoveWindow {
3960   return YES;
3963 @end