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