cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / content / browser / renderer_host / render_widget_host_view_mac.mm
blob1062eaf41577c2f222388e4af621eddb2df9a6e3
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/logging.h"
17 #include "base/mac/mac_util.h"
18 #include "base/mac/scoped_cftyperef.h"
19 #import "base/mac/scoped_nsobject.h"
20 #include "base/mac/sdk_forward_declarations.h"
21 #include "base/message_loop/message_loop.h"
22 #include "base/metrics/histogram.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/strings/sys_string_conversions.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/sys_info.h"
28 #include "base/trace_event/trace_event.h"
29 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
30 #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
31 #include "content/browser/bad_message.h"
32 #import "content/browser/cocoa/system_hotkey_helper_mac.h"
33 #import "content/browser/cocoa/system_hotkey_map.h"
34 #include "content/browser/compositor/resize_lock.h"
35 #include "content/browser/frame_host/frame_tree.h"
36 #include "content/browser/frame_host/frame_tree_node.h"
37 #include "content/browser/frame_host/render_frame_host_impl.h"
38 #include "content/browser/gpu/compositor_util.h"
39 #include "content/browser/renderer_host/render_view_host_impl.h"
40 #include "content/browser/renderer_host/render_widget_helper.h"
41 #include "content/browser/renderer_host/render_widget_host_delegate.h"
42 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
43 #import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
44 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
45 #include "content/browser/renderer_host/render_widget_resize_helper.h"
46 #import "content/browser/renderer_host/text_input_client_mac.h"
47 #include "content/common/accessibility_messages.h"
48 #include "content/common/edit_command.h"
49 #include "content/common/gpu/gpu_messages.h"
50 #include "content/common/input_messages.h"
51 #include "content/common/view_messages.h"
52 #include "content/common/webplugin_geometry.h"
53 #include "content/public/browser/browser_context.h"
54 #include "content/public/browser/browser_plugin_guest_manager.h"
55 #include "content/public/browser/browser_thread.h"
56 #include "content/public/browser/native_web_keyboard_event.h"
57 #include "content/public/browser/notification_service.h"
58 #include "content/public/browser/notification_types.h"
59 #include "content/public/browser/render_widget_host_view_frame_subscriber.h"
60 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
61 #include "content/public/browser/web_contents.h"
62 #include "skia/ext/platform_canvas.h"
63 #include "skia/ext/skia_utils_mac.h"
64 #include "third_party/WebKit/public/platform/WebScreenInfo.h"
65 #include "third_party/WebKit/public/web/WebInputEvent.h"
66 #include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
67 #import "third_party/mozilla/ComplexTextInputPanel.h"
68 #include "ui/accelerated_widget_mac/io_surface_layer.h"
69 #include "ui/accelerated_widget_mac/surface_handle_types.h"
70 #include "ui/base/cocoa/animation_utils.h"
71 #import "ui/base/cocoa/fullscreen_window_manager.h"
72 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
73 #include "ui/base/layout.h"
74 #include "ui/compositor/compositor.h"
75 #include "ui/compositor/layer.h"
76 #include "ui/events/keycodes/keyboard_codes.h"
77 #include "ui/gfx/color_profile.h"
78 #include "ui/gfx/display.h"
79 #include "ui/gfx/geometry/dip_util.h"
80 #include "ui/gfx/geometry/point.h"
81 #include "ui/gfx/geometry/rect_conversions.h"
82 #include "ui/gfx/geometry/size_conversions.h"
83 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
84 #include "ui/gfx/screen.h"
85 #include "ui/gl/gl_switches.h"
87 using content::BrowserAccessibility;
88 using content::BrowserAccessibilityManager;
89 using content::EditCommand;
90 using content::FrameTreeNode;
91 using content::NativeWebKeyboardEvent;
92 using content::RenderFrameHost;
93 using content::RenderViewHost;
94 using content::RenderViewHostImpl;
95 using content::RenderWidgetHostImpl;
96 using content::RenderWidgetHostViewMac;
97 using content::RenderWidgetHostViewMacEditCommandHelper;
98 using content::TextInputClientMac;
99 using content::WebContents;
100 using blink::WebInputEvent;
101 using blink::WebInputEventFactory;
102 using blink::WebMouseEvent;
103 using blink::WebMouseWheelEvent;
104 using blink::WebGestureEvent;
106 namespace {
108 // Whether a keyboard event has been reserved by OSX.
109 BOOL EventIsReservedBySystem(NSEvent* event) {
110   content::SystemHotkeyHelperMac* helper =
111       content::SystemHotkeyHelperMac::GetInstance();
112   return helper->map()->IsEventReserved(event);
115 RenderWidgetHostViewMac* GetRenderWidgetHostViewToUse(
116     RenderWidgetHostViewMac* render_widget_host_view) {
117   WebContents* web_contents = render_widget_host_view->GetWebContents();
118   if (!web_contents)
119     return render_widget_host_view;
120   content::BrowserPluginGuestManager* guest_manager =
121       web_contents->GetBrowserContext()->GetGuestManager();
122   if (!guest_manager)
123     return render_widget_host_view;
124   content::WebContents* guest =
125       guest_manager->GetFullPageGuest(web_contents);
126   if (!guest)
127     return render_widget_host_view;
128   return static_cast<RenderWidgetHostViewMac*>(
129       guest->GetRenderWidgetHostView());
132 }  // namespace
134 // These are not documented, so use only after checking -respondsToSelector:.
135 @interface NSApplication (UndocumentedSpeechMethods)
136 - (void)speakString:(NSString*)string;
137 - (void)stopSpeaking:(id)sender;
138 - (BOOL)isSpeaking;
139 @end
141 // This method will return YES for OS X versions 10.7.3 and later, and NO
142 // otherwise.
143 // Used to prevent a crash when building with the 10.7 SDK and accessing the
144 // notification below. See: http://crbug.com/260595.
145 static BOOL SupportsBackingPropertiesChangedNotification() {
146   // windowDidChangeBackingProperties: method has been added to the
147   // NSWindowDelegate protocol in 10.7.3, at the same time as the
148   // NSWindowDidChangeBackingPropertiesNotification notification was added.
149   // If the protocol contains this method description, the notification should
150   // be supported as well.
151   Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
152   struct objc_method_description methodDescription =
153       protocol_getMethodDescription(
154           windowDelegateProtocol,
155           @selector(windowDidChangeBackingProperties:),
156           NO,
157           YES);
159   // If the protocol does not contain the method, the returned method
160   // description is {NULL, NULL}
161   return methodDescription.name != NULL || methodDescription.types != NULL;
164 // Private methods:
165 @interface RenderWidgetHostViewCocoa ()
166 @property(nonatomic, assign) NSRange selectedRange;
167 @property(nonatomic, assign) NSRange markedRange;
169 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
170 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
171 - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event
172                    consumed:(BOOL)consumed;
174 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
175 - (void)windowDidChangeBackingProperties:(NSNotification*)notification;
176 - (void)windowChangedGlobalFrame:(NSNotification*)notification;
177 - (void)checkForPluginImeCancellation;
178 - (void)updateScreenProperties;
179 - (void)setResponderDelegate:
180         (NSObject<RenderWidgetHostViewMacDelegate>*)delegate;
181 @end
183 // A window subclass that allows the fullscreen window to become main and gain
184 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
185 // handled by the browser.
186 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
187 @end
189 @implementation PepperFlashFullscreenWindow
191 - (BOOL)canBecomeKeyWindow {
192   return YES;
195 - (BOOL)canBecomeMainWindow {
196   return YES;
199 @end
201 @interface RenderWidgetPopupWindow : NSWindow {
202    // The event tap that allows monitoring of all events, to properly close with
203    // a click outside the bounds of the window.
204   id clickEventTap_;
206 @end
208 @implementation RenderWidgetPopupWindow
210 - (id)initWithContentRect:(NSRect)contentRect
211                 styleMask:(NSUInteger)windowStyle
212                   backing:(NSBackingStoreType)bufferingType
213                     defer:(BOOL)deferCreation {
214   if (self = [super initWithContentRect:contentRect
215                               styleMask:windowStyle
216                                 backing:bufferingType
217                                   defer:deferCreation]) {
218     [self setOpaque:NO];
219     [self setBackgroundColor:[NSColor clearColor]];
220     [self startObservingClicks];
221   }
222   return self;
225 - (void)close {
226   [self stopObservingClicks];
227   [super close];
230 // Gets called when the menubar is clicked.
231 // Needed because the local event monitor doesn't see the click on the menubar.
232 - (void)beganTracking:(NSNotification*)notification {
233   [self close];
236 // Install the callback.
237 - (void)startObservingClicks {
238   clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
239       handler:^NSEvent* (NSEvent* event) {
240           if ([event window] == self)
241             return event;
242           NSEventType eventType = [event type];
243           if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
244             [self close];
245           return event;
246   }];
248   NSNotificationCenter* notificationCenter =
249       [NSNotificationCenter defaultCenter];
250   [notificationCenter addObserver:self
251          selector:@selector(beganTracking:)
252              name:NSMenuDidBeginTrackingNotification
253            object:[NSApp mainMenu]];
256 // Remove the callback.
257 - (void)stopObservingClicks {
258   if (!clickEventTap_)
259     return;
261   [NSEvent removeMonitor:clickEventTap_];
262    clickEventTap_ = nil;
264   NSNotificationCenter* notificationCenter =
265       [NSNotificationCenter defaultCenter];
266   [notificationCenter removeObserver:self
267                 name:NSMenuDidBeginTrackingNotification
268               object:[NSApp mainMenu]];
271 @end
273 namespace {
275 // Maximum number of characters we allow in a tooltip.
276 const size_t kMaxTooltipLength = 1024;
278 // TODO(suzhe): Upstream this function.
279 blink::WebColor WebColorFromNSColor(NSColor *color) {
280   CGFloat r, g, b, a;
281   [color getRed:&r green:&g blue:&b alpha:&a];
283   return
284       std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
285       std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
286       std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8  |
287       std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
290 // Extract underline information from an attributed string. Mostly copied from
291 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
292 void ExtractUnderlines(
293     NSAttributedString* string,
294     std::vector<blink::WebCompositionUnderline>* underlines) {
295   int length = [[string string] length];
296   int i = 0;
297   while (i < length) {
298     NSRange range;
299     NSDictionary* attrs = [string attributesAtIndex:i
300                               longestEffectiveRange:&range
301                                             inRange:NSMakeRange(i, length - i)];
302     if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
303       blink::WebColor color = SK_ColorBLACK;
304       if (NSColor *colorAttr =
305           [attrs objectForKey:NSUnderlineColorAttributeName]) {
306         color = WebColorFromNSColor(
307             [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
308       }
309       underlines->push_back(
310           blink::WebCompositionUnderline(range.location,
311                                          NSMaxRange(range),
312                                          color,
313                                          [style intValue] > 1,
314                                          SK_ColorTRANSPARENT));
315     }
316     i = range.location + range.length;
317   }
320 // EnablePasswordInput() and DisablePasswordInput() are copied from
321 // enableSecureTextInput() and disableSecureTextInput() functions in
322 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
323 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
324 // here, because they are already called in webkit and they are system wide
325 // functions.
326 void EnablePasswordInput() {
327   CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
328   TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
329                          sizeof(CFArrayRef), &inputSources);
330   CFRelease(inputSources);
333 void DisablePasswordInput() {
334   TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
337 // Calls to [NSScreen screens], required by FlipYFromRectToScreen and
338 // FlipNSRectToRectScreen, can take several milliseconds. Only re-compute this
339 // value when screen info changes.
340 // TODO(ccameron): An observer on every RWHVCocoa will set this to false
341 // on NSApplicationDidChangeScreenParametersNotification. Only one observer
342 // is necessary.
343 bool g_screen_info_up_to_date = false;
345 float FlipYFromRectToScreen(float y, float rect_height) {
346   TRACE_EVENT0("browser", "FlipYFromRectToScreen");
347   static CGFloat screen_zero_height = 0;
348   if (!g_screen_info_up_to_date) {
349     if ([[NSScreen screens] count] > 0) {
350       screen_zero_height =
351           [[[NSScreen screens] objectAtIndex:0] frame].size.height;
352       g_screen_info_up_to_date = true;
353     } else {
354       return y;
355     }
356   }
357   return screen_zero_height - y - rect_height;
360 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
361 // left of the primary screen (Carbon coordinates), and stuffs it into a
362 // gfx::Rect.
363 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
364   gfx::Rect new_rect(NSRectToCGRect(rect));
365   new_rect.set_y(FlipYFromRectToScreen(new_rect.y(), new_rect.height()));
366   return new_rect;
369 // Returns the window that visually contains the given view. This is different
370 // from [view window] in the case of tab dragging, where the view's owning
371 // window is a floating panel attached to the actual browser window that the tab
372 // is visually part of.
373 NSWindow* ApparentWindowForView(NSView* view) {
374   // TODO(shess): In case of !window, the view has been removed from
375   // the view hierarchy because the tab isn't main.  Could retrieve
376   // the information from the main tab for our window.
377   NSWindow* enclosing_window = [view window];
379   // See if this is a tab drag window. The width check is to distinguish that
380   // case from extension popup windows.
381   NSWindow* ancestor_window = [enclosing_window parentWindow];
382   if (ancestor_window && (NSWidth([enclosing_window frame]) ==
383                           NSWidth([ancestor_window frame]))) {
384     enclosing_window = ancestor_window;
385   }
387   return enclosing_window;
390 blink::WebScreenInfo GetWebScreenInfo(NSView* view) {
391   gfx::Display display =
392       gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
394   NSScreen* screen = [NSScreen deepestScreen];
396   blink::WebScreenInfo results;
398   results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
399   results.depth = NSBitsPerPixelFromDepth([screen depth]);
400   results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
401   results.isMonochrome =
402       [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
403   results.rect = display.bounds();
404   results.availableRect = display.work_area();
405   results.orientationAngle = display.RotationAsDegree();
406   results.orientationType =
407       content::RenderWidgetHostViewBase::GetOrientationTypeForDesktop(display);
409   return results;
412 }  // namespace
414 namespace content {
416 ////////////////////////////////////////////////////////////////////////////////
417 // DelegatedFrameHost, public:
419 ui::Layer* RenderWidgetHostViewMac::DelegatedFrameHostGetLayer() const {
420   return root_layer_.get();
423 bool RenderWidgetHostViewMac::DelegatedFrameHostIsVisible() const {
424   return !render_widget_host_->is_hidden();
427 gfx::Size RenderWidgetHostViewMac::DelegatedFrameHostDesiredSizeInDIP() const {
428   return GetViewBounds().size();
431 bool RenderWidgetHostViewMac::DelegatedFrameCanCreateResizeLock() const {
432   // Mac uses the RenderWidgetResizeHelper instead of a resize lock.
433   return false;
436 scoped_ptr<ResizeLock>
437 RenderWidgetHostViewMac::DelegatedFrameHostCreateResizeLock(
438     bool defer_compositor_lock) {
439   NOTREACHED();
440   return scoped_ptr<ResizeLock>();
443 void RenderWidgetHostViewMac::DelegatedFrameHostResizeLockWasReleased() {
444   NOTREACHED();
447 void RenderWidgetHostViewMac::DelegatedFrameHostSendCompositorSwapAck(
448     int output_surface_id,
449     const cc::CompositorFrameAck& ack) {
450   render_widget_host_->Send(new ViewMsg_SwapCompositorFrameAck(
451       render_widget_host_->GetRoutingID(), output_surface_id, ack));
454 void RenderWidgetHostViewMac::DelegatedFrameHostSendReclaimCompositorResources(
455     int output_surface_id,
456     const cc::CompositorFrameAck& ack) {
457   render_widget_host_->Send(new ViewMsg_ReclaimCompositorResources(
458       render_widget_host_->GetRoutingID(), output_surface_id, ack));
461 void RenderWidgetHostViewMac::DelegatedFrameHostOnLostCompositorResources() {
462   render_widget_host_->ScheduleComposite();
465 void RenderWidgetHostViewMac::DelegatedFrameHostUpdateVSyncParameters(
466     const base::TimeTicks& timebase,
467     const base::TimeDelta& interval) {
468   render_widget_host_->UpdateVSyncParameters(timebase, interval);
471 ////////////////////////////////////////////////////////////////////////////////
472 // AcceleratedWidgetMacNSView, public:
474 NSView* RenderWidgetHostViewMac::AcceleratedWidgetGetNSView() const {
475   return cocoa_view_;
478 bool RenderWidgetHostViewMac::AcceleratedWidgetShouldIgnoreBackpressure()
479     const {
480   // If vsync is disabled, then always draw and ack frames immediately.
481   static bool is_vsync_disabled =
482       base::CommandLine::ForCurrentProcess()->HasSwitch(
483           switches::kDisableGpuVsync);
484   if (is_vsync_disabled)
485     return true;
487   // If the window is occluded, then this frame's display call may be severely
488   // throttled. This is a good thing, unless tab capture may be active, because
489   // the broadcast will be inappropriately throttled.
490   // http://crbug.com/350410
492   // If tab capture isn't active then only ack frames when we draw them.
493   if (delegated_frame_host_ && !delegated_frame_host_->HasFrameSubscriber())
494     return false;
496   NSWindow* window = [cocoa_view_ window];
497   // If the view isn't even in the heirarchy then frames will never be drawn,
498   // so ack them immediately.
499   if (!window)
500     return true;
502   // Check the window occlusion API.
503   if ([window respondsToSelector:@selector(occlusionState)]) {
504     if ([window occlusionState] & NSWindowOcclusionStateVisible) {
505       // If the window is visible then it is safe to wait until frames are
506       // drawn to ack them.
507       return false;
508     } else {
509       // If the window is occluded then frames may never be drawn, so ack them
510       // immediately.
511       return true;
512     }
513   }
515   // If the window occlusion API is not present then ack frames when we draw
516   // them.
517   return false;
520 void RenderWidgetHostViewMac::AcceleratedWidgetGetVSyncParameters(
521     base::TimeTicks* timebase, base::TimeDelta* interval) const {
522   if (display_link_ &&
523       display_link_->GetVSyncParameters(timebase, interval))
524     return;
525   *timebase = base::TimeTicks();
526   *interval = base::TimeDelta();
529 void RenderWidgetHostViewMac::AcceleratedWidgetSwapCompleted(
530     const std::vector<ui::LatencyInfo>& all_latency_info) {
531   if (!render_widget_host_)
532     return;
533   base::TimeTicks swap_time = base::TimeTicks::Now();
535   for (auto latency_info : all_latency_info) {
536     latency_info.AddLatencyNumberWithTimestamp(
537         ui::INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT, 0, 0, swap_time, 1);
538     latency_info.AddLatencyNumberWithTimestamp(
539         ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0,
540         swap_time, 1);
541     render_widget_host_->FrameSwapped(latency_info);
542   }
544   if (display_link_)
545     display_link_->NotifyCurrentTime(swap_time);
548 void RenderWidgetHostViewMac::AcceleratedWidgetHitError() {
549   // Request a new frame be drawn.
550   browser_compositor_->compositor()->ScheduleFullRedraw();
553 ///////////////////////////////////////////////////////////////////////////////
554 // RenderWidgetHostViewBase, public:
556 // static
557 void RenderWidgetHostViewBase::GetDefaultScreenInfo(
558     blink::WebScreenInfo* results) {
559   *results = GetWebScreenInfo(NULL);
562 ///////////////////////////////////////////////////////////////////////////////
563 // RenderWidgetHostViewMac, public:
565 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget,
566                                                  bool is_guest_view_hack)
567     : render_widget_host_(RenderWidgetHostImpl::From(widget)),
568       text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
569       can_compose_inline_(true),
570       browser_compositor_state_(BrowserCompositorDestroyed),
571       browser_compositor_placeholder_(new BrowserCompositorMacPlaceholder),
572       page_at_minimum_scale_(true),
573       is_loading_(false),
574       allow_pause_for_resize_or_repaint_(true),
575       is_guest_view_hack_(is_guest_view_hack),
576       fullscreen_parent_host_view_(NULL),
577       weak_factory_(this) {
578   // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
579   // goes away.  Since we autorelease it, our caller must put
580   // |GetNativeView()| into the view hierarchy right after calling us.
581   cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
582                   initWithRenderWidgetHostViewMac:this] autorelease];
584   // Paint this view host with |background_color_| when there is no content
585   // ready to draw.
586   background_layer_.reset([[CALayer alloc] init]);
587   // Set the default color to be white. This is the wrong thing to do, but many
588   // UI components expect this view to be opaque.
589   [background_layer_ setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
590   [cocoa_view_ setLayer:background_layer_];
591   [cocoa_view_ setWantsLayer:YES];
593   if (IsDelegatedRendererEnabled()) {
594     root_layer_.reset(new ui::Layer(ui::LAYER_SOLID_COLOR));
595     delegated_frame_host_.reset(new DelegatedFrameHost(this));
596   }
598   gfx::Screen::GetScreenFor(cocoa_view_)->AddObserver(this);
600   if (!is_guest_view_hack_)
601     render_widget_host_->SetView(this);
603   // Let the page-level input event router know about our surface ID
604   // namespace for surface-based hit testing.
605   if (UseSurfacesEnabled() && render_widget_host_->delegate() &&
606       render_widget_host_->delegate()->GetInputEventRouter()) {
607     render_widget_host_->delegate()
608         ->GetInputEventRouter()
609         ->AddSurfaceIdNamespaceOwner(GetSurfaceIdNamespace(), this);
610   }
613 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
614   gfx::Screen::GetScreenFor(cocoa_view_)->RemoveObserver(this);
616   // This is being called from |cocoa_view_|'s destructor, so invalidate the
617   // pointer.
618   cocoa_view_ = nil;
620   UnlockMouse();
622   if (UseSurfacesEnabled() && render_widget_host_ &&
623       render_widget_host_->delegate() &&
624       render_widget_host_->delegate()->GetInputEventRouter()) {
625     render_widget_host_->delegate()
626         ->GetInputEventRouter()
627         ->RemoveSurfaceIdNamespaceOwner(GetSurfaceIdNamespace());
628   }
630   // Ensure that the browser compositor is destroyed in a safe order.
631   ShutdownBrowserCompositor();
633   // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
634   // RenderWidgetHost does we need to tell it not to hold a stale pointer to
635   // us.
636   if (render_widget_host_) {
637     // If this is a RenderWidgetHostViewGuest's platform_view_, we're not the
638     // RWH's view, the RenderWidgetHostViewGuest is. So don't reset the RWH's
639     // view, the RenderWidgetHostViewGuest will do it.
640     if (!is_guest_view_hack_)
641       render_widget_host_->SetView(NULL);
642   }
645 void RenderWidgetHostViewMac::SetDelegate(
646     NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
647   [cocoa_view_ setResponderDelegate:delegate];
650 void RenderWidgetHostViewMac::SetAllowPauseForResizeOrRepaint(bool allow) {
651   allow_pause_for_resize_or_repaint_ = allow;
654 ///////////////////////////////////////////////////////////////////////////////
655 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
657 void RenderWidgetHostViewMac::EnsureBrowserCompositorView() {
658   TRACE_EVENT0("browser",
659                "RenderWidgetHostViewMac::EnsureBrowserCompositorView");
661   // Create the view, to transition from Destroyed -> Suspended.
662   if (browser_compositor_state_ == BrowserCompositorDestroyed) {
663     browser_compositor_ = BrowserCompositorMac::Create();
664     browser_compositor_->compositor()->SetRootLayer(root_layer_.get());
665     browser_compositor_->compositor()->SetHostHasTransparentBackground(
666         !GetBackgroundOpaque());
667     browser_compositor_->accelerated_widget_mac()->SetNSView(this);
668     browser_compositor_state_ = BrowserCompositorSuspended;
669   }
671   // Show the DelegatedFrameHost to transition from Suspended -> Active.
672   if (browser_compositor_state_ == BrowserCompositorSuspended) {
673     delegated_frame_host_->SetCompositor(browser_compositor_->compositor());
674     delegated_frame_host_->WasShown(ui::LatencyInfo());
675     // Unsuspend the browser compositor after showing the delegated frame host.
676     // If there is not a saved delegated frame, then the delegated frame host
677     // will keep the compositor locked until a delegated frame is swapped.
678     float scale_factor = ViewScaleFactor();
679     browser_compositor_->compositor()->SetScaleAndSize(
680         scale_factor,
681         gfx::ConvertSizeToPixel(scale_factor, GetViewBounds().size()));
682     browser_compositor_->Unsuspend();
683     browser_compositor_state_ = BrowserCompositorActive;
684   }
687 void RenderWidgetHostViewMac::SuspendBrowserCompositorView() {
688   TRACE_EVENT0("browser",
689                "RenderWidgetHostViewMac::SuspendBrowserCompositorView");
691   // Hide the DelegatedFrameHost to transition from Active -> Suspended.
692   if (browser_compositor_state_ == BrowserCompositorActive) {
693     // Ensure that any changes made to the ui::Compositor do not result in new
694     // frames being produced.
695     browser_compositor_->Suspend();
696     // Marking the DelegatedFrameHost as removed from the window hierarchy is
697     // necessary to remove all connections to its old ui::Compositor.
698     delegated_frame_host_->WasHidden();
699     delegated_frame_host_->ResetCompositor();
700     browser_compositor_state_ = BrowserCompositorSuspended;
701   }
704 void RenderWidgetHostViewMac::DestroyBrowserCompositorView() {
705   TRACE_EVENT0("browser",
706                "RenderWidgetHostViewMac::DestroyBrowserCompositorView");
708   // Transition from Active -> Suspended if need be.
709   SuspendBrowserCompositorView();
711   // Destroy the BrowserCompositorView to transition Suspended -> Destroyed.
712   if (browser_compositor_state_ == BrowserCompositorSuspended) {
713     browser_compositor_->accelerated_widget_mac()->ResetNSView();
714     browser_compositor_->compositor()->SetScaleAndSize(1.0, gfx::Size(0, 0));
715     browser_compositor_->compositor()->SetRootLayer(nullptr);
716     BrowserCompositorMac::Recycle(browser_compositor_.Pass());
717     browser_compositor_state_ = BrowserCompositorDestroyed;
718   }
721 void RenderWidgetHostViewMac::DestroySuspendedBrowserCompositorViewIfNeeded() {
722   if (browser_compositor_state_ != BrowserCompositorSuspended)
723     return;
725   // If this view is in a window that is visible, keep around the suspended
726   // BrowserCompositorView in case |cocoa_view_| is suddenly revealed (so that
727   // we don't flash white).
728   NSWindow* window = [cocoa_view_ window];
729   if (window)
730     return;
732   // This should only be reached if |render_widget_host_| is hidden, destroyed,
733   // or in the process of being destroyed.
734   DestroyBrowserCompositorView();
737 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
738   bool handled = true;
739   IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
740     IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
741     IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
742     IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedTextCompleted,
743         OnGetRenderedTextCompleted)
744     IPC_MESSAGE_UNHANDLED(handled = false)
745   IPC_END_MESSAGE_MAP()
746   return handled;
749 void RenderWidgetHostViewMac::InitAsChild(
750     gfx::NativeView parent_view) {
753 void RenderWidgetHostViewMac::InitAsPopup(
754     RenderWidgetHostView* parent_host_view,
755     const gfx::Rect& pos) {
756   bool activatable = popup_type_ == blink::WebPopupTypeNone;
757   [cocoa_view_ setCloseOnDeactivate:YES];
758   [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
760   NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
761   origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height());
763   popup_window_.reset([[RenderWidgetPopupWindow alloc]
764       initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
765                                      pos.width(), pos.height())
766                 styleMask:NSBorderlessWindowMask
767                   backing:NSBackingStoreBuffered
768                     defer:NO]);
769   [popup_window_ setLevel:NSPopUpMenuWindowLevel];
770   [popup_window_ setReleasedWhenClosed:NO];
771   [popup_window_ makeKeyAndOrderFront:nil];
772   [[popup_window_ contentView] addSubview:cocoa_view_];
773   [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
774   [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
775   [[NSNotificationCenter defaultCenter]
776       addObserver:cocoa_view_
777          selector:@selector(popupWindowWillClose:)
778              name:NSWindowWillCloseNotification
779            object:popup_window_];
782 // This function creates the fullscreen window and hides the dock and menubar if
783 // necessary. Note, this codepath is only used for pepper flash when
784 // pp::FlashFullScreen::SetFullscreen() is called. If
785 // pp::FullScreen::SetFullscreen() is called then the entire browser window
786 // will enter fullscreen instead.
787 void RenderWidgetHostViewMac::InitAsFullscreen(
788     RenderWidgetHostView* reference_host_view) {
789   fullscreen_parent_host_view_ =
790       static_cast<RenderWidgetHostViewMac*>(reference_host_view);
791   NSWindow* parent_window = nil;
792   if (reference_host_view)
793     parent_window = [reference_host_view->GetNativeView() window];
794   NSScreen* screen = [parent_window screen];
795   if (!screen)
796     screen = [NSScreen mainScreen];
798   pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
799       initWithContentRect:[screen frame]
800                 styleMask:NSBorderlessWindowMask
801                   backing:NSBackingStoreBuffered
802                     defer:NO]);
803   [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
804   [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
805   [cocoa_view_ setCanBeKeyView:YES];
806   [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
807   [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
808   // If the pepper fullscreen window isn't opaque then there are performance
809   // issues when it's on the discrete GPU and the Chrome window is being drawn
810   // to. http://crbug.com/171911
811   [pepper_fullscreen_window_ setOpaque:YES];
813   // Note that this forms a reference cycle between the fullscreen window and
814   // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
815   // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
816   // This cycle is normally broken when -keyEvent: receives an <esc> key, which
817   // explicitly calls Shutdown on the render_widget_host_, which calls
818   // Destroy() on RWHVMac, which drops the reference to
819   // pepper_fullscreen_window_.
820   [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
822   // Note that this keeps another reference to pepper_fullscreen_window_.
823   fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
824       initWithWindow:pepper_fullscreen_window_.get()
825        desiredScreen:screen]);
826   [fullscreen_window_manager_ enterFullscreenMode];
827   [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
830 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
831   // See comment in InitAsFullscreen(): There is a reference cycle between
832   // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
833   // Tests that test pepper fullscreen mode without sending an <esc> event
834   // need to call this method to break the reference cycle.
835   [fullscreen_window_manager_ exitFullscreenMode];
836   fullscreen_window_manager_.reset();
837   [pepper_fullscreen_window_ close];
838   pepper_fullscreen_window_.reset();
841 int RenderWidgetHostViewMac::window_number() const {
842   NSWindow* window = [cocoa_view_ window];
843   if (!window)
844     return -1;
845   return [window windowNumber];
848 float RenderWidgetHostViewMac::ViewScaleFactor() const {
849   return ui::GetScaleFactorForNativeView(cocoa_view_);
852 void RenderWidgetHostViewMac::UpdateDisplayLink() {
853   static bool is_vsync_disabled =
854       base::CommandLine::ForCurrentProcess()->HasSwitch(
855           switches::kDisableGpuVsync);
856   if (is_vsync_disabled)
857     return;
859   NSScreen* screen = [[cocoa_view_ window] screen];
860   NSDictionary* screen_description = [screen deviceDescription];
861   NSNumber* screen_number = [screen_description objectForKey:@"NSScreenNumber"];
862   CGDirectDisplayID display_id = [screen_number unsignedIntValue];
864   display_link_ = ui::DisplayLinkMac::GetForDisplay(display_id);
865   if (!display_link_.get()) {
866     // Note that on some headless systems, the display link will fail to be
867     // created, so this should not be a fatal error.
868     LOG(ERROR) << "Failed to create display link.";
869   }
872 void RenderWidgetHostViewMac::SendVSyncParametersToRenderer() {
873   if (!render_widget_host_ || !display_link_.get())
874     return;
876   if (!display_link_->GetVSyncParameters(&vsync_timebase_, &vsync_interval_)) {
877     vsync_timebase_ = base::TimeTicks();
878     vsync_interval_ = base::TimeDelta();
879     return;
880   }
882   if (browser_compositor_) {
883     browser_compositor_->compositor()->vsync_manager()->UpdateVSyncParameters(
884         vsync_timebase_, vsync_interval_);
885   }
888 void RenderWidgetHostViewMac::SpeakText(const std::string& text) {
889   [NSApp speakString:base::SysUTF8ToNSString(text)];
892 void RenderWidgetHostViewMac::UpdateBackingStoreProperties() {
893   if (!render_widget_host_)
894     return;
895   render_widget_host_->NotifyScreenInfoChanged();
898 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
899   return render_widget_host_;
902 void RenderWidgetHostViewMac::Show() {
903   ScopedCAActionDisabler disabler;
904   [cocoa_view_ setHidden:NO];
905   if (!render_widget_host_->is_hidden())
906     return;
908   // Re-create the browser compositor. If the DelegatedFrameHost has a cached
909   // frame from the last time it was visible, then it will immediately be
910   // drawn. If not, then the compositor will remain locked until a new delegated
911   // frame is swapped.
912   EnsureBrowserCompositorView();
914   WasUnOccluded();
916   // If there is not a frame being currently drawn, kick one, so that the below
917   // pause will have a frame to wait on.
918   render_widget_host_->ScheduleComposite();
919   PauseForPendingResizeOrRepaintsAndDraw();
922 void RenderWidgetHostViewMac::Hide() {
923   ScopedCAActionDisabler disabler;
924   [cocoa_view_ setHidden:YES];
925   WasOccluded();
926   DestroySuspendedBrowserCompositorViewIfNeeded();
929 void RenderWidgetHostViewMac::WasUnOccluded() {
930   if (!render_widget_host_->is_hidden())
931     return;
933   ui::LatencyInfo renderer_latency_info;
934   renderer_latency_info.AddLatencyNumber(
935       ui::TAB_SHOW_COMPONENT,
936       render_widget_host_->GetLatencyComponentId(),
937       0);
938   render_widget_host_->WasShown(renderer_latency_info);
941 void RenderWidgetHostViewMac::WasOccluded() {
942   if (render_widget_host_->is_hidden())
943     return;
945   // SuspendBrowserCompositorView() instructs the GPU process to free up
946   // resources such as textures. WasHidden() places the renderer process in the
947   // background and throttles its I/O. We're cafeful to call WasHidden() only
948   // after calling SuspendBrowserCompositorView(), otherwise the backgrounded
949   // and throttled renderer's communication with GPU process will take extra
950   // time to complete. The delay will block the foreground renderer's
951   // communication with the GPU process, resulting in longer tab switching
952   // time. http://crbug.com/502502 .
953   SuspendBrowserCompositorView();
954   render_widget_host_->WasHidden();
957 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
958   gfx::Rect rect = GetViewBounds();
959   rect.set_size(size);
960   SetBounds(rect);
963 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
964   // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
965   // TODO(thakis): fix, http://crbug.com/73362
966   if (render_widget_host_->is_hidden())
967     return;
969   // During the initial creation of the RenderWidgetHostView in
970   // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
971   // an empty size. In the Windows code flow, it is not ignored because
972   // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
973   // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
974   // flags to keep things sized properly. On the other hand, if the size is not
975   // empty then this is a valid request for a pop-up.
976   if (rect.size().IsEmpty())
977     return;
979   // Ignore the position of |rect| for non-popup rwhvs. This is because
980   // background tabs do not have a window, but the window is required for the
981   // coordinate conversions. Popups are always for a visible tab.
982   //
983   // Note: If |cocoa_view_| has been removed from the view hierarchy, it's still
984   // valid for resizing to be requested (e.g., during tab capture, to size the
985   // view to screen-capture resolution). In this case, simply treat the view as
986   // relative to the screen.
987   BOOL isRelativeToScreen = IsPopup() ||
988       ![[cocoa_view_ superview] isKindOfClass:[BaseView class]];
989   if (isRelativeToScreen) {
990     // The position of |rect| is screen coordinate system and we have to
991     // consider Cocoa coordinate system is upside-down and also multi-screen.
992     NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
993     NSSize size = NSMakeSize(rect.width(), rect.height());
994     size = [cocoa_view_ convertSize:size toView:nil];
995     origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height);
996     NSRect frame = NSMakeRect(origin_global.x, origin_global.y,
997                               size.width, size.height);
998     if (IsPopup())
999       [popup_window_ setFrame:frame display:YES];
1000     else
1001       [cocoa_view_ setFrame:frame];
1002   } else {
1003     BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
1004     gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
1005     rect2.set_width(rect.width());
1006     rect2.set_height(rect.height());
1007     [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
1008   }
1011 gfx::Vector2dF RenderWidgetHostViewMac::GetLastScrollOffset() const {
1012   return last_scroll_offset_;
1015 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
1016   return cocoa_view_;
1019 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
1020   return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
1023 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
1024   return cocoa_view_;
1027 void RenderWidgetHostViewMac::MovePluginWindows(
1028     const std::vector<WebPluginGeometry>& moves) {
1029   // Must be overridden, but unused on this platform. Core Animation
1030   // plugins are drawn by the GPU process (through the compositor),
1031   // and Core Graphics plugins are drawn by the renderer process.
1032   DCHECK_CURRENTLY_ON(BrowserThread::UI);
1035 void RenderWidgetHostViewMac::Focus() {
1036   [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
1039 bool RenderWidgetHostViewMac::HasFocus() const {
1040   return [[cocoa_view_ window] firstResponder] == cocoa_view_;
1043 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
1044   if (delegated_frame_host_)
1045     return delegated_frame_host_->CanCopyToBitmap();
1046   return false;
1049 bool RenderWidgetHostViewMac::IsShowing() {
1050   return ![cocoa_view_ isHidden];
1053 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
1054   NSRect bounds = [cocoa_view_ bounds];
1055   // TODO(shess): In case of !window, the view has been removed from
1056   // the view hierarchy because the tab isn't main.  Could retrieve
1057   // the information from the main tab for our window.
1058   NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1059   if (!enclosing_window)
1060     return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
1062   bounds = [cocoa_view_ convertRect:bounds toView:nil];
1063   bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
1064   return FlipNSRectToRectScreen(bounds);
1067 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
1068   WebCursor web_cursor = cursor;
1069   [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
1072 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
1073   is_loading_ = is_loading;
1074   // If we ever decide to show the waiting cursor while the page is loading
1075   // like Chrome does on Windows, call |UpdateCursor()| here.
1078 void RenderWidgetHostViewMac::TextInputStateChanged(
1079 const ViewHostMsg_TextInputState_Params& params) {
1080   if (text_input_type_ != params.type
1081       || can_compose_inline_ != params.can_compose_inline) {
1082     text_input_type_ = params.type;
1083     can_compose_inline_ = params.can_compose_inline;
1084     if (HasFocus()) {
1085       SetTextInputActive(true);
1087       // Let AppKit cache the new input context to make IMEs happy.
1088       // See http://crbug.com/73039.
1089       [NSApp updateWindows];
1091 #ifndef __LP64__
1092       UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
1093 #endif
1094     }
1095   }
1098 void RenderWidgetHostViewMac::ImeCancelComposition() {
1099   [cocoa_view_ cancelComposition];
1102 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
1103     const gfx::Range& range,
1104     const std::vector<gfx::Rect>& character_bounds) {
1105   // The RangeChanged message is only sent with valid values. The current
1106   // caret position (start == end) will be sent if there is no IME range.
1107   [cocoa_view_ setMarkedRange:range.ToNSRange()];
1108   composition_range_ = range;
1109   composition_bounds_ = character_bounds;
1112 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
1113                                                 int error_code) {
1114   Destroy();
1117 void RenderWidgetHostViewMac::RenderWidgetHostGone() {
1118   // Clear SurfaceID namespace ownership before we shutdown the
1119   // compositor.
1120   if (UseSurfacesEnabled() && render_widget_host_ &&
1121       render_widget_host_->delegate() &&
1122       render_widget_host_->delegate()->GetInputEventRouter()) {
1123     render_widget_host_->delegate()
1124         ->GetInputEventRouter()
1125         ->RemoveSurfaceIdNamespaceOwner(GetSurfaceIdNamespace());
1126   }
1128   // Destroy the DelegatedFrameHost, to prevent crashes when Destroy is never
1129   // called on the view.
1130   // http://crbug.com/404828
1131   ShutdownBrowserCompositor();
1134 void RenderWidgetHostViewMac::Destroy() {
1135   [[NSNotificationCenter defaultCenter]
1136       removeObserver:cocoa_view_
1137                 name:NSWindowWillCloseNotification
1138               object:popup_window_];
1140   // We've been told to destroy.
1141   [cocoa_view_ retain];
1142   [cocoa_view_ removeFromSuperview];
1143   [cocoa_view_ autorelease];
1145   [popup_window_ close];
1146   popup_window_.autorelease();
1148   [fullscreen_window_manager_ exitFullscreenMode];
1149   fullscreen_window_manager_.reset();
1150   [pepper_fullscreen_window_ close];
1152   // This can be called as part of processing the window's responder
1153   // chain, for instance |-performKeyEquivalent:|.  In that case the
1154   // object needs to survive until the stack unwinds.
1155   pepper_fullscreen_window_.autorelease();
1157   // Clear SurfaceID namespace ownership before we shutdown the
1158   // compositor.
1159   if (UseSurfacesEnabled() && render_widget_host_ &&
1160       render_widget_host_->delegate() &&
1161       render_widget_host_->delegate()->GetInputEventRouter()) {
1162     render_widget_host_->delegate()
1163         ->GetInputEventRouter()
1164         ->RemoveSurfaceIdNamespaceOwner(GetSurfaceIdNamespace());
1165   }
1167   // Delete the delegated frame state, which will reach back into
1168   // render_widget_host_.
1169   ShutdownBrowserCompositor();
1171   // We get this call just before |render_widget_host_| deletes
1172   // itself.  But we are owned by |cocoa_view_|, which may be retained
1173   // by some other code.  Examples are WebContentsViewMac's
1174   // |latent_focus_view_| and TabWindowController's
1175   // |cachedContentView_|.
1176   render_widget_host_ = NULL;
1179 // Called from the renderer to tell us what the tooltip text should be. It
1180 // calls us frequently so we need to cache the value to prevent doing a lot
1181 // of repeat work.
1182 void RenderWidgetHostViewMac::SetTooltipText(
1183     const base::string16& tooltip_text) {
1184   if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1185     tooltip_text_ = tooltip_text;
1187     // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1188     // Windows; we're just trying to be polite. Don't persist the trimmed
1189     // string, as then the comparison above will always fail and we'll try to
1190     // set it again every single time the mouse moves.
1191     base::string16 display_text = tooltip_text_;
1192     if (tooltip_text_.length() > kMaxTooltipLength)
1193       display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1195     NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1196     [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1197   }
1200 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1201   return [NSApp respondsToSelector:@selector(speakString:)] &&
1202          [NSApp respondsToSelector:@selector(stopSpeaking:)];
1205 void RenderWidgetHostViewMac::SpeakSelection() {
1206   if (![NSApp respondsToSelector:@selector(speakString:)])
1207     return;
1209   if (selected_text_.empty() && render_widget_host_) {
1210     // If there's no selection, speak all text. Send an asynchronous IPC
1211     // request for fetching all the text for a webcontent.
1212     // ViewMsg_GetRenderedTextCompleted is sent back to IPC Message receiver.
1213     render_widget_host_->Send(new ViewMsg_GetRenderedText(
1214         render_widget_host_->GetRoutingID()));
1215     return;
1216   }
1218   SpeakText(selected_text_);
1221 bool RenderWidgetHostViewMac::IsSpeaking() const {
1222   return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1223          [NSApp isSpeaking];
1226 void RenderWidgetHostViewMac::StopSpeaking() {
1227   if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1228     [NSApp stopSpeaking:cocoa_view_];
1232 // RenderWidgetHostViewCocoa uses the stored selection text,
1233 // which implements NSServicesRequests protocol.
1235 void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text,
1236                                                size_t offset,
1237                                                const gfx::Range& range) {
1238   if (range.is_empty() || text.empty()) {
1239     selected_text_.clear();
1240   } else {
1241     size_t pos = range.GetMin() - offset;
1242     size_t n = range.length();
1244     DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1245     if (pos >= text.length()) {
1246       DCHECK(false) << "The text can not cover range.";
1247       return;
1248     }
1249     selected_text_ = base::UTF16ToUTF8(text.substr(pos, n));
1250   }
1252   [cocoa_view_ setSelectedRange:range.ToNSRange()];
1253   // Updates markedRange when there is no marked text so that retrieving
1254   // markedRange immediately after calling setMarkdText: returns the current
1255   // caret position.
1256   if (![cocoa_view_ hasMarkedText]) {
1257     [cocoa_view_ setMarkedRange:range.ToNSRange()];
1258   }
1260   RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1263 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1264     const ViewHostMsg_SelectionBounds_Params& params) {
1265   if (params.anchor_rect == params.focus_rect)
1266     caret_rect_ = params.anchor_rect;
1269 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1270   RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1272   // Create a fake mouse event to inform the render widget that the mouse
1273   // left or entered.
1274   NSWindow* window = [cocoa_view_ window];
1275   // TODO(asvitkine): If the location outside of the event stream doesn't
1276   // correspond to the current event (due to delayed event processing), then
1277   // this may result in a cursor flicker if there are later mouse move events
1278   // in the pipeline. Find a way to use the mouse location from the event that
1279   // dismissed the context menu.
1280   NSPoint location = [window mouseLocationOutsideOfEventStream];
1281   NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1282                                       location:location
1283                                  modifierFlags:0
1284                                      timestamp:0
1285                                   windowNumber:window_number()
1286                                        context:nil
1287                                    eventNumber:0
1288                                     clickCount:0
1289                                       pressure:0];
1290   WebMouseEvent web_event =
1291       WebInputEventFactory::mouseEvent(event, cocoa_view_);
1292   if (showing)
1293     web_event.type = WebInputEvent::MouseLeave;
1294   ForwardMouseEvent(web_event);
1297 bool RenderWidgetHostViewMac::IsPopup() const {
1298   return popup_type_ != blink::WebPopupTypeNone;
1301 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1302     const gfx::Rect& src_subrect,
1303     const gfx::Size& dst_size,
1304     ReadbackRequestCallback& callback,
1305     const SkColorType preferred_color_type) {
1306   if (delegated_frame_host_) {
1307     delegated_frame_host_->CopyFromCompositingSurface(
1308         src_subrect, dst_size, callback, preferred_color_type);
1309   }
1312 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1313       const gfx::Rect& src_subrect,
1314       const scoped_refptr<media::VideoFrame>& target,
1315       const base::Callback<void(bool)>& callback) {
1316   if (delegated_frame_host_) {
1317     delegated_frame_host_->CopyFromCompositingSurfaceToVideoFrame(
1318         src_subrect, target, callback);
1319   }
1322 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1323   if (delegated_frame_host_)
1324     return delegated_frame_host_->CanCopyToVideoFrame();
1325   return false;
1328 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1329   if (delegated_frame_host_)
1330     return delegated_frame_host_->CanSubscribeFrame();
1331   return false;
1334 void RenderWidgetHostViewMac::BeginFrameSubscription(
1335     scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1336   if (delegated_frame_host_)
1337     delegated_frame_host_->BeginFrameSubscription(subscriber.Pass());
1340 void RenderWidgetHostViewMac::EndFrameSubscription() {
1341   if (delegated_frame_host_)
1342     delegated_frame_host_->EndFrameSubscription();
1345 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1346   if (render_widget_host_)
1347     render_widget_host_->ForwardMouseEvent(event);
1349   if (event.type == WebInputEvent::MouseLeave) {
1350     [cocoa_view_ setToolTipAtMousePoint:nil];
1351     tooltip_text_.clear();
1352   }
1355 void RenderWidgetHostViewMac::KillSelf() {
1356   if (!weak_factory_.HasWeakPtrs()) {
1357     [cocoa_view_ setHidden:YES];
1358     base::MessageLoop::current()->PostTask(FROM_HERE,
1359         base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1360                    weak_factory_.GetWeakPtr()));
1361   }
1364 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1365     const NativeWebKeyboardEvent& event) {
1366   // Check WebInputEvent type since multiple types of events can be sent into
1367   // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1368   // necessary to avoid double processing.
1369   // Also check the native type, since NSFlagsChanged is considered a key event
1370   // for WebKit purposes, but isn't considered a key event by the OS.
1371   if (event.type == WebInputEvent::RawKeyDown &&
1372       [event.os_event type] == NSKeyDown)
1373     return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1374   return false;
1377 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1378     const base::string16& text, int plugin_id) {
1379   if (render_widget_host_) {
1380     render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1381         render_widget_host_->GetRoutingID(), text, plugin_id));
1382   }
1385 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1386     const std::vector<gfx::Rect>& bounds,
1387     const gfx::Range& range,
1388     size_t* line_break_point) {
1389   DCHECK(line_break_point);
1390   if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1391     return false;
1393   // We can't check line breaking completely from only rectangle array. Thus we
1394   // assume the line breaking as the next character's y offset is larger than
1395   // a threshold. Currently the threshold is determined as minimum y offset plus
1396   // 75% of maximum height.
1397   // TODO(nona): Check the threshold is reliable or not.
1398   // TODO(nona): Bidi support.
1399   const size_t loop_end_idx = std::min(bounds.size(), range.end());
1400   int max_height = 0;
1401   int min_y_offset = kint32max;
1402   for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1403     max_height = std::max(max_height, bounds[idx].height());
1404     min_y_offset = std::min(min_y_offset, bounds[idx].y());
1405   }
1406   int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1407   for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1408     if (bounds[idx].y() > line_break_threshold) {
1409       *line_break_point = idx;
1410       return true;
1411     }
1412   }
1413   return false;
1416 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1417     const gfx::Range& range,
1418     gfx::Range* actual_range) {
1419   DCHECK(actual_range);
1420   DCHECK(!composition_bounds_.empty());
1421   DCHECK(range.start() <= composition_bounds_.size());
1422   DCHECK(range.end() <= composition_bounds_.size());
1424   if (range.is_empty()) {
1425     *actual_range = range;
1426     if (range.start() == composition_bounds_.size()) {
1427       return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1428                        composition_bounds_[range.start() - 1].y(),
1429                        0,
1430                        composition_bounds_[range.start() - 1].height());
1431     } else {
1432       return gfx::Rect(composition_bounds_[range.start()].x(),
1433                        composition_bounds_[range.start()].y(),
1434                        0,
1435                        composition_bounds_[range.start()].height());
1436     }
1437   }
1439   size_t end_idx;
1440   if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1441     end_idx = range.end();
1442   }
1443   *actual_range = gfx::Range(range.start(), end_idx);
1444   gfx::Rect rect = composition_bounds_[range.start()];
1445   for (size_t i = range.start() + 1; i < end_idx; ++i) {
1446     rect.Union(composition_bounds_[i]);
1447   }
1448   return rect;
1451 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1452     const gfx::Range& request_range) {
1453   if (composition_range_.is_empty())
1454     return gfx::Range::InvalidRange();
1456   if (request_range.is_reversed())
1457     return gfx::Range::InvalidRange();
1459   if (request_range.start() < composition_range_.start() ||
1460       request_range.start() > composition_range_.end() ||
1461       request_range.end() > composition_range_.end()) {
1462     return gfx::Range::InvalidRange();
1463   }
1465   return gfx::Range(
1466       request_range.start() - composition_range_.start(),
1467       request_range.end() - composition_range_.start());
1470 WebContents* RenderWidgetHostViewMac::GetWebContents() {
1471   if (!render_widget_host_->IsRenderView())
1472     return NULL;
1474   return WebContents::FromRenderViewHost(
1475       RenderViewHost::From(render_widget_host_));
1478 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1479     NSRange range,
1480     NSRect* rect,
1481     NSRange* actual_range) {
1482   DCHECK(rect);
1483   // This exists to make IMEs more responsive, see http://crbug.com/115920
1484   TRACE_EVENT0("browser",
1485                "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1487   // If requested range is same as caret location, we can just return it.
1488   if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1489     if (actual_range)
1490       *actual_range = range;
1491     *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1492     return true;
1493   }
1495   const gfx::Range request_range_in_composition =
1496       ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1497   if (request_range_in_composition == gfx::Range::InvalidRange())
1498     return false;
1500   // If firstRectForCharacterRange in WebFrame is failed in renderer,
1501   // ImeCompositionRangeChanged will be sent with empty vector.
1502   if (composition_bounds_.empty())
1503     return false;
1504   DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1506   gfx::Range ui_actual_range;
1507   *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1508                                request_range_in_composition,
1509                                &ui_actual_range).ToCGRect());
1510   if (actual_range) {
1511     *actual_range = gfx::Range(
1512         composition_range_.start() + ui_actual_range.start(),
1513         composition_range_.start() + ui_actual_range.end()).ToNSRange();
1514   }
1515   return true;
1518 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1519       const gfx::Size& desired_size) {
1520   if (browser_compositor_) {
1521     return browser_compositor_->accelerated_widget_mac()->HasFrameOfSize(
1522         desired_size);
1523   }
1524   return false;
1527 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1528     uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1529   TRACE_EVENT0("browser", "RenderWidgetHostViewMac::OnSwapCompositorFrame");
1531   last_scroll_offset_ = frame->metadata.root_scroll_offset;
1533   page_at_minimum_scale_ = frame->metadata.page_scale_factor ==
1534                            frame->metadata.min_page_scale_factor;
1536   if (frame->delegated_frame_data) {
1537     float scale_factor = frame->metadata.device_scale_factor;
1539     // Compute the frame size based on the root render pass rect size.
1540     cc::RenderPass* root_pass =
1541         frame->delegated_frame_data->render_pass_list.back();
1542     gfx::Size pixel_size = root_pass->output_rect.size();
1543     gfx::Size dip_size = gfx::ConvertSizeToDIP(scale_factor, pixel_size);
1545     root_layer_->SetBounds(gfx::Rect(dip_size));
1546     if (!render_widget_host_->is_hidden()) {
1547       EnsureBrowserCompositorView();
1548       browser_compositor_->compositor()->SetScaleAndSize(
1549           scale_factor, pixel_size);
1550     }
1552     SendVSyncParametersToRenderer();
1554     delegated_frame_host_->SwapDelegatedFrame(
1555         output_surface_id,
1556         frame->delegated_frame_data.Pass(),
1557         frame->metadata.device_scale_factor,
1558         frame->metadata.latency_info,
1559         &frame->metadata.satisfies_sequences);
1560   } else {
1561     DLOG(ERROR) << "Received unexpected frame type.";
1562     bad_message::ReceivedBadMessage(render_widget_host_->GetProcess(),
1563                                     bad_message::RWHVM_UNEXPECTED_FRAME_TYPE);
1564   }
1567 void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) {
1568   *results = GetWebScreenInfo(GetNativeView());
1571 bool RenderWidgetHostViewMac::GetScreenColorProfile(
1572     std::vector<char>* color_profile) {
1573   DCHECK(color_profile->empty());
1574   NSWindow* window = GetWebContents()->GetTopLevelNativeWindow();
1575   return gfx::GetDisplayColorProfile(window, color_profile);
1578 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1579   // TODO(shess): In case of !window, the view has been removed from
1580   // the view hierarchy because the tab isn't main.  Could retrieve
1581   // the information from the main tab for our window.
1582   NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1583   if (!enclosing_window)
1584     return gfx::Rect();
1586   NSRect bounds = [enclosing_window frame];
1587   return FlipNSRectToRectScreen(bounds);
1590 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1591   // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1592   // completely on Mac OS.
1593   return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NULL_TRANSPORT);
1596 bool RenderWidgetHostViewMac::LockMouse() {
1597   if (mouse_locked_)
1598     return true;
1600   mouse_locked_ = true;
1602   // Lock position of mouse cursor and hide it.
1603   CGAssociateMouseAndMouseCursorPosition(NO);
1604   [NSCursor hide];
1606   // Clear the tooltip window.
1607   SetTooltipText(base::string16());
1609   return true;
1612 void RenderWidgetHostViewMac::UnlockMouse() {
1613   if (!mouse_locked_)
1614     return;
1615   mouse_locked_ = false;
1617   // Unlock position of mouse cursor and unhide it.
1618   CGAssociateMouseAndMouseCursorPosition(YES);
1619   [NSCursor unhide];
1621   if (render_widget_host_)
1622     render_widget_host_->LostMouseLock();
1625 void RenderWidgetHostViewMac::WheelEventAck(
1626     const blink::WebMouseWheelEvent& event,
1627     InputEventAckState ack_result) {
1628   bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
1629   // Only record a wheel event as unhandled if JavaScript handlers got a chance
1630   // to see it (no-op wheel events are ignored by the event dispatcher)
1631   if (event.deltaX || event.deltaY)
1632     [cocoa_view_ processedWheelEvent:event consumed:consumed];
1635 uint32_t RenderWidgetHostViewMac::GetSurfaceIdNamespace() {
1636   if (delegated_frame_host_)
1637     return delegated_frame_host_->GetSurfaceIdNamespace();
1639   return 0;
1642 uint32_t RenderWidgetHostViewMac::SurfaceIdNamespaceAtPoint(
1643     const gfx::Point& point,
1644     gfx::Point* transformed_point) {
1645   cc::SurfaceId id =
1646       delegated_frame_host_->SurfaceIdAtPoint(point, transformed_point);
1647   // It is possible that the renderer has not yet produced a surface, in which
1648   // case we return our current namespace.
1649   if (id.is_null())
1650     return GetSurfaceIdNamespace();
1651   return cc::SurfaceIdAllocator::NamespaceForId(id);
1654 void RenderWidgetHostViewMac::ProcessMouseEvent(
1655     const blink::WebMouseEvent& event) {
1656   render_widget_host_->ForwardMouseEvent(event);
1658 void RenderWidgetHostViewMac::ProcessMouseWheelEvent(
1659     const blink::WebMouseWheelEvent& event) {
1660   render_widget_host_->ForwardWheelEvent(event);
1663 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
1664   if (render_widget_host_)
1665     return render_widget_host_->Send(message);
1666   delete message;
1667   return false;
1670 void RenderWidgetHostViewMac::ShutdownHost() {
1671   weak_factory_.InvalidateWeakPtrs();
1672   render_widget_host_->Shutdown();
1673   // Do not touch any members at this point, |this| has been deleted.
1676 void RenderWidgetHostViewMac::ShutdownBrowserCompositor() {
1677   DestroyBrowserCompositorView();
1678   delegated_frame_host_.reset();
1679   root_layer_.reset();
1680   browser_compositor_placeholder_.reset();
1683 void RenderWidgetHostViewMac::SetActive(bool active) {
1684   if (render_widget_host_) {
1685     render_widget_host_->SetActive(active);
1686     if (active) {
1687       if (HasFocus())
1688         render_widget_host_->Focus();
1689     } else {
1690       render_widget_host_->Blur();
1691     }
1692   }
1693   if (HasFocus())
1694     SetTextInputActive(active);
1695   if (!active) {
1696     [cocoa_view_ setPluginImeActive:NO];
1697     UnlockMouse();
1698   }
1701 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
1702   if (render_widget_host_) {
1703     render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
1704         render_widget_host_->GetRoutingID(), visible));
1705   }
1708 void RenderWidgetHostViewMac::WindowFrameChanged() {
1709   if (render_widget_host_) {
1710     render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
1711         render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
1712         GetViewBounds()));
1713   }
1716 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
1717   RenderWidgetHostViewMacDictionaryHelper helper(this);
1718   helper.ShowDefinitionForSelection();
1721 void RenderWidgetHostViewMac::SetBackgroundColor(SkColor color) {
1722   RenderWidgetHostViewBase::SetBackgroundColor(color);
1723   bool opaque = GetBackgroundOpaque();
1725   if (render_widget_host_)
1726     render_widget_host_->SetBackgroundOpaque(opaque);
1728   [cocoa_view_ setOpaque:opaque];
1729   if (browser_compositor_state_ != BrowserCompositorDestroyed)
1730     browser_compositor_->compositor()->SetHostHasTransparentBackground(!opaque);
1733 BrowserAccessibilityManager*
1734     RenderWidgetHostViewMac::CreateBrowserAccessibilityManager(
1735         BrowserAccessibilityDelegate* delegate) {
1736   return new BrowserAccessibilityManagerMac(
1737       cocoa_view_,
1738       BrowserAccessibilityManagerMac::GetEmptyDocument(),
1739       delegate);
1742 gfx::Point RenderWidgetHostViewMac::AccessibilityOriginInScreen(
1743     const gfx::Rect& bounds) {
1744   NSPoint origin = NSMakePoint(bounds.x(), bounds.y());
1745   NSSize size = NSMakeSize(bounds.width(), bounds.height());
1746   origin.y = NSHeight([cocoa_view_ bounds]) - origin.y;
1747   NSPoint originInWindow = [cocoa_view_ convertPoint:origin toView:nil];
1748   NSPoint originInScreen =
1749       [[cocoa_view_ window] convertBaseToScreen:originInWindow];
1750   originInScreen.y = originInScreen.y - size.height;
1751   return gfx::Point(originInScreen.x, originInScreen.y);
1754 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
1755   if (active) {
1756     if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1757       EnablePasswordInput();
1758     else
1759       DisablePasswordInput();
1760   } else {
1761     if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1762       DisablePasswordInput();
1763   }
1766 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
1767                                                    int plugin_id) {
1768   [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
1771 void RenderWidgetHostViewMac::OnStartPluginIme() {
1772   [cocoa_view_ setPluginImeActive:YES];
1775 void RenderWidgetHostViewMac::OnGetRenderedTextCompleted(
1776     const std::string& text) {
1777   SpeakText(text);
1780 void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() {
1781   if (!render_widget_host_ || render_widget_host_->is_hidden())
1782     return;
1784   // Pausing for one view prevents others from receiving frames.
1785   // This may lead to large delays, causing overlaps. See crbug.com/352020.
1786   if (!allow_pause_for_resize_or_repaint_)
1787     return;
1789   // Wait for a frame of the right size to come in.
1790   if (browser_compositor_)
1791     browser_compositor_->accelerated_widget_mac()->BeginPumpingFrames();
1792   render_widget_host_->PauseForPendingResizeOrRepaints();
1793   if (browser_compositor_)
1794     browser_compositor_->accelerated_widget_mac()->EndPumpingFrames();
1797 ////////////////////////////////////////////////////////////////////////////////
1798 // gfx::DisplayObserver, public:
1800 void RenderWidgetHostViewMac::OnDisplayAdded(const gfx::Display& display) {
1803 void RenderWidgetHostViewMac::OnDisplayRemoved(const gfx::Display& display) {
1806 void RenderWidgetHostViewMac::OnDisplayMetricsChanged(
1807     const gfx::Display& display, uint32_t metrics) {
1808   gfx::Screen* screen = gfx::Screen::GetScreenFor(cocoa_view_);
1809   if (display.id() != screen->GetDisplayNearestWindow(cocoa_view_).id())
1810     return;
1812   UpdateScreenInfo(cocoa_view_);
1815 }  // namespace content
1817 // RenderWidgetHostViewCocoa ---------------------------------------------------
1819 @implementation RenderWidgetHostViewCocoa
1820 @synthesize selectedRange = selectedRange_;
1821 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
1822 @synthesize markedRange = markedRange_;
1824 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
1825   self = [super initWithFrame:NSZeroRect];
1826   if (self) {
1827     self.acceptsTouchEvents = YES;
1828     editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
1829     editCommand_helper_->AddEditingSelectorsToClass([self class]);
1831     renderWidgetHostView_.reset(r);
1832     canBeKeyView_ = YES;
1833     opaque_ = YES;
1834     focusedPluginIdentifier_ = -1;
1835     pinchHasReachedZoomThreshold_ = false;
1837     // OpenGL support:
1838     if ([self respondsToSelector:
1839         @selector(setWantsBestResolutionOpenGLSurface:)]) {
1840       [self setWantsBestResolutionOpenGLSurface:YES];
1841     }
1842     [[NSNotificationCenter defaultCenter]
1843         addObserver:self
1844            selector:@selector(didChangeScreenParameters:)
1845                name:NSApplicationDidChangeScreenParametersNotification
1846              object:nil];
1847   }
1848   return self;
1851 - (void)dealloc {
1852   if (responderDelegate_ &&
1853       [responderDelegate_ respondsToSelector:@selector(viewGone:)])
1854     [responderDelegate_ viewGone:self];
1855   responderDelegate_.reset();
1857   [[NSNotificationCenter defaultCenter] removeObserver:self];
1859   [super dealloc];
1862 - (void)didChangeScreenParameters:(NSNotification*)notify {
1863   g_screen_info_up_to_date = false;
1866 - (void)setResponderDelegate:
1867             (NSObject<RenderWidgetHostViewMacDelegate>*)delegate {
1868   DCHECK(!responderDelegate_);
1869   responderDelegate_.reset([delegate retain]);
1872 - (void)resetCursorRects {
1873   if (currentCursor_) {
1874     [self addCursorRect:[self visibleRect] cursor:currentCursor_];
1875     [currentCursor_ setOnMouseEntered:YES];
1876   }
1879 - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event
1880                    consumed:(BOOL)consumed {
1881   [responderDelegate_ rendererHandledWheelEvent:event consumed:consumed];
1884 - (BOOL)respondsToSelector:(SEL)selector {
1885   // Trickiness: this doesn't mean "does this object's superclass respond to
1886   // this selector" but rather "does the -respondsToSelector impl from the
1887   // superclass say that this class responds to the selector".
1888   if ([super respondsToSelector:selector])
1889     return YES;
1891   if (responderDelegate_)
1892     return [responderDelegate_ respondsToSelector:selector];
1894   return NO;
1897 - (id)forwardingTargetForSelector:(SEL)selector {
1898   if ([responderDelegate_ respondsToSelector:selector])
1899     return responderDelegate_.get();
1901   return [super forwardingTargetForSelector:selector];
1904 - (void)setCanBeKeyView:(BOOL)can {
1905   canBeKeyView_ = can;
1908 - (BOOL)acceptsMouseEventsWhenInactive {
1909   // Some types of windows (balloons, always-on-top panels) want to accept mouse
1910   // clicks w/o the first click being treated as 'activation'. Same applies to
1911   // mouse move events.
1912   return [[self window] level] > NSNormalWindowLevel;
1915 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
1916   return [self acceptsMouseEventsWhenInactive];
1919 - (void)setCloseOnDeactivate:(BOOL)b {
1920   closeOnDeactivate_ = b;
1923 - (void)setOpaque:(BOOL)opaque {
1924   opaque_ = opaque;
1927 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
1928   NSWindow* window = [self window];
1929   // If this is a background window, don't handle mouse movement events. This
1930   // is the expected behavior on the Mac as evidenced by other applications.
1931   if ([theEvent type] == NSMouseMoved &&
1932       ![self acceptsMouseEventsWhenInactive] &&
1933       ![window isKeyWindow]) {
1934     return YES;
1935   }
1937   // Use hitTest to check whether the mouse is over a nonWebContentView - in
1938   // which case the mouse event should not be handled by the render host.
1939   const SEL nonWebContentViewSelector = @selector(nonWebContentView);
1940   NSView* contentView = [window contentView];
1941   NSView* view = [contentView hitTest:[theEvent locationInWindow]];
1942   // Traverse the superview hierarchy as the hitTest will return the frontmost
1943   // view, such as an NSTextView, while nonWebContentView may be specified by
1944   // its parent view.
1945   while (view) {
1946     if ([view respondsToSelector:nonWebContentViewSelector] &&
1947         [view performSelector:nonWebContentViewSelector]) {
1948       // The cursor is over a nonWebContentView - ignore this mouse event.
1949       return YES;
1950     }
1951     if ([view isKindOfClass:[self class]] && ![view isEqual:self] &&
1952         !hasOpenMouseDown_) {
1953       // The cursor is over an overlapping render widget. This check is done by
1954       // both views so the one that's returned by -hitTest: will end up
1955       // processing the event.
1956       // Note that while dragging, we only get events for the render view where
1957       // drag started, even if mouse is  actually over another view or outside
1958       // the window. Cocoa does this for us. We should handle these events and
1959       // not ignore (since there is no other render view to handle them). Thus
1960       // the |!hasOpenMouseDown_| check above.
1961       return YES;
1962     }
1963     view = [view superview];
1964   }
1965   return NO;
1968 - (void)mouseEvent:(NSEvent*)theEvent {
1969   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
1970   if (responderDelegate_ &&
1971       [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
1972     BOOL handled = [responderDelegate_ handleEvent:theEvent];
1973     if (handled)
1974       return;
1975   }
1977   if ([self shouldIgnoreMouseEvent:theEvent]) {
1978     // If this is the first such event, send a mouse exit to the host view.
1979     if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
1980       WebMouseEvent exitEvent =
1981           WebInputEventFactory::mouseEvent(theEvent, self);
1982       exitEvent.type = WebInputEvent::MouseLeave;
1983       exitEvent.button = WebMouseEvent::ButtonNone;
1984       renderWidgetHostView_->ForwardMouseEvent(exitEvent);
1985     }
1986     mouseEventWasIgnored_ = YES;
1987     return;
1988   }
1990   if (mouseEventWasIgnored_) {
1991     // If this is the first mouse event after a previous event that was ignored
1992     // due to the hitTest, send a mouse enter event to the host view.
1993     if (renderWidgetHostView_->render_widget_host_) {
1994       WebMouseEvent enterEvent =
1995           WebInputEventFactory::mouseEvent(theEvent, self);
1996       enterEvent.type = WebInputEvent::MouseMove;
1997       enterEvent.button = WebMouseEvent::ButtonNone;
1998       if (renderWidgetHostView_->render_widget_host_->delegate() &&
1999           renderWidgetHostView_->render_widget_host_->delegate()
2000               ->GetInputEventRouter()) {
2001         renderWidgetHostView_->render_widget_host_->delegate()
2002             ->GetInputEventRouter()
2003             ->RouteMouseEvent(renderWidgetHostView_.get(), &enterEvent);
2004       } else {
2005         renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2006       }
2007     }
2008   }
2009   mouseEventWasIgnored_ = NO;
2011   // Don't cancel child popups; killing them on a mouse click would prevent the
2012   // user from positioning the insertion point in the text field spawning the
2013   // popup. A click outside the text field would cause the text field to drop
2014   // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2015   // the popup anyway, so we're OK.
2017   NSEventType type = [theEvent type];
2018   if (type == NSLeftMouseDown)
2019     hasOpenMouseDown_ = YES;
2020   else if (type == NSLeftMouseUp)
2021     hasOpenMouseDown_ = NO;
2023   // TODO(suzhe): We should send mouse events to the input method first if it
2024   // wants to handle them. But it won't work without implementing method
2025   // - (NSUInteger)characterIndexForPoint:.
2026   // See: http://code.google.com/p/chromium/issues/detail?id=47141
2027   // Instead of sending mouse events to the input method first, we now just
2028   // simply confirm all ongoing composition here.
2029   if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2030       type == NSOtherMouseDown) {
2031     [self confirmComposition];
2032   }
2034   WebMouseEvent event = WebInputEventFactory::mouseEvent(theEvent, self);
2035   if (renderWidgetHostView_->render_widget_host_->delegate() &&
2036       renderWidgetHostView_->render_widget_host_->delegate()
2037           ->GetInputEventRouter()) {
2038     renderWidgetHostView_->render_widget_host_->delegate()
2039         ->GetInputEventRouter()
2040         ->RouteMouseEvent(renderWidgetHostView_.get(), &event);
2041   } else {
2042     renderWidgetHostView_->ForwardMouseEvent(event);
2043   }
2046 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2047   // |performKeyEquivalent:| is sent to all views of a window, not only down the
2048   // responder chain (cf. "Handling Key Equivalents" in
2049   // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2050   // ). We only want to handle key equivalents if we're first responder.
2051   if ([[self window] firstResponder] != self)
2052     return NO;
2054   // If the event is reserved by the system, then do not pass it to web content.
2055   if (EventIsReservedBySystem(theEvent))
2056     return NO;
2058   // If we return |NO| from this function, cocoa will send the key event to
2059   // the menu and only if the menu does not process the event to |keyDown:|. We
2060   // want to send the event to a renderer _before_ sending it to the menu, so
2061   // we need to return |YES| for all events that might be swallowed by the menu.
2062   // We do not return |YES| for every keypress because we don't get |keyDown:|
2063   // events for keys that we handle this way.
2064   NSUInteger modifierFlags = [theEvent modifierFlags];
2065   if ((modifierFlags & NSCommandKeyMask) == 0) {
2066     // Make sure the menu does not contain key equivalents that don't
2067     // contain cmd.
2068     DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2069     return NO;
2070   }
2072   // Command key combinations are sent via performKeyEquivalent rather than
2073   // keyDown:. We just forward this on and if WebCore doesn't want to handle
2074   // it, we let the WebContentsView figure out how to reinject it.
2075   [self keyEvent:theEvent wasKeyEquivalent:YES];
2076   return YES;
2079 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2080   // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2081   // returned NO. If this function returns |YES|, Cocoa sends the event to
2082   // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2083   // to us instead of doing key view loop control, ctrl-left/right get handled
2084   // correctly, etc.
2085   // (However, there are still some keys that Cocoa swallows, e.g. the key
2086   // equivalent that Cocoa uses for toggling the input language. In this case,
2087   // that's actually a good thing, though -- see http://crbug.com/26115 .)
2088   return YES;
2091 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2092   if (responderDelegate_ &&
2093       [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2094     BOOL handled = [responderDelegate_ handleEvent:theEvent];
2095     if (handled)
2096       return kEventHandled;
2097   }
2099   [self keyEvent:theEvent wasKeyEquivalent:NO];
2100   return kEventHandled;
2103 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2104   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2106   // If the user changes the system hotkey mapping after Chrome has been
2107   // launched, then it is possible that a formerly reserved system hotkey is no
2108   // longer reserved. The hotkey would have skipped the renderer, but would
2109   // also have not been handled by the system. If this is the case, immediately
2110   // return.
2111   // TODO(erikchen): SystemHotkeyHelperMac should use the File System Events
2112   // api to monitor changes to system hotkeys. This logic will have to be
2113   // updated.
2114   // http://crbug.com/383558.
2115   if (EventIsReservedBySystem(theEvent))
2116     return;
2118   DCHECK([theEvent type] != NSKeyDown ||
2119          !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2121   if ([theEvent type] == NSFlagsChanged) {
2122     // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2123     // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2124     int keyCode = [theEvent keyCode];
2125     if (!keyCode || keyCode == 10 || keyCode == 63)
2126       return;
2127   }
2129   // Don't cancel child popups; the key events are probably what's triggering
2130   // the popup in the first place.
2132   RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2133   DCHECK(widgetHost);
2135   NativeWebKeyboardEvent event(theEvent);
2137   // Force fullscreen windows to close on Escape so they won't keep the keyboard
2138   // grabbed or be stuck onscreen if the renderer is hanging.
2139   if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2140       event.windowsKeyCode == ui::VKEY_ESCAPE &&
2141       renderWidgetHostView_->pepper_fullscreen_window()) {
2142     RenderWidgetHostViewMac* parent =
2143         renderWidgetHostView_->fullscreen_parent_host_view();
2144     if (parent)
2145       parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2146     widgetHost->Shutdown();
2147     return;
2148   }
2150   // Suppress the escape key up event if necessary.
2151   if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2152     if (event.type == NativeWebKeyboardEvent::KeyUp)
2153       suppressNextEscapeKeyUp_ = NO;
2154     return;
2155   }
2157   // Do not forward key up events unless preceded by a matching key down,
2158   // otherwise we might get an event from releasing the return key in the
2159   // omnibox (http://crbug.com/338736).
2160   if ([theEvent type] == NSKeyUp) {
2161     auto numErased = keyDownCodes_.erase([theEvent keyCode]);
2162     if (numErased < 1)
2163       return;
2164   }
2166   // We only handle key down events and just simply forward other events.
2167   if ([theEvent type] != NSKeyDown) {
2168     widgetHost->ForwardKeyboardEvent(event);
2170     // Possibly autohide the cursor.
2171     if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2172       [NSCursor setHiddenUntilMouseMoves:YES];
2174     return;
2175   }
2177   keyDownCodes_.insert([theEvent keyCode]);
2179   base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2181   // Records the current marked text state, so that we can know if the marked
2182   // text was deleted or not after handling the key down event.
2183   BOOL oldHasMarkedText = hasMarkedText_;
2185   // This method should not be called recursively.
2186   DCHECK(!handlingKeyDown_);
2188   // Tells insertText: and doCommandBySelector: that we are handling a key
2189   // down event.
2190   handlingKeyDown_ = YES;
2192   // These variables might be set when handling the keyboard event.
2193   // Clear them here so that we can know whether they have changed afterwards.
2194   textToBeInserted_.clear();
2195   markedText_.clear();
2196   markedTextSelectedRange_ = NSMakeRange(NSNotFound, 0);
2197   underlines_.clear();
2198   unmarkTextCalled_ = NO;
2199   hasEditCommands_ = NO;
2200   editCommands_.clear();
2202   // Before doing anything with a key down, check to see if plugin IME has been
2203   // cancelled, since the plugin host needs to be informed of that before
2204   // receiving the keydown.
2205   if ([theEvent type] == NSKeyDown)
2206     [self checkForPluginImeCancellation];
2208   // Sends key down events to input method first, then we can decide what should
2209   // be done according to input method's feedback.
2210   // If a plugin is active, bypass this step since events are forwarded directly
2211   // to the plugin IME.
2212   if (focusedPluginIdentifier_ == -1)
2213     [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2215   handlingKeyDown_ = NO;
2217   // Indicates if we should send the key event and corresponding editor commands
2218   // after processing the input method result.
2219   BOOL delayEventUntilAfterImeCompostion = NO;
2221   // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2222   // while an input method is composing or inserting a text.
2223   // Gmail checks this code in its onkeydown handler to stop auto-completing
2224   // e-mail addresses while composing a CJK text.
2225   // If the text to be inserted has only one character, then we don't need this
2226   // trick, because we'll send the text as a key press event instead.
2227   if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2228     NativeWebKeyboardEvent fakeEvent = event;
2229     fakeEvent.windowsKeyCode = 0xE5;  // VKEY_PROCESSKEY
2230     fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2231     fakeEvent.skip_in_browser = true;
2232     widgetHost->ForwardKeyboardEvent(fakeEvent);
2233     // If this key event was handled by the input method, but
2234     // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2235     // enqueued edit commands, then in order to let webkit handle them
2236     // correctly, we need to send the real key event and corresponding edit
2237     // commands after processing the input method result.
2238     // We shouldn't do this if a new marked text was set by the input method,
2239     // otherwise the new marked text might be cancelled by webkit.
2240     if (hasEditCommands_ && !hasMarkedText_)
2241       delayEventUntilAfterImeCompostion = YES;
2242   } else {
2243     if (!editCommands_.empty()) {
2244       widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2245           widgetHost->GetRoutingID(), editCommands_));
2246     }
2247     widgetHost->ForwardKeyboardEvent(event);
2248   }
2250   // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2251   // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2252   // be set to NULL. So we check it here and return immediately if it's NULL.
2253   if (!renderWidgetHostView_->render_widget_host_)
2254     return;
2256   // Then send keypress and/or composition related events.
2257   // If there was a marked text or the text to be inserted is longer than 1
2258   // character, then we send the text by calling ConfirmComposition().
2259   // Otherwise, if the text to be inserted only contains 1 character, then we
2260   // can just send a keypress event which is fabricated by changing the type of
2261   // the keydown event, so that we can retain all necessary informations, such
2262   // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2263   // prevent the browser from handling it again.
2264   // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2265   // handle BMP characters here, as we can always insert non-BMP characters as
2266   // text.
2267   BOOL textInserted = NO;
2268   if (textToBeInserted_.length() >
2269       ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2270     widgetHost->ImeConfirmComposition(
2271         textToBeInserted_, gfx::Range::InvalidRange(), false);
2272     textInserted = YES;
2273   }
2275   // Updates or cancels the composition. If some text has been inserted, then
2276   // we don't need to cancel the composition explicitly.
2277   if (hasMarkedText_ && markedText_.length()) {
2278     // Sends the updated marked text to the renderer so it can update the
2279     // composition node in WebKit.
2280     // When marked text is available, |markedTextSelectedRange_| will be the
2281     // range being selected inside the marked text.
2282     widgetHost->ImeSetComposition(markedText_, underlines_,
2283                                   markedTextSelectedRange_.location,
2284                                   NSMaxRange(markedTextSelectedRange_));
2285   } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2286     if (unmarkTextCalled_) {
2287       widgetHost->ImeConfirmComposition(
2288           base::string16(), gfx::Range::InvalidRange(), false);
2289     } else {
2290       widgetHost->ImeCancelComposition();
2291     }
2292   }
2294   // If the key event was handled by the input method but it also generated some
2295   // edit commands, then we need to send the real key event and corresponding
2296   // edit commands here. This usually occurs when the input method wants to
2297   // finish current composition session but still wants the application to
2298   // handle the key event. See http://crbug.com/48161 for reference.
2299   if (delayEventUntilAfterImeCompostion) {
2300     // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2301     // with windowsKeyCode == 0xE5 has already been sent to webkit.
2302     // So before sending the real key down event, we need to send a fake key up
2303     // event to balance it.
2304     NativeWebKeyboardEvent fakeEvent = event;
2305     fakeEvent.type = blink::WebInputEvent::KeyUp;
2306     fakeEvent.skip_in_browser = true;
2307     widgetHost->ForwardKeyboardEvent(fakeEvent);
2308     // Not checking |renderWidgetHostView_->render_widget_host_| here because
2309     // a key event with |skip_in_browser| == true won't be handled by browser,
2310     // thus it won't destroy the widget.
2312     if (!editCommands_.empty()) {
2313       widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2314           widgetHost->GetRoutingID(), editCommands_));
2315     }
2316     widgetHost->ForwardKeyboardEvent(event);
2318     // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2319     // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2320     // be set to NULL. So we check it here and return immediately if it's NULL.
2321     if (!renderWidgetHostView_->render_widget_host_)
2322       return;
2323   }
2325   const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2326   // Only send a corresponding key press event if there is no marked text.
2327   if (!hasMarkedText_) {
2328     if (!textInserted && textToBeInserted_.length() == 1) {
2329       // If a single character was inserted, then we just send it as a keypress
2330       // event.
2331       event.type = blink::WebInputEvent::Char;
2332       event.text[0] = textToBeInserted_[0];
2333       event.text[1] = 0;
2334       event.skip_in_browser = true;
2335       widgetHost->ForwardKeyboardEvent(event);
2336     } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2337                [[theEvent characters] length] > 0 &&
2338                (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2339                 (hasEditCommands_ && editCommands_.empty()))) {
2340       // We don't get insertText: calls if ctrl or cmd is down, or the key event
2341       // generates an insert command. So synthesize a keypress event for these
2342       // cases, unless the key event generated any other command.
2343       event.type = blink::WebInputEvent::Char;
2344       event.skip_in_browser = true;
2345       widgetHost->ForwardKeyboardEvent(event);
2346     }
2347   }
2349   // Possibly autohide the cursor.
2350   if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2351     [NSCursor setHiddenUntilMouseMoves:YES];
2354 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2355   DCHECK(base::mac::IsOSLionOrLater());
2357   if ([event phase] != NSEventPhaseEnded &&
2358       [event phase] != NSEventPhaseCancelled) {
2359     return;
2360   }
2362   if (renderWidgetHostView_->render_widget_host_) {
2363     // History-swiping is not possible if the logic reaches this point.
2364     // Allow rubber-banding in both directions.
2365     bool canRubberbandLeft = true;
2366     bool canRubberbandRight = true;
2367     WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
2368         event, self, canRubberbandLeft, canRubberbandRight);
2369     webEvent.railsMode = mouseWheelFilter_.UpdateRailsMode(webEvent);
2370     renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2371   }
2373   if (endWheelMonitor_) {
2374     [NSEvent removeMonitor:endWheelMonitor_];
2375     endWheelMonitor_ = nil;
2376   }
2379 - (void)beginGestureWithEvent:(NSEvent*)event {
2380   [responderDelegate_ beginGestureWithEvent:event];
2381   gestureBeginEvent_.reset(
2382       new WebGestureEvent(WebInputEventFactory::gestureEvent(event, self)));
2384   // If the page is at the minimum zoom level, require a threshold be reached
2385   // before the pinch has an effect.
2386   if (renderWidgetHostView_->page_at_minimum_scale_) {
2387     pinchHasReachedZoomThreshold_ = false;
2388     pinchUnusedAmount_ = 1;
2389   }
2392 - (void)endGestureWithEvent:(NSEvent*)event {
2393   [responderDelegate_ endGestureWithEvent:event];
2394   gestureBeginEvent_.reset();
2396   if (!renderWidgetHostView_->render_widget_host_)
2397     return;
2399   if (gestureBeginPinchSent_) {
2400     WebGestureEvent endEvent(WebInputEventFactory::gestureEvent(event, self));
2401     endEvent.type = WebInputEvent::GesturePinchEnd;
2402     renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(endEvent);
2403     gestureBeginPinchSent_ = NO;
2404   }
2407 - (void)touchesMovedWithEvent:(NSEvent*)event {
2408   [responderDelegate_ touchesMovedWithEvent:event];
2411 - (void)touchesBeganWithEvent:(NSEvent*)event {
2412   [responderDelegate_ touchesBeganWithEvent:event];
2415 - (void)touchesCancelledWithEvent:(NSEvent*)event {
2416   [responderDelegate_ touchesCancelledWithEvent:event];
2419 - (void)touchesEndedWithEvent:(NSEvent*)event {
2420   [responderDelegate_ touchesEndedWithEvent:event];
2423 - (void)smartMagnifyWithEvent:(NSEvent*)event {
2424   const WebGestureEvent& smartMagnifyEvent =
2425       WebInputEventFactory::gestureEvent(event, self);
2426   if (renderWidgetHostView_ && renderWidgetHostView_->render_widget_host_) {
2427     renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(
2428         smartMagnifyEvent);
2429   }
2432 // This is invoked only on 10.8 or newer when the user taps a word using
2433 // three fingers.
2434 - (void)quickLookWithEvent:(NSEvent*)event {
2435   NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
2436   TextInputClientMac::GetInstance()->GetStringAtPoint(
2437       renderWidgetHostView_->render_widget_host_,
2438       gfx::Point(point.x, NSHeight([self frame]) - point.y),
2439       ^(NSAttributedString* string, NSPoint baselinePoint) {
2440           if (string && [string length] > 0) {
2441             dispatch_async(dispatch_get_main_queue(), ^{
2442                 [self showDefinitionForAttributedString:string
2443                                                 atPoint:baselinePoint];
2444             });
2445           }
2446       }
2447   );
2450 // This method handles 2 different types of hardware events.
2451 // (Apple does not distinguish between them).
2452 //  a. Scrolling the middle wheel of a mouse.
2453 //  b. Swiping on the track pad.
2455 // This method is responsible for 2 types of behavior:
2456 //  a. Scrolling the content of window.
2457 //  b. Navigating forwards/backwards in history.
2459 // This is a brief description of the logic:
2460 //  1. If the content can be scrolled, scroll the content.
2461 //     (This requires a roundtrip to blink to determine whether the content
2462 //      can be scrolled.)
2463 //     Once this logic is triggered, the navigate logic cannot be triggered
2464 //     until the gesture finishes.
2465 //  2. If the user is making a horizontal swipe, start the navigate
2466 //     forward/backwards UI.
2467 //     Once this logic is triggered, the user can either cancel or complete
2468 //     the gesture. If the user completes the gesture, all remaining touches
2469 //     are swallowed, and not allowed to scroll the content. If the user
2470 //     cancels the gesture, all remaining touches are forwarded to the content
2471 //     scroll logic. The user cannot trigger the navigation logic again.
2472 - (void)scrollWheel:(NSEvent*)event {
2473   if (responderDelegate_ &&
2474       [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2475     BOOL handled = [responderDelegate_ handleEvent:event];
2476     if (handled)
2477       return;
2478   }
2480   // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
2481   // the event is received even when the mouse cursor is no longer over the view
2482   // when the scrolling ends (e.g. if the tab was switched). This is necessary
2483   // for ending rubber-banding in such cases.
2484   if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
2485       !endWheelMonitor_) {
2486     endWheelMonitor_ =
2487       [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
2488       handler:^(NSEvent* blockEvent) {
2489           [self shortCircuitScrollWheelEvent:blockEvent];
2490           return blockEvent;
2491       }];
2492   }
2494   // This is responsible for content scrolling!
2495   if (renderWidgetHostView_->render_widget_host_) {
2496     BOOL canRubberbandLeft = [responderDelegate_ canRubberbandLeft:self];
2497     BOOL canRubberbandRight = [responderDelegate_ canRubberbandRight:self];
2498     WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
2499         event, self, canRubberbandLeft, canRubberbandRight);
2500     webEvent.railsMode = mouseWheelFilter_.UpdateRailsMode(webEvent);
2501     if (renderWidgetHostView_->render_widget_host_->delegate() &&
2502         renderWidgetHostView_->render_widget_host_->delegate()
2503             ->GetInputEventRouter()) {
2504       renderWidgetHostView_->render_widget_host_->delegate()
2505           ->GetInputEventRouter()
2506           ->RouteMouseWheelEvent(renderWidgetHostView_.get(), &webEvent);
2507     } else {
2508       renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2509     }
2510   }
2513 // Called repeatedly during a pinch gesture, with incremental change values.
2514 - (void)magnifyWithEvent:(NSEvent*)event {
2515   if (!renderWidgetHostView_->render_widget_host_)
2516     return;
2518   // If, due to nesting of multiple gestures (e.g, from multiple touch
2519   // devices), the beginning of the gesture has been lost, skip the remainder
2520   // of the gesture.
2521   if (!gestureBeginEvent_)
2522     return;
2524   if (!pinchHasReachedZoomThreshold_) {
2525       pinchUnusedAmount_ *= (1 + [event magnification]);
2526       if (pinchUnusedAmount_ < 0.667 || pinchUnusedAmount_ > 1.5)
2527           pinchHasReachedZoomThreshold_ = true;
2528   }
2530   // Send a GesturePinchBegin event if none has been sent yet.
2531   if (!gestureBeginPinchSent_) {
2532     WebGestureEvent beginEvent(*gestureBeginEvent_);
2533     beginEvent.type = WebInputEvent::GesturePinchBegin;
2534     renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(beginEvent);
2535     gestureBeginPinchSent_ = YES;
2536   }
2538   // Send a GesturePinchUpdate event.
2539   WebGestureEvent updateEvent =
2540       WebInputEventFactory::gestureEvent(event, self);
2541   updateEvent.data.pinchUpdate.zoomDisabled = !pinchHasReachedZoomThreshold_;
2542   renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(updateEvent);
2545 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
2546   NSWindow* oldWindow = [self window];
2548   NSNotificationCenter* notificationCenter =
2549       [NSNotificationCenter defaultCenter];
2551   // Backing property notifications crash on 10.6 when building with the 10.7
2552   // SDK, see http://crbug.com/260595.
2553   static BOOL supportsBackingPropertiesNotification =
2554       SupportsBackingPropertiesChangedNotification();
2556   if (oldWindow) {
2557     if (supportsBackingPropertiesNotification) {
2558       [notificationCenter
2559           removeObserver:self
2560                     name:NSWindowDidChangeBackingPropertiesNotification
2561                   object:oldWindow];
2562     }
2563     [notificationCenter
2564         removeObserver:self
2565                   name:NSWindowDidMoveNotification
2566                 object:oldWindow];
2567     [notificationCenter
2568         removeObserver:self
2569                   name:NSWindowDidEndLiveResizeNotification
2570                 object:oldWindow];
2571   }
2572   if (newWindow) {
2573     if (supportsBackingPropertiesNotification) {
2574       [notificationCenter
2575           addObserver:self
2576              selector:@selector(windowDidChangeBackingProperties:)
2577                  name:NSWindowDidChangeBackingPropertiesNotification
2578                object:newWindow];
2579     }
2580     [notificationCenter
2581         addObserver:self
2582            selector:@selector(windowChangedGlobalFrame:)
2583                name:NSWindowDidMoveNotification
2584              object:newWindow];
2585     [notificationCenter
2586         addObserver:self
2587            selector:@selector(windowChangedGlobalFrame:)
2588                name:NSWindowDidEndLiveResizeNotification
2589              object:newWindow];
2590   }
2593 - (void)updateScreenProperties{
2594   renderWidgetHostView_->UpdateBackingStoreProperties();
2595   renderWidgetHostView_->UpdateDisplayLink();
2598 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
2599 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
2600   // Background tabs check if their screen scale factor, color profile, and
2601   // vsync properties changed when they are added to a window.
2603   // Allocating a CGLayerRef with the current scale factor immediately from
2604   // this handler doesn't work. Schedule the backing store update on the
2605   // next runloop cycle, then things are read for CGLayerRef allocations to
2606   // work.
2607   [self performSelector:@selector(updateScreenProperties)
2608              withObject:nil
2609              afterDelay:0];
2612 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
2613   renderWidgetHostView_->UpdateScreenInfo(
2614       renderWidgetHostView_->GetNativeView());
2617 - (void)setFrameSize:(NSSize)newSize {
2618   TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
2620   // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
2621   // -setFrame: isn't neccessary.
2622   [super setFrameSize:newSize];
2624   if (!renderWidgetHostView_->render_widget_host_)
2625     return;
2627   renderWidgetHostView_->render_widget_host_->SendScreenRects();
2628   renderWidgetHostView_->render_widget_host_->WasResized();
2629   if (renderWidgetHostView_->delegated_frame_host_)
2630     renderWidgetHostView_->delegated_frame_host_->WasResized();
2632   // Wait for the frame that WasResize might have requested. If the view is
2633   // being made visible at a new size, then this call will have no effect
2634   // because the view widget is still hidden, and the pause call in WasShown
2635   // will have this effect for us.
2636   renderWidgetHostView_->PauseForPendingResizeOrRepaintsAndDraw();
2639 - (BOOL)canBecomeKeyView {
2640   if (!renderWidgetHostView_->render_widget_host_)
2641     return NO;
2643   return canBeKeyView_;
2646 - (BOOL)acceptsFirstResponder {
2647   if (!renderWidgetHostView_->render_widget_host_)
2648     return NO;
2650   return canBeKeyView_;
2653 - (BOOL)becomeFirstResponder {
2654   if (!renderWidgetHostView_->render_widget_host_)
2655     return NO;
2657   renderWidgetHostView_->render_widget_host_->Focus();
2658   renderWidgetHostView_->SetTextInputActive(true);
2660   // Cancel any onging composition text which was left before we lost focus.
2661   // TODO(suzhe): We should do it in -resignFirstResponder: method, but
2662   // somehow that method won't be called when switching among different tabs.
2663   // See http://crbug.com/47209
2664   [self cancelComposition];
2666   NSNumber* direction = [NSNumber numberWithUnsignedInteger:
2667       [[self window] keyViewSelectionDirection]];
2668   NSDictionary* userInfo =
2669       [NSDictionary dictionaryWithObject:direction
2670                                   forKey:kSelectionDirection];
2671   [[NSNotificationCenter defaultCenter]
2672       postNotificationName:kViewDidBecomeFirstResponder
2673                     object:self
2674                   userInfo:userInfo];
2676   return YES;
2679 - (BOOL)resignFirstResponder {
2680   renderWidgetHostView_->SetTextInputActive(false);
2681   if (!renderWidgetHostView_->render_widget_host_)
2682     return YES;
2684   if (closeOnDeactivate_)
2685     renderWidgetHostView_->KillSelf();
2687   renderWidgetHostView_->render_widget_host_->Blur();
2689   // We should cancel any onging composition whenever RWH's Blur() method gets
2690   // called, because in this case, webkit will confirm the ongoing composition
2691   // internally.
2692   [self cancelComposition];
2694   return YES;
2697 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
2698   if (responderDelegate_ &&
2699       [responderDelegate_
2700           respondsToSelector:@selector(validateUserInterfaceItem:
2701                                                      isValidItem:)]) {
2702     BOOL valid;
2703     BOOL known =
2704         [responderDelegate_ validateUserInterfaceItem:item isValidItem:&valid];
2705     if (known)
2706       return valid;
2707   }
2709   SEL action = [item action];
2711   if (action == @selector(stopSpeaking:)) {
2712     return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
2713            renderWidgetHostView_->IsSpeaking();
2714   }
2715   if (action == @selector(startSpeaking:)) {
2716     return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
2717            renderWidgetHostView_->SupportsSpeech();
2718   }
2720   // For now, these actions are always enabled for render view,
2721   // this is sub-optimal.
2722   // TODO(suzhe): Plumb the "can*" methods up from WebCore.
2723   if (action == @selector(undo:) ||
2724       action == @selector(redo:) ||
2725       action == @selector(cut:) ||
2726       action == @selector(copy:) ||
2727       action == @selector(copyToFindPboard:) ||
2728       action == @selector(paste:) ||
2729       action == @selector(pasteAndMatchStyle:)) {
2730     return renderWidgetHostView_->render_widget_host_->IsRenderView();
2731   }
2733   return editCommand_helper_->IsMenuItemEnabled(action, self);
2736 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
2737   return renderWidgetHostView_.get();
2740 // Determine whether we should autohide the cursor (i.e., hide it until mouse
2741 // move) for the given event. Customize here to be more selective about which
2742 // key presses to autohide on.
2743 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
2744   return ([event type] == NSKeyDown &&
2745              !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
2748 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
2749                                          index:(NSUInteger)index
2750                                       maxCount:(NSUInteger)maxCount {
2751   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
2752   NSUInteger totalLength = [fullArray count];
2753   if (index >= totalLength)
2754     return nil;
2755   NSUInteger length = MIN(totalLength - index, maxCount);
2756   return [fullArray subarrayWithRange:NSMakeRange(index, length)];
2759 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
2760   NSArray* fullArray = [self accessibilityAttributeValue:attribute];
2761   return [fullArray count];
2764 - (id)accessibilityAttributeValue:(NSString *)attribute {
2765   BrowserAccessibilityManager* manager =
2766       renderWidgetHostView_->render_widget_host_
2767           ->GetRootBrowserAccessibilityManager();
2769   // Contents specifies document view of RenderWidgetHostViewCocoa provided by
2770   // BrowserAccessibilityManager. Children includes all subviews in addition to
2771   // contents. Currently we do not have subviews besides the document view.
2772   if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
2773           [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
2774       manager) {
2775     return [NSArray arrayWithObjects:manager->
2776         GetRoot()->ToBrowserAccessibilityCocoa(), nil];
2777   } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
2778     return NSAccessibilityScrollAreaRole;
2779   }
2780   id ret = [super accessibilityAttributeValue:attribute];
2781   return ret;
2784 - (NSArray*)accessibilityAttributeNames {
2785   NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
2786   [ret addObject:NSAccessibilityContentsAttribute];
2787   [ret addObjectsFromArray:[super accessibilityAttributeNames]];
2788   return ret;
2791 - (id)accessibilityHitTest:(NSPoint)point {
2792   BrowserAccessibilityManager* manager =
2793       renderWidgetHostView_->render_widget_host_
2794           ->GetRootBrowserAccessibilityManager();
2795   if (!manager)
2796     return self;
2797   NSPoint pointInWindow = [[self window] convertScreenToBase:point];
2798   NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
2799   localPoint.y = NSHeight([self bounds]) - localPoint.y;
2800   BrowserAccessibilityCocoa* root =
2801       manager->GetRoot()->ToBrowserAccessibilityCocoa();
2802   id obj = [root accessibilityHitTest:localPoint];
2803   return obj;
2806 - (BOOL)accessibilityIsIgnored {
2807   BrowserAccessibilityManager* manager =
2808       renderWidgetHostView_->render_widget_host_
2809           ->GetRootBrowserAccessibilityManager();
2810   return !manager;
2813 - (NSUInteger)accessibilityGetIndexOf:(id)child {
2814   BrowserAccessibilityManager* manager =
2815       renderWidgetHostView_->render_widget_host_
2816           ->GetRootBrowserAccessibilityManager();
2817   // Only child is root.
2818   if (manager &&
2819       manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
2820     return 0;
2821   } else {
2822     return NSNotFound;
2823   }
2826 - (id)accessibilityFocusedUIElement {
2827   BrowserAccessibilityManager* manager =
2828       renderWidgetHostView_->render_widget_host_
2829           ->GetRootBrowserAccessibilityManager();
2830   if (manager) {
2831     BrowserAccessibility* focused_item = manager->GetFocus(NULL);
2832     DCHECK(focused_item);
2833     if (focused_item) {
2834       BrowserAccessibilityCocoa* focused_item_cocoa =
2835           focused_item->ToBrowserAccessibilityCocoa();
2836       DCHECK(focused_item_cocoa);
2837       if (focused_item_cocoa)
2838         return focused_item_cocoa;
2839     }
2840   }
2841   return [super accessibilityFocusedUIElement];
2844 // Below is our NSTextInputClient implementation.
2846 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
2847 // functions to process this event.
2849 // [WebHTMLView keyDown] ->
2850 //     EventHandler::keyEvent() ->
2851 //     ...
2852 //     [WebEditorClient handleKeyboardEvent] ->
2853 //     [WebHTMLView _interceptEditingKeyEvent] ->
2854 //     [NSResponder interpretKeyEvents] ->
2855 //     [WebHTMLView insertText] ->
2856 //     Editor::insertText()
2858 // Unfortunately, it is hard for Chromium to use this implementation because
2859 // it causes key-typing jank.
2860 // RenderWidgetHostViewMac is running in a browser process. On the other
2861 // hand, Editor and EventHandler are running in a renderer process.
2862 // So, if we used this implementation, a NSKeyDown event is dispatched to
2863 // the following functions of Chromium.
2865 // [RenderWidgetHostViewMac keyEvent] (browser) ->
2866 //     |Sync IPC (KeyDown)| (*1) ->
2867 //     EventHandler::keyEvent() (renderer) ->
2868 //     ...
2869 //     EditorClientImpl::handleKeyboardEvent() (renderer) ->
2870 //     |Sync IPC| (*2) ->
2871 //     [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
2872 //     [self interpretKeyEvents] ->
2873 //     [RenderWidgetHostViewMac insertText] (browser) ->
2874 //     |Async IPC| ->
2875 //     Editor::insertText() (renderer)
2877 // (*1) we need to wait until this call finishes since WebHTMLView uses the
2878 // result of EventHandler::keyEvent().
2879 // (*2) we need to wait until this call finishes since WebEditorClient uses
2880 // the result of [WebHTMLView _interceptEditingKeyEvent].
2882 // This needs many sync IPC messages sent between a browser and a renderer for
2883 // each key event, which would probably result in key-typing jank.
2884 // To avoid this problem, this implementation processes key events (and input
2885 // method events) totally in a browser process and sends asynchronous input
2886 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
2887 // renderer process.
2889 // [RenderWidgetHostViewMac keyEvent] (browser) ->
2890 //     |Async IPC (RawKeyDown)| ->
2891 //     [self interpretKeyEvents] ->
2892 //     [RenderWidgetHostViewMac insertText] (browser) ->
2893 //     |Async IPC (Char)| ->
2894 //     Editor::insertText() (renderer)
2896 // Since this implementation doesn't have to wait any IPC calls, this doesn't
2897 // make any key-typing jank. --hbono 7/23/09
2899 extern "C" {
2900 extern NSString *NSTextInputReplacementRangeAttributeName;
2903 - (NSArray *)validAttributesForMarkedText {
2904   // This code is just copied from WebKit except renaming variables.
2905   if (!validAttributesForMarkedText_) {
2906     validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
2907         NSUnderlineStyleAttributeName,
2908         NSUnderlineColorAttributeName,
2909         NSMarkedClauseSegmentAttributeName,
2910         NSTextInputReplacementRangeAttributeName,
2911         nil]);
2912   }
2913   return validAttributesForMarkedText_.get();
2916 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
2917   DCHECK([self window]);
2918   // |thePoint| is in screen coordinates, but needs to be converted to WebKit
2919   // coordinates (upper left origin). Scroll offsets will be taken care of in
2920   // the renderer.
2921   thePoint = [[self window] convertScreenToBase:thePoint];
2922   thePoint = [self convertPoint:thePoint fromView:nil];
2923   thePoint.y = NSHeight([self frame]) - thePoint.y;
2925   NSUInteger index =
2926       TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
2927           renderWidgetHostView_->render_widget_host_,
2928           gfx::Point(thePoint.x, thePoint.y));
2929   return index;
2932 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
2933                              actualRange:(NSRangePointer)actualRange {
2934   NSRect rect;
2935   if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
2936           theRange,
2937           &rect,
2938           actualRange)) {
2939     rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
2940         renderWidgetHostView_->render_widget_host_, theRange);
2942     // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
2943     if (actualRange)
2944       *actualRange = theRange;
2945   }
2947   // The returned rectangle is in WebKit coordinates (upper left origin), so
2948   // flip the coordinate system.
2949   NSRect viewFrame = [self frame];
2950   rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
2951   return rect;
2954 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
2955                          actualRange:(NSRangePointer)actualRange {
2956   NSRect rect = [self firstViewRectForCharacterRange:theRange
2957                                          actualRange:actualRange];
2959   // Convert into screen coordinates for return.
2960   rect = [self convertRect:rect toView:nil];
2961   rect.origin = [[self window] convertBaseToScreen:rect.origin];
2962   return rect;
2965 - (NSRange)markedRange {
2966   // An input method calls this method to check if an application really has
2967   // a text being composed when hasMarkedText call returns true.
2968   // Returns the range saved in the setMarkedText method so the input method
2969   // calls the setMarkedText method and we can update the composition node
2970   // there. (When this method returns an empty range, the input method doesn't
2971   // call the setMarkedText method.)
2972   return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
2975 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
2976     actualRange:(NSRangePointer)actualRange {
2977   // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
2978   if (actualRange)
2979     *actualRange = range;
2980   NSAttributedString* str =
2981       TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
2982           renderWidgetHostView_->render_widget_host_, range);
2983   return str;
2986 - (NSInteger)conversationIdentifier {
2987   return reinterpret_cast<NSInteger>(self);
2990 // Each RenderWidgetHostViewCocoa has its own input context, but we return
2991 // nil when the caret is in non-editable content or password box to avoid
2992 // making input methods do their work.
2993 - (NSTextInputContext *)inputContext {
2994   if (focusedPluginIdentifier_ != -1)
2995     return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
2997   switch(renderWidgetHostView_->text_input_type_) {
2998     case ui::TEXT_INPUT_TYPE_NONE:
2999     case ui::TEXT_INPUT_TYPE_PASSWORD:
3000       return nil;
3001     default:
3002       return [super inputContext];
3003   }
3006 - (BOOL)hasMarkedText {
3007   // An input method calls this function to figure out whether or not an
3008   // application is really composing a text. If it is composing, it calls
3009   // the markedRange method, and maybe calls the setMarkedText method.
3010   // It seems an input method usually calls this function when it is about to
3011   // cancel an ongoing composition. If an application has a non-empty marked
3012   // range, it calls the setMarkedText method to delete the range.
3013   return hasMarkedText_;
3016 - (void)unmarkText {
3017   // Delete the composition node of the renderer and finish an ongoing
3018   // composition.
3019   // It seems an input method calls the setMarkedText method and set an empty
3020   // text when it cancels an ongoing composition, i.e. I have never seen an
3021   // input method calls this method.
3022   hasMarkedText_ = NO;
3023   markedText_.clear();
3024   markedTextSelectedRange_ = NSMakeRange(NSNotFound, 0);
3025   underlines_.clear();
3027   // If we are handling a key down event, then ConfirmComposition() will be
3028   // called in keyEvent: method.
3029   if (!handlingKeyDown_) {
3030     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3031         base::string16(), gfx::Range::InvalidRange(), false);
3032   } else {
3033     unmarkTextCalled_ = YES;
3034   }
3037 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3038                               replacementRange:(NSRange)replacementRange {
3039   // An input method updates the composition string.
3040   // We send the given text and range to the renderer so it can update the
3041   // composition node of WebKit.
3042   // TODO(suzhe): It's hard for us to support replacementRange without accessing
3043   // the full web content.
3044   BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3045   NSString* im_text = isAttributedString ? [string string] : string;
3046   int length = [im_text length];
3048   // |markedRange_| will get set on a callback from ImeSetComposition().
3049   markedTextSelectedRange_ = newSelRange;
3050   markedText_ = base::SysNSStringToUTF16(im_text);
3051   hasMarkedText_ = (length > 0);
3053   underlines_.clear();
3054   if (isAttributedString) {
3055     ExtractUnderlines(string, &underlines_);
3056   } else {
3057     // Use a thin black underline by default.
3058     underlines_.push_back(blink::WebCompositionUnderline(
3059         0, length, SK_ColorBLACK, false, SK_ColorTRANSPARENT));
3060   }
3062   // If we are handling a key down event, then SetComposition() will be
3063   // called in keyEvent: method.
3064   // Input methods of Mac use setMarkedText calls with an empty text to cancel
3065   // an ongoing composition. So, we should check whether or not the given text
3066   // is empty to update the input method state. (Our input method backend can
3067   // automatically cancels an ongoing composition when we send an empty text.
3068   // So, it is OK to send an empty text to the renderer.)
3069   if (!handlingKeyDown_) {
3070     renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3071         markedText_, underlines_,
3072         newSelRange.location, NSMaxRange(newSelRange));
3073   }
3076 - (void)doCommandBySelector:(SEL)selector {
3077   // An input method calls this function to dispatch an editing command to be
3078   // handled by this view.
3079   if (selector == @selector(noop:))
3080     return;
3082   std::string command(
3083       [RenderWidgetHostViewMacEditCommandHelper::
3084           CommandNameForSelector(selector) UTF8String]);
3086   // If this method is called when handling a key down event, then we need to
3087   // handle the command in the key event handler. Otherwise we can just handle
3088   // it here.
3089   if (handlingKeyDown_) {
3090     hasEditCommands_ = YES;
3091     // We ignore commands that insert characters, because this was causing
3092     // strange behavior (e.g. tab always inserted a tab rather than moving to
3093     // the next field on the page).
3094     if (!base::StartsWith(command, "insert",
3095                           base::CompareCase::INSENSITIVE_ASCII))
3096       editCommands_.push_back(EditCommand(command, ""));
3097   } else {
3098     RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3099     rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
3100                                               command, ""));
3101   }
3104 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
3105   // An input method has characters to be inserted.
3106   // Same as Linux, Mac calls this method not only:
3107   // * when an input method finishs composing text, but also;
3108   // * when we type an ASCII character (without using input methods).
3109   // When we aren't using input methods, we should send the given character as
3110   // a Char event so it is dispatched to an onkeypress() event handler of
3111   // JavaScript.
3112   // On the other hand, when we are using input methods, we should send the
3113   // given characters as an input method event and prevent the characters from
3114   // being dispatched to onkeypress() event handlers.
3115   // Text inserting might be initiated by other source instead of keyboard
3116   // events, such as the Characters dialog. In this case the text should be
3117   // sent as an input method event as well.
3118   // TODO(suzhe): It's hard for us to support replacementRange without accessing
3119   // the full web content.
3120   BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3121   NSString* im_text = isAttributedString ? [string string] : string;
3122   if (handlingKeyDown_) {
3123     textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
3124   } else {
3125     gfx::Range replacement_range(replacementRange);
3126     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3127         base::SysNSStringToUTF16(im_text), replacement_range, false);
3128   }
3130   // Inserting text will delete all marked text automatically.
3131   hasMarkedText_ = NO;
3134 - (void)insertText:(id)string {
3135   [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
3138 - (void)viewDidMoveToWindow {
3139   if ([self window]) {
3140     [self updateScreenProperties];
3141   } else {
3142     // If the RenderWidgetHostViewCocoa is being removed from its window, tear
3143     // down its browser compositor resources, if needed.
3144     renderWidgetHostView_->DestroySuspendedBrowserCompositorViewIfNeeded();
3145   }
3147   if (canBeKeyView_) {
3148     NSWindow* newWindow = [self window];
3149     // Pointer comparison only, since we don't know if lastWindow_ is still
3150     // valid.
3151     if (newWindow) {
3152       // If we move into a new window, refresh the frame information. We
3153       // don't need to do it if it was the same window as it used to be in,
3154       // since that case is covered by WasShown(). We only want to do this for
3155       // real browser views, not popups.
3156       if (newWindow != lastWindow_) {
3157         lastWindow_ = newWindow;
3158         renderWidgetHostView_->WindowFrameChanged();
3159       }
3160     }
3161   }
3163   // If we switch windows (or are removed from the view hierarchy), cancel any
3164   // open mouse-downs.
3165   if (hasOpenMouseDown_) {
3166     WebMouseEvent event;
3167     event.type = WebInputEvent::MouseUp;
3168     event.button = WebMouseEvent::ButtonLeft;
3169     renderWidgetHostView_->ForwardMouseEvent(event);
3171     hasOpenMouseDown_ = NO;
3172   }
3175 - (void)undo:(id)sender {
3176   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3177   if (web_contents)
3178     web_contents->Undo();
3181 - (void)redo:(id)sender {
3182   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3183   if (web_contents)
3184     web_contents->Redo();
3187 - (void)cut:(id)sender {
3188   content::RenderWidgetHostDelegate* render_widget_host_delegate =
3189       renderWidgetHostView_->render_widget_host_->delegate();
3190   if (render_widget_host_delegate)
3191     render_widget_host_delegate->Cut();
3194 - (void)copy:(id)sender {
3195   content::RenderWidgetHostDelegate* render_widget_host_delegate =
3196       renderWidgetHostView_->render_widget_host_->delegate();
3197   if (render_widget_host_delegate)
3198     render_widget_host_delegate->Copy();
3201 - (void)copyToFindPboard:(id)sender {
3202   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3203   if (web_contents)
3204     web_contents->CopyToFindPboard();
3207 - (void)paste:(id)sender {
3208   content::RenderWidgetHostDelegate* render_widget_host_delegate =
3209       renderWidgetHostView_->render_widget_host_->delegate();
3210   if (render_widget_host_delegate)
3211     render_widget_host_delegate->Paste();
3214 - (void)pasteAndMatchStyle:(id)sender {
3215   WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3216   if (web_contents)
3217     web_contents->PasteAndMatchStyle();
3220 - (void)selectAll:(id)sender {
3221   // editCommand_helper_ adds implementations for most NSResponder methods
3222   // dynamically. But the renderer side only sends selection results back to
3223   // the browser if they were triggered by a keyboard event or went through
3224   // one of the Select methods on RWH. Since selectAll: is called from the
3225   // menu handler, neither is true.
3226   // Explicitly call SelectAll() here to make sure the renderer returns
3227   // selection results.
3228   content::RenderWidgetHostDelegate* render_widget_host_delegate =
3229       renderWidgetHostView_->render_widget_host_->delegate();
3230   if (render_widget_host_delegate)
3231     render_widget_host_delegate->SelectAll();
3234 - (void)startSpeaking:(id)sender {
3235   GetRenderWidgetHostViewToUse(renderWidgetHostView_.get())->SpeakSelection();
3238 - (void)stopSpeaking:(id)sender {
3239   GetRenderWidgetHostViewToUse(renderWidgetHostView_.get())->StopSpeaking();
3242 - (void)cancelComposition {
3243   if (!hasMarkedText_)
3244     return;
3246   // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
3247   // doesn't call any NSTextInput functions, such as setMarkedText or
3248   // insertText. So, we need to send an IPC message to a renderer so it can
3249   // delete the composition node.
3250   // TODO(erikchen): NSInputManager is deprecated since OSX 10.6. Switch to
3251   // NSTextInputContext. http://www.crbug.com/479010.
3252 #pragma clang diagnostic push
3253 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
3254   NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3255   [currentInputManager markedTextAbandoned:self];
3256 #pragma clang diagnostic pop
3258   hasMarkedText_ = NO;
3259   // Should not call [self unmarkText] here, because it'll send unnecessary
3260   // cancel composition IPC message to the renderer.
3263 - (void)confirmComposition {
3264   if (!hasMarkedText_)
3265     return;
3267   if (renderWidgetHostView_->render_widget_host_)
3268     renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3269         base::string16(), gfx::Range::InvalidRange(), false);
3271   [self cancelComposition];
3274 - (void)setPluginImeActive:(BOOL)active {
3275   if (active == pluginImeActive_)
3276     return;
3278   pluginImeActive_ = active;
3279   if (!active) {
3280     [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
3281     renderWidgetHostView_->PluginImeCompositionCompleted(
3282         base::string16(), focusedPluginIdentifier_);
3283   }
3286 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
3287   if (focused)
3288     focusedPluginIdentifier_ = pluginId;
3289   else if (focusedPluginIdentifier_ == pluginId)
3290     focusedPluginIdentifier_ = -1;
3292   // Whenever plugin focus changes, plugin IME resets.
3293   [self setPluginImeActive:NO];
3296 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
3297   if (!pluginImeActive_)
3298     return false;
3300   ComplexTextInputPanel* inputPanel =
3301       [ComplexTextInputPanel sharedComplexTextInputPanel];
3302   NSString* composited_string = nil;
3303   BOOL handled = [inputPanel interpretKeyEvent:event
3304                                         string:&composited_string];
3305   if (composited_string) {
3306     renderWidgetHostView_->PluginImeCompositionCompleted(
3307         base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
3308     pluginImeActive_ = NO;
3309   }
3310   return handled;
3313 - (void)checkForPluginImeCancellation {
3314   if (pluginImeActive_ &&
3315       ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
3316     renderWidgetHostView_->PluginImeCompositionCompleted(
3317         base::string16(), focusedPluginIdentifier_);
3318     pluginImeActive_ = NO;
3319   }
3322 // Overriding a NSResponder method to support application services.
3324 - (id)validRequestorForSendType:(NSString*)sendType
3325                      returnType:(NSString*)returnType {
3326   id requestor = nil;
3327   BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
3328   BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
3329   BOOL hasText = !renderWidgetHostView_->selected_text().empty();
3330   BOOL takesText =
3331       renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
3333   if (sendTypeIsString && hasText && !returnType) {
3334     requestor = self;
3335   } else if (!sendType && returnTypeIsString && takesText) {
3336     requestor = self;
3337   } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
3338     requestor = self;
3339   } else {
3340     requestor = [super validRequestorForSendType:sendType
3341                                       returnType:returnType];
3342   }
3343   return requestor;
3346 - (void)viewWillStartLiveResize {
3347   [super viewWillStartLiveResize];
3348   RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3349   if (widget)
3350     widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
3353 - (void)viewDidEndLiveResize {
3354   [super viewDidEndLiveResize];
3355   RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3356   if (widget)
3357     widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
3360 - (void)updateCursor:(NSCursor*)cursor {
3361   if (currentCursor_ == cursor)
3362     return;
3364   currentCursor_.reset([cursor retain]);
3365   [[self window] invalidateCursorRectsForView:self];
3368 - (void)popupWindowWillClose:(NSNotification *)notification {
3369   renderWidgetHostView_->KillSelf();
3372 @end
3375 // Supporting application services
3377 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
3379 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
3380                              types:(NSArray*)types {
3381   const std::string& str = renderWidgetHostView_->selected_text();
3382   if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
3384   base::scoped_nsobject<NSString> text(
3385       [[NSString alloc] initWithUTF8String:str.c_str()]);
3386   NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
3387   [pboard declareTypes:toDeclare owner:nil];
3388   return [pboard setString:text forType:NSStringPboardType];
3391 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
3392   NSString *string = [pboard stringForType:NSStringPboardType];
3393   if (!string) return NO;
3395   // If the user is currently using an IME, confirm the IME input,
3396   // and then insert the text from the service, the same as TextEdit and Safari.
3397   [self confirmComposition];
3398   [self insertText:string];
3399   return YES;
3402 - (BOOL)isOpaque {
3403   return opaque_;
3406 // "-webkit-app-region: drag | no-drag" is implemented on Mac by excluding
3407 // regions that are not draggable. (See ControlRegionView in
3408 // native_app_window_cocoa.mm). This requires the render host view to be
3409 // draggable by default.
3410 - (BOOL)mouseDownCanMoveWindow {
3411   return YES;
3414 @end