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