1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
7 #import <objc/runtime.h>
8 #include <QuartzCore/QuartzCore.h>
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/command_line.h"
14 #include "base/debug/crash_logging.h"
15 #include "base/debug/trace_event.h"
16 #include "base/logging.h"
17 #include "base/mac/mac_util.h"
18 #include "base/mac/scoped_cftyperef.h"
19 #import "base/mac/scoped_nsobject.h"
20 #include "base/mac/sdk_forward_declarations.h"
21 #include "base/message_loop/message_loop.h"
22 #include "base/metrics/histogram.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/strings/sys_string_conversions.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/sys_info.h"
28 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
29 #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
30 #include "content/browser/renderer_host/backing_store_mac.h"
31 #include "content/browser/renderer_host/backing_store_manager.h"
32 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
33 #include "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
34 #include "content/browser/renderer_host/compositing_iosurface_mac.h"
35 #include "content/browser/renderer_host/render_view_host_impl.h"
36 #import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
37 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
38 #import "content/browser/renderer_host/text_input_client_mac.h"
39 #include "content/common/accessibility_messages.h"
40 #include "content/common/edit_command.h"
41 #include "content/common/gpu/gpu_messages.h"
42 #include "content/common/input_messages.h"
43 #include "content/common/view_messages.h"
44 #include "content/common/webplugin_geometry.h"
45 #include "content/port/browser/render_widget_host_view_frame_subscriber.h"
46 #include "content/public/browser/browser_thread.h"
47 #include "content/public/browser/native_web_keyboard_event.h"
48 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
49 #include "content/public/browser/user_metrics.h"
50 #include "content/public/common/content_switches.h"
51 #include "skia/ext/platform_canvas.h"
52 #include "third_party/WebKit/public/platform/WebScreenInfo.h"
53 #include "third_party/WebKit/public/web/WebInputEvent.h"
54 #include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
55 #import "third_party/mozilla/ComplexTextInputPanel.h"
56 #include "ui/base/cocoa/animation_utils.h"
57 #import "ui/base/cocoa/fullscreen_window_manager.h"
58 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
59 #include "ui/events/keycodes/keyboard_codes.h"
60 #include "ui/base/layout.h"
61 #include "ui/gfx/display.h"
62 #include "ui/gfx/point.h"
63 #include "ui/gfx/rect_conversions.h"
64 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
65 #include "ui/gfx/screen.h"
66 #include "ui/gfx/size_conversions.h"
67 #include "ui/gl/io_surface_support_mac.h"
69 using content::BackingStoreMac;
70 using content::BrowserAccessibility;
71 using content::BrowserAccessibilityManager;
72 using content::EditCommand;
73 using content::NativeWebKeyboardEvent;
74 using content::RenderViewHostImpl;
75 using content::RenderWidgetHostImpl;
76 using content::RenderWidgetHostViewMac;
77 using content::RenderWidgetHostViewMacEditCommandHelper;
78 using content::TextInputClientMac;
79 using blink::WebInputEvent;
80 using blink::WebInputEventFactory;
81 using blink::WebMouseEvent;
82 using blink::WebMouseWheelEvent;
84 enum CoreAnimationStatus {
85 CORE_ANIMATION_DISABLED,
86 CORE_ANIMATION_ENABLED_LAZY,
87 CORE_ANIMATION_ENABLED_ALWAYS,
90 static CoreAnimationStatus GetCoreAnimationStatus() {
91 // TODO(sail) Remove this.
92 if (!CommandLine::ForCurrentProcess()->HasSwitch(
93 switches::kUseCoreAnimation)) {
94 return CORE_ANIMATION_DISABLED;
96 if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
97 switches::kUseCoreAnimation) == "lazy") {
98 return CORE_ANIMATION_ENABLED_LAZY;
100 if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
101 switches::kUseCoreAnimation) == "disabled") {
102 return CORE_ANIMATION_DISABLED;
104 return CORE_ANIMATION_ENABLED_ALWAYS;
107 // These are not documented, so use only after checking -respondsToSelector:.
108 @interface NSApplication (UndocumentedSpeechMethods)
109 - (void)speakString:(NSString*)string;
110 - (void)stopSpeaking:(id)sender;
114 // Declare things that are part of the 10.7 SDK.
115 #if !defined(MAC_OS_X_VERSION_10_7) || \
116 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
117 @interface NSView (NSOpenGLSurfaceResolutionLionAPI)
118 - (void)setWantsBestResolutionOpenGLSurface:(BOOL)flag;
121 static NSString* const NSWindowDidChangeBackingPropertiesNotification =
122 @"NSWindowDidChangeBackingPropertiesNotification";
123 static NSString* const NSBackingPropertyOldScaleFactorKey =
124 @"NSBackingPropertyOldScaleFactorKey";
125 // Note: Apple's example code (linked from the comment above
126 // -windowDidChangeBackingProperties:) uses
127 // @"NSWindowBackingPropertiesChangeOldBackingScaleFactorKey", but that always
128 // returns an old scale of 0. @"NSBackingPropertyOldScaleFactorKey" seems to
129 // work in practice, and it's what's used in Apple's WebKit port
130 // (WebKit/mac/WebView/WebView.mm).
134 // This method will return YES for OS X versions 10.7.3 and later, and NO
136 // Used to prevent a crash when building with the 10.7 SDK and accessing the
137 // notification below. See: http://crbug.com/260595.
138 static BOOL SupportsBackingPropertiesChangedNotification() {
139 // windowDidChangeBackingProperties: method has been added to the
140 // NSWindowDelegate protocol in 10.7.3, at the same time as the
141 // NSWindowDidChangeBackingPropertiesNotification notification was added.
142 // If the protocol contains this method description, the notification should
143 // be supported as well.
144 Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
145 struct objc_method_description methodDescription =
146 protocol_getMethodDescription(
147 windowDelegateProtocol,
148 @selector(windowDidChangeBackingProperties:),
152 // If the protocol does not contain the method, the returned method
153 // description is {NULL, NULL}
154 return methodDescription.name != NULL || methodDescription.types != NULL;
157 static float ScaleFactor(NSView* view) {
158 return ui::GetImageScale(ui::GetScaleFactorForNativeView(view));
162 @interface RenderWidgetHostViewCocoa ()
163 @property(nonatomic, assign) NSRange selectedRange;
164 @property(nonatomic, assign) NSRange markedRange;
165 @property(nonatomic, assign)
166 NSObject<RenderWidgetHostViewMacDelegate>* delegate;
168 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
169 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
170 - (void)gotUnhandledWheelEvent;
171 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right;
172 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar;
173 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
174 - (void)windowDidChangeBackingProperties:(NSNotification*)notification;
175 - (void)windowChangedGlobalFrame:(NSNotification*)notification;
176 - (void)drawBackingStore:(BackingStoreMac*)backingStore
177 dirtyRect:(CGRect)dirtyRect
178 inContext:(CGContextRef)context;
179 - (void)updateSoftwareLayerScaleFactor;
180 - (void)checkForPluginImeCancellation;
181 - (void)updateTabBackingStoreScaleFactor;
184 // A window subclass that allows the fullscreen window to become main and gain
185 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
186 // handled by the browser.
187 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
190 @implementation PepperFlashFullscreenWindow
192 - (BOOL)canBecomeKeyWindow {
196 - (BOOL)canBecomeMainWindow {
202 @interface RenderWidgetPopupWindow : NSWindow {
203 // The event tap that allows monitoring of all events, to properly close with
204 // a click outside the bounds of the window.
209 @implementation RenderWidgetPopupWindow
211 - (id)initWithContentRect:(NSRect)contentRect
212 styleMask:(NSUInteger)windowStyle
213 backing:(NSBackingStoreType)bufferingType
214 defer:(BOOL)deferCreation {
215 if (self = [super initWithContentRect:contentRect
216 styleMask:windowStyle
217 backing:bufferingType
218 defer:deferCreation]) {
219 CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
221 [self setBackgroundColor:[NSColor clearColor]];
222 [self startObservingClicks];
228 [self stopObservingClicks];
232 // Gets called when the menubar is clicked.
233 // Needed because the local event monitor doesn't see the click on the menubar.
234 - (void)beganTracking:(NSNotification*)notification {
238 // Install the callback.
239 - (void)startObservingClicks {
240 clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
241 handler:^NSEvent* (NSEvent* event) {
242 if ([event window] == self)
244 NSEventType eventType = [event type];
245 if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
250 NSNotificationCenter* notificationCenter =
251 [NSNotificationCenter defaultCenter];
252 [notificationCenter addObserver:self
253 selector:@selector(beganTracking:)
254 name:NSMenuDidBeginTrackingNotification
255 object:[NSApp mainMenu]];
258 // Remove the callback.
259 - (void)stopObservingClicks {
263 [NSEvent removeMonitor:clickEventTap_];
264 clickEventTap_ = nil;
266 NSNotificationCenter* notificationCenter =
267 [NSNotificationCenter defaultCenter];
268 [notificationCenter removeObserver:self
269 name:NSMenuDidBeginTrackingNotification
270 object:[NSApp mainMenu]];
277 // Maximum number of characters we allow in a tooltip.
278 const size_t kMaxTooltipLength = 1024;
280 // TODO(suzhe): Upstream this function.
281 blink::WebColor WebColorFromNSColor(NSColor *color) {
283 [color getRed:&r green:&g blue:&b alpha:&a];
286 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
287 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
288 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
289 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
292 // Extract underline information from an attributed string. Mostly copied from
293 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
294 void ExtractUnderlines(
295 NSAttributedString* string,
296 std::vector<blink::WebCompositionUnderline>* underlines) {
297 int length = [[string string] length];
301 NSDictionary* attrs = [string attributesAtIndex:i
302 longestEffectiveRange:&range
303 inRange:NSMakeRange(i, length - i)];
304 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
305 blink::WebColor color = SK_ColorBLACK;
306 if (NSColor *colorAttr =
307 [attrs objectForKey:NSUnderlineColorAttributeName]) {
308 color = WebColorFromNSColor(
309 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
311 underlines->push_back(blink::WebCompositionUnderline(
312 range.location, NSMaxRange(range), color, [style intValue] > 1));
314 i = range.location + range.length;
318 // EnablePasswordInput() and DisablePasswordInput() are copied from
319 // enableSecureTextInput() and disableSecureTextInput() functions in
320 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
321 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
322 // here, because they are already called in webkit and they are system wide
324 void EnablePasswordInput() {
325 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
326 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
327 sizeof(CFArrayRef), &inputSources);
328 CFRelease(inputSources);
331 void DisablePasswordInput() {
332 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
335 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
336 // left of the primary screen (Carbon coordinates), and stuffs it into a
338 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
339 gfx::Rect new_rect(NSRectToCGRect(rect));
340 if ([[NSScreen screens] count] > 0) {
341 new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height -
342 new_rect.y() - new_rect.height());
347 // Returns the window that visually contains the given view. This is different
348 // from [view window] in the case of tab dragging, where the view's owning
349 // window is a floating panel attached to the actual browser window that the tab
350 // is visually part of.
351 NSWindow* ApparentWindowForView(NSView* view) {
352 // TODO(shess): In case of !window, the view has been removed from
353 // the view hierarchy because the tab isn't main. Could retrieve
354 // the information from the main tab for our window.
355 NSWindow* enclosing_window = [view window];
357 // See if this is a tab drag window. The width check is to distinguish that
358 // case from extension popup windows.
359 NSWindow* ancestor_window = [enclosing_window parentWindow];
360 if (ancestor_window && (NSWidth([enclosing_window frame]) ==
361 NSWidth([ancestor_window frame]))) {
362 enclosing_window = ancestor_window;
365 return enclosing_window;
368 blink::WebScreenInfo GetWebScreenInfo(NSView* view) {
369 gfx::Display display =
370 gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
372 NSScreen* screen = [NSScreen deepestScreen];
374 blink::WebScreenInfo results;
376 results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
377 results.depth = NSBitsPerPixelFromDepth([screen depth]);
378 results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
379 results.isMonochrome =
380 [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
381 results.rect = display.bounds();
382 results.availableRect = display.work_area();
390 ///////////////////////////////////////////////////////////////////////////////
391 // RenderWidgetHostView, public:
394 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
395 RenderWidgetHost* widget) {
396 return new RenderWidgetHostViewMac(widget);
400 void RenderWidgetHostViewPort::GetDefaultScreenInfo(
401 blink::WebScreenInfo* results) {
402 *results = GetWebScreenInfo(NULL);
405 ///////////////////////////////////////////////////////////////////////////////
406 // RenderWidgetHostViewMac, public:
408 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
409 : render_widget_host_(RenderWidgetHostImpl::From(widget)),
410 about_to_validate_and_paint_(false),
411 call_set_needs_display_in_rect_pending_(false),
412 last_frame_was_accelerated_(false),
413 text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
414 can_compose_inline_(true),
415 allow_overlapping_views_(false),
416 use_core_animation_(false),
419 fullscreen_parent_host_view_(NULL),
420 underlay_view_has_drawn_(false),
421 overlay_view_weak_factory_(this),
422 software_frame_weak_ptr_factory_(this) {
423 software_frame_manager_.reset(new SoftwareFrameManager(
424 software_frame_weak_ptr_factory_.GetWeakPtr()));
425 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
426 // goes away. Since we autorelease it, our caller must put
427 // |GetNativeView()| into the view hierarchy right after calling us.
428 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
429 initWithRenderWidgetHostViewMac:this] autorelease];
431 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_ALWAYS) {
432 EnableCoreAnimation();
435 render_widget_host_->SetView(this);
438 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
439 // This is being called from |cocoa_view_|'s destructor, so invalidate the
445 // Make sure that the layer doesn't reach into the now-invalid object.
446 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
447 software_layer_.reset();
449 // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
450 // RenderWidgetHost does we need to tell it not to hold a stale pointer to
452 if (render_widget_host_)
453 render_widget_host_->SetView(NULL);
456 void RenderWidgetHostViewMac::SetDelegate(
457 NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
458 [cocoa_view_ setDelegate:delegate];
461 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
462 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY) {
464 ScopedCAActionDisabler disabler;
465 EnableCoreAnimation();
470 if (allow_overlapping_views_ == overlapping)
472 allow_overlapping_views_ = overlapping;
473 [cocoa_view_ setNeedsDisplay:YES];
474 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
477 ///////////////////////////////////////////////////////////////////////////////
478 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
480 void RenderWidgetHostViewMac::EnableCoreAnimation() {
481 if (use_core_animation_)
484 use_core_animation_ = true;
486 // Un-bind the GL context from this view because the CoreAnimation path will
487 // not use explicit setView and clearDrawable calls.
488 ClearBoundContextDrawable();
490 software_layer_.reset([[CALayer alloc] init]);
491 if (!software_layer_)
492 LOG(ERROR) << "Failed to create CALayer for software rendering";
493 [software_layer_ setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
494 [software_layer_ setDelegate:cocoa_view_];
495 [software_layer_ setContentsGravity:kCAGravityTopLeft];
496 [software_layer_ setFrame:NSRectToCGRect([cocoa_view_ bounds])];
497 [software_layer_ setNeedsDisplay];
498 [cocoa_view_ updateSoftwareLayerScaleFactor];
500 [cocoa_view_ setLayer:software_layer_];
501 [cocoa_view_ setWantsLayer:YES];
503 if (compositing_iosurface_) {
504 if (!CreateCompositedIOSurfaceLayer()) {
505 LOG(ERROR) << "Failed to create CALayer for existing IOSurface";
506 GotAcceleratedCompositingError();
512 bool RenderWidgetHostViewMac::CreateCompositedIOSurface() {
513 if (compositing_iosurface_context_ && compositing_iosurface_)
516 ScopedCAActionDisabler disabler;
518 // Create the GL context and shaders.
519 if (!compositing_iosurface_context_) {
520 compositing_iosurface_context_ =
521 CompositingIOSurfaceContext::Get(window_number());
522 if (!compositing_iosurface_context_) {
523 LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
527 // Create the IOSurface texture.
528 if (!compositing_iosurface_) {
529 compositing_iosurface_.reset(CompositingIOSurfaceMac::Create());
530 if (!compositing_iosurface_) {
531 LOG(ERROR) << "Failed to create CompositingIOSurface";
539 bool RenderWidgetHostViewMac::CreateCompositedIOSurfaceLayer() {
540 CHECK(compositing_iosurface_context_ && compositing_iosurface_);
541 if (compositing_iosurface_layer_ || !use_core_animation_)
544 ScopedCAActionDisabler disabler;
546 // Create the GL CoreAnimation layer.
547 if (!compositing_iosurface_layer_) {
548 compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
549 initWithRenderWidgetHostViewMac:this]);
550 if (!compositing_iosurface_layer_) {
551 LOG(ERROR) << "Failed to create CALayer for IOSurface";
554 [software_layer_ addSublayer:compositing_iosurface_layer_];
557 // Creating the CompositingIOSurfaceLayer may attempt to draw in setLayer,
558 // which, if it fails, will promptly tear down everything that was just
559 // created. If that happened, return failure.
560 return compositing_iosurface_context_ &&
561 compositing_iosurface_ &&
562 (compositing_iosurface_layer_ || !use_core_animation_);
565 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer(
566 DestroyContextBehavior destroy_context_behavior) {
567 ScopedCAActionDisabler disabler;
569 compositing_iosurface_.reset();
570 if (compositing_iosurface_layer_) {
571 [software_layer_ setNeedsDisplay];
572 [compositing_iosurface_layer_ removeFromSuperlayer];
573 [compositing_iosurface_layer_ disableCompositing];
574 compositing_iosurface_layer_.reset();
576 switch (destroy_context_behavior) {
577 case kLeaveContextBoundToView:
579 case kDestroyContext:
580 ClearBoundContextDrawable();
581 compositing_iosurface_context_ = NULL;
589 void RenderWidgetHostViewMac::ClearBoundContextDrawable() {
590 if (compositing_iosurface_context_ &&
592 [[compositing_iosurface_context_->nsgl_context() view]
593 isEqual:cocoa_view_]) {
594 [compositing_iosurface_context_->nsgl_context() clearDrawable];
598 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
600 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
601 IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
602 IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
603 IPC_MESSAGE_UNHANDLED(handled = false)
604 IPC_END_MESSAGE_MAP()
608 void RenderWidgetHostViewMac::InitAsChild(
609 gfx::NativeView parent_view) {
612 void RenderWidgetHostViewMac::InitAsPopup(
613 RenderWidgetHostView* parent_host_view,
614 const gfx::Rect& pos) {
615 bool activatable = popup_type_ == blink::WebPopupTypeNone;
616 [cocoa_view_ setCloseOnDeactivate:YES];
617 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
619 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
620 if ([[NSScreen screens] count] > 0) {
621 origin_global.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height -
622 pos.height() - origin_global.y;
625 popup_window_.reset([[RenderWidgetPopupWindow alloc]
626 initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
627 pos.width(), pos.height())
628 styleMask:NSBorderlessWindowMask
629 backing:NSBackingStoreBuffered
631 [popup_window_ setLevel:NSPopUpMenuWindowLevel];
632 [popup_window_ setReleasedWhenClosed:NO];
633 [popup_window_ makeKeyAndOrderFront:nil];
634 [[popup_window_ contentView] addSubview:cocoa_view_];
635 [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
636 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
637 [[NSNotificationCenter defaultCenter]
638 addObserver:cocoa_view_
639 selector:@selector(popupWindowWillClose:)
640 name:NSWindowWillCloseNotification
641 object:popup_window_];
644 // This function creates the fullscreen window and hides the dock and menubar if
645 // necessary. Note, this codepath is only used for pepper flash when
646 // pp::FlashFullScreen::SetFullscreen() is called. If
647 // pp::FullScreen::SetFullscreen() is called then the entire browser window
648 // will enter fullscreen instead.
649 void RenderWidgetHostViewMac::InitAsFullscreen(
650 RenderWidgetHostView* reference_host_view) {
651 fullscreen_parent_host_view_ =
652 static_cast<RenderWidgetHostViewMac*>(reference_host_view);
653 NSWindow* parent_window = nil;
654 if (reference_host_view)
655 parent_window = [reference_host_view->GetNativeView() window];
656 NSScreen* screen = [parent_window screen];
658 screen = [NSScreen mainScreen];
660 pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
661 initWithContentRect:[screen frame]
662 styleMask:NSBorderlessWindowMask
663 backing:NSBackingStoreBuffered
665 [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
666 [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
667 [cocoa_view_ setCanBeKeyView:YES];
668 [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
669 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
670 // If the pepper fullscreen window isn't opaque then there are performance
671 // issues when it's on the discrete GPU and the Chrome window is being drawn
672 // to. http://crbug.com/171911
673 [pepper_fullscreen_window_ setOpaque:YES];
675 // Note that this forms a reference cycle between the fullscreen window and
676 // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
677 // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
678 // This cycle is normally broken when -keyEvent: receives an <esc> key, which
679 // explicitly calls Shutdown on the render_widget_host_, which calls
680 // Destroy() on RWHVMac, which drops the reference to
681 // pepper_fullscreen_window_.
682 [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
684 // Note that this keeps another reference to pepper_fullscreen_window_.
685 fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
686 initWithWindow:pepper_fullscreen_window_.get()
687 desiredScreen:screen]);
688 [fullscreen_window_manager_ enterFullscreenMode];
689 [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
692 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
693 // See comment in InitAsFullscreen(): There is a reference cycle between
694 // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
695 // Tests that test pepper fullscreen mode without sending an <esc> event
696 // need to call this method to break the reference cycle.
697 [fullscreen_window_manager_ exitFullscreenMode];
698 fullscreen_window_manager_.reset();
699 [pepper_fullscreen_window_ close];
700 pepper_fullscreen_window_.reset();
703 int RenderWidgetHostViewMac::window_number() const {
704 NSWindow* window = [cocoa_view_ window];
707 return [window windowNumber];
710 float RenderWidgetHostViewMac::scale_factor() const {
711 return ScaleFactor(cocoa_view_);
714 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
715 return render_widget_host_;
718 void RenderWidgetHostViewMac::WasShown() {
719 if (!render_widget_host_->is_hidden())
722 if (web_contents_switch_paint_time_.is_null())
723 web_contents_switch_paint_time_ = base::TimeTicks::Now();
724 render_widget_host_->WasShown();
725 software_frame_manager_->SetVisibility(true);
727 // We're messing with the window, so do this to ensure no flashes.
728 if (!use_core_animation_)
729 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
731 [compositing_iosurface_layer_ setNeedsDisplay];
734 void RenderWidgetHostViewMac::WasHidden() {
735 if (render_widget_host_->is_hidden())
738 // If we have a renderer, then inform it that we are being hidden so it can
739 // reduce its resource utilization.
740 render_widget_host_->WasHidden();
741 software_frame_manager_->SetVisibility(false);
743 // There can be a transparent flash as this view is removed and the next is
744 // added, because of OSX windowing races between displaying the contents of
745 // the NSView and its corresponding OpenGL context.
746 // disableScreenUpdatesUntilFlush prevents the transparent flash by avoiding
747 // screen updates until the next tab draws.
748 if (!use_core_animation_)
749 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
751 web_contents_switch_paint_time_ = base::TimeTicks();
754 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
755 gfx::Rect rect = GetViewBounds();
760 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
761 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
762 // TODO(thakis): fix, http://crbug.com/73362
763 if (render_widget_host_->is_hidden())
766 // During the initial creation of the RenderWidgetHostView in
767 // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
768 // an empty size. In the Windows code flow, it is not ignored because
769 // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
770 // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
771 // flags to keep things sized properly. On the other hand, if the size is not
772 // empty then this is a valid request for a pop-up.
773 if (rect.size().IsEmpty())
776 // Ignore the position of |rect| for non-popup rwhvs. This is because
777 // background tabs do not have a window, but the window is required for the
778 // coordinate conversions. Popups are always for a visible tab.
780 // The position of |rect| is screen coordinate system and we have to
781 // consider Cocoa coordinate system is upside-down and also multi-screen.
782 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
783 NSSize size = NSMakeSize(rect.width(), rect.height());
784 size = [cocoa_view_ convertSize:size toView:nil];
785 if ([[NSScreen screens] count] > 0) {
787 static_cast<NSScreen*>([[NSScreen screens] objectAtIndex:0]);
789 NSHeight([screen frame]) - size.height - origin_global.y;
791 [popup_window_ setFrame:NSMakeRect(origin_global.x, origin_global.y,
792 size.width, size.height)
795 DCHECK([[cocoa_view_ superview] isKindOfClass:[BaseView class]]);
796 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
797 gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
798 rect2.set_width(rect.width());
799 rect2.set_height(rect.height());
800 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
804 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
808 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
809 return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
812 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
814 return static_cast<gfx::NativeViewAccessible>(NULL);
817 void RenderWidgetHostViewMac::MovePluginWindows(
818 const gfx::Vector2d& scroll_offset,
819 const std::vector<WebPluginGeometry>& moves) {
820 // Must be overridden, but unused on this platform. Core Animation
821 // plugins are drawn by the GPU process (through the compositor),
822 // and Core Graphics plugins are drawn by the renderer process.
823 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
826 void RenderWidgetHostViewMac::Focus() {
827 [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
830 void RenderWidgetHostViewMac::Blur() {
832 [[cocoa_view_ window] makeFirstResponder:nil];
835 bool RenderWidgetHostViewMac::HasFocus() const {
836 return [[cocoa_view_ window] firstResponder] == cocoa_view_;
839 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
840 return !!render_widget_host_->GetBackingStore(false) ||
841 software_frame_manager_->HasCurrentFrame() ||
842 (compositing_iosurface_ && compositing_iosurface_->HasIOSurface());
845 void RenderWidgetHostViewMac::Show() {
846 [cocoa_view_ setHidden:NO];
851 void RenderWidgetHostViewMac::Hide() {
852 // We're messing with the window, so do this to ensure no flashes.
853 if (!use_core_animation_)
854 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
856 [cocoa_view_ setHidden:YES];
861 bool RenderWidgetHostViewMac::IsShowing() {
862 return ![cocoa_view_ isHidden];
865 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
866 NSRect bounds = [cocoa_view_ bounds];
867 // TODO(shess): In case of !window, the view has been removed from
868 // the view hierarchy because the tab isn't main. Could retrieve
869 // the information from the main tab for our window.
870 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
871 if (!enclosing_window)
872 return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
874 bounds = [cocoa_view_ convertRect:bounds toView:nil];
875 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
876 return FlipNSRectToRectScreen(bounds);
879 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
880 WebCursor web_cursor = cursor;
881 [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
884 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
885 is_loading_ = is_loading;
886 // If we ever decide to show the waiting cursor while the page is loading
887 // like Chrome does on Windows, call |UpdateCursor()| here.
890 void RenderWidgetHostViewMac::TextInputTypeChanged(
891 ui::TextInputType type,
892 ui::TextInputMode input_mode,
893 bool can_compose_inline) {
894 if (text_input_type_ != type
895 || can_compose_inline_ != can_compose_inline) {
896 text_input_type_ = type;
897 can_compose_inline_ = can_compose_inline;
899 SetTextInputActive(true);
901 // Let AppKit cache the new input context to make IMEs happy.
902 // See http://crbug.com/73039.
903 [NSApp updateWindows];
906 UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
912 void RenderWidgetHostViewMac::ImeCancelComposition() {
913 [cocoa_view_ cancelComposition];
916 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
917 const gfx::Range& range,
918 const std::vector<gfx::Rect>& character_bounds) {
919 // The RangeChanged message is only sent with valid values. The current
920 // caret position (start == end) will be sent if there is no IME range.
921 [cocoa_view_ setMarkedRange:range.ToNSRange()];
922 composition_range_ = range;
923 composition_bounds_ = character_bounds;
926 void RenderWidgetHostViewMac::DidUpdateBackingStore(
927 const gfx::Rect& scroll_rect,
928 const gfx::Vector2d& scroll_delta,
929 const std::vector<gfx::Rect>& copy_rects,
930 const std::vector<ui::LatencyInfo>& latency_info) {
933 for (size_t i = 0; i < latency_info.size(); i++)
934 software_latency_info_.push_back(latency_info[i]);
936 if (render_widget_host_->is_hidden())
939 std::vector<gfx::Rect> rects(copy_rects);
941 // Because the findbar might be open, we cannot use scrollRect:by: here. For
942 // now, simply mark all of scroll rect as dirty.
943 if (!scroll_rect.IsEmpty())
944 rects.push_back(scroll_rect);
946 for (size_t i = 0; i < rects.size(); ++i) {
947 NSRect ns_rect = [cocoa_view_ flipRectToNSRect:rects[i]];
949 if (about_to_validate_and_paint_) {
950 // As much as we'd like to use -setNeedsDisplayInRect: here, we can't.
951 // We're in the middle of executing a -drawRect:, and as soon as it
952 // returns Cocoa will clear its record of what needs display. We instead
953 // use |performSelector:| to call |setNeedsDisplayInRect:| after returning
954 // to the main loop, at which point |drawRect:| is no longer on the
956 DCHECK([NSThread isMainThread]);
957 if (!call_set_needs_display_in_rect_pending_) {
958 [cocoa_view_ performSelector:@selector(callSetNeedsDisplayInRect)
961 call_set_needs_display_in_rect_pending_ = true;
962 invalid_rect_ = ns_rect;
964 // The old invalid rect is probably invalid now, since the view has most
965 // likely been resized, but there's no harm in dirtying the union. In
966 // the limit, this becomes equivalent to dirtying the whole view.
967 invalid_rect_ = NSUnionRect(invalid_rect_, ns_rect);
970 [cocoa_view_ setNeedsDisplayInRect:ns_rect];
974 if (!about_to_validate_and_paint_)
975 [cocoa_view_ displayIfNeeded];
978 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
983 void RenderWidgetHostViewMac::Destroy() {
984 [[NSNotificationCenter defaultCenter]
985 removeObserver:cocoa_view_
986 name:NSWindowWillCloseNotification
987 object:popup_window_];
989 // We've been told to destroy.
990 [cocoa_view_ retain];
991 [cocoa_view_ removeFromSuperview];
992 [cocoa_view_ autorelease];
994 [popup_window_ close];
995 popup_window_.autorelease();
997 [fullscreen_window_manager_ exitFullscreenMode];
998 fullscreen_window_manager_.reset();
999 [pepper_fullscreen_window_ close];
1001 // This can be called as part of processing the window's responder
1002 // chain, for instance |-performKeyEquivalent:|. In that case the
1003 // object needs to survive until the stack unwinds.
1004 pepper_fullscreen_window_.autorelease();
1006 // We get this call just before |render_widget_host_| deletes
1007 // itself. But we are owned by |cocoa_view_|, which may be retained
1008 // by some other code. Examples are WebContentsViewMac's
1009 // |latent_focus_view_| and TabWindowController's
1010 // |cachedContentView_|.
1011 render_widget_host_ = NULL;
1014 // Called from the renderer to tell us what the tooltip text should be. It
1015 // calls us frequently so we need to cache the value to prevent doing a lot
1017 void RenderWidgetHostViewMac::SetTooltipText(
1018 const base::string16& tooltip_text) {
1019 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1020 tooltip_text_ = tooltip_text;
1022 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1023 // Windows; we're just trying to be polite. Don't persist the trimmed
1024 // string, as then the comparison above will always fail and we'll try to
1025 // set it again every single time the mouse moves.
1026 base::string16 display_text = tooltip_text_;
1027 if (tooltip_text_.length() > kMaxTooltipLength)
1028 display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1030 NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1031 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1035 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1036 return [NSApp respondsToSelector:@selector(speakString:)] &&
1037 [NSApp respondsToSelector:@selector(stopSpeaking:)];
1040 void RenderWidgetHostViewMac::SpeakSelection() {
1041 if ([NSApp respondsToSelector:@selector(speakString:)])
1042 [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
1045 bool RenderWidgetHostViewMac::IsSpeaking() const {
1046 return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1050 void RenderWidgetHostViewMac::StopSpeaking() {
1051 if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1052 [NSApp stopSpeaking:cocoa_view_];
1056 // RenderWidgetHostViewCocoa uses the stored selection text,
1057 // which implements NSServicesRequests protocol.
1059 void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text,
1061 const gfx::Range& range) {
1062 if (range.is_empty() || text.empty()) {
1063 selected_text_.clear();
1065 size_t pos = range.GetMin() - offset;
1066 size_t n = range.length();
1068 DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1069 if (pos >= text.length()) {
1070 DCHECK(false) << "The text can not cover range.";
1073 selected_text_ = base::UTF16ToUTF8(text.substr(pos, n));
1076 [cocoa_view_ setSelectedRange:range.ToNSRange()];
1077 // Updates markedRange when there is no marked text so that retrieving
1078 // markedRange immediately after calling setMarkdText: returns the current
1080 if (![cocoa_view_ hasMarkedText]) {
1081 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1084 RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1087 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1088 const ViewHostMsg_SelectionBounds_Params& params) {
1089 if (params.anchor_rect == params.focus_rect)
1090 caret_rect_ = params.anchor_rect;
1093 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1096 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1097 RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1099 // Create a fake mouse event to inform the render widget that the mouse
1101 NSWindow* window = [cocoa_view_ window];
1102 // TODO(asvitkine): If the location outside of the event stream doesn't
1103 // correspond to the current event (due to delayed event processing), then
1104 // this may result in a cursor flicker if there are later mouse move events
1105 // in the pipeline. Find a way to use the mouse location from the event that
1106 // dismissed the context menu.
1107 NSPoint location = [window mouseLocationOutsideOfEventStream];
1108 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1112 windowNumber:window_number()
1117 WebMouseEvent web_event =
1118 WebInputEventFactory::mouseEvent(event, cocoa_view_);
1120 web_event.type = WebInputEvent::MouseLeave;
1121 ForwardMouseEvent(web_event);
1124 bool RenderWidgetHostViewMac::IsPopup() const {
1125 return popup_type_ != blink::WebPopupTypeNone;
1128 BackingStore* RenderWidgetHostViewMac::AllocBackingStore(
1129 const gfx::Size& size) {
1130 float scale = ScaleFactor(cocoa_view_);
1131 return new BackingStoreMac(render_widget_host_, size, scale);
1134 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1135 const gfx::Rect& src_subrect,
1136 const gfx::Size& dst_size,
1137 const base::Callback<void(bool, const SkBitmap&)>& callback) {
1138 base::ScopedClosureRunner scoped_callback_runner(
1139 base::Bind(callback, false, SkBitmap()));
1140 float scale = ScaleFactor(cocoa_view_);
1141 gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1142 gfx::ScaleSize(dst_size, scale));
1143 if (compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) {
1144 ignore_result(scoped_callback_runner.Release());
1145 compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1148 } else if (software_frame_manager_->HasCurrentFrame()) {
1149 gfx::Rect src_pixel_rect = gfx::ToEnclosingRect(gfx::ScaleRect(
1151 software_frame_manager_->GetCurrentFrameDeviceScaleFactor()));
1152 SkBitmap source_bitmap;
1153 source_bitmap.setConfig(
1154 SkBitmap::kARGB_8888_Config,
1155 software_frame_manager_->GetCurrentFrameSizeInPixels().width(),
1156 software_frame_manager_->GetCurrentFrameSizeInPixels().height(),
1158 kOpaque_SkAlphaType);
1159 source_bitmap.setPixels(software_frame_manager_->GetCurrentFramePixels());
1161 SkBitmap target_bitmap;
1162 target_bitmap.setConfig(
1163 SkBitmap::kARGB_8888_Config,
1164 dst_pixel_size.width(),
1165 dst_pixel_size.height(),
1167 kOpaque_SkAlphaType);
1168 if (!target_bitmap.allocPixels())
1171 SkCanvas target_canvas(target_bitmap);
1172 SkRect src_pixel_skrect = SkRect::MakeXYWH(
1173 src_pixel_rect.x(), src_pixel_rect.y(),
1174 src_pixel_rect.width(), src_pixel_rect.height());
1175 target_canvas.drawBitmapRectToRect(
1178 SkRect::MakeXYWH(0, 0, dst_pixel_size.width(), dst_pixel_size.height()),
1180 SkCanvas::kNone_DrawBitmapRectFlag);
1182 ignore_result(scoped_callback_runner.Release());
1183 callback.Run(true, target_bitmap);
1187 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1188 const gfx::Rect& src_subrect,
1189 const scoped_refptr<media::VideoFrame>& target,
1190 const base::Callback<void(bool)>& callback) {
1191 base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1192 if (!render_widget_host_->is_accelerated_compositing_active() ||
1193 !compositing_iosurface_ ||
1194 !compositing_iosurface_->HasIOSurface())
1197 if (!target.get()) {
1202 if (target->format() != media::VideoFrame::YV12 &&
1203 target->format() != media::VideoFrame::I420) {
1208 if (src_subrect.IsEmpty())
1211 ignore_result(scoped_callback_runner.Release());
1212 compositing_iosurface_->CopyToVideoFrame(
1213 GetScaledOpenGLPixelRect(src_subrect),
1218 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1219 return (!render_widget_host_->GetBackingStore(false) &&
1220 !software_frame_manager_->HasCurrentFrame() &&
1221 render_widget_host_->is_accelerated_compositing_active() &&
1222 compositing_iosurface_ &&
1223 compositing_iosurface_->HasIOSurface());
1226 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1227 return !software_frame_manager_->HasCurrentFrame();
1230 void RenderWidgetHostViewMac::BeginFrameSubscription(
1231 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1232 frame_subscriber_ = subscriber.Pass();
1235 void RenderWidgetHostViewMac::EndFrameSubscription() {
1236 frame_subscriber_.reset();
1239 // Sets whether or not to accept first responder status.
1240 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1241 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1244 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1245 if (render_widget_host_)
1246 render_widget_host_->ForwardMouseEvent(event);
1248 if (event.type == WebInputEvent::MouseLeave) {
1249 [cocoa_view_ setToolTipAtMousePoint:nil];
1250 tooltip_text_.clear();
1254 void RenderWidgetHostViewMac::KillSelf() {
1255 if (!weak_factory_.HasWeakPtrs()) {
1256 [cocoa_view_ setHidden:YES];
1257 base::MessageLoop::current()->PostTask(FROM_HERE,
1258 base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1259 weak_factory_.GetWeakPtr()));
1263 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1264 const NativeWebKeyboardEvent& event) {
1265 // Check WebInputEvent type since multiple types of events can be sent into
1266 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1267 // necessary to avoid double processing.
1268 // Also check the native type, since NSFlagsChanged is considered a key event
1269 // for WebKit purposes, but isn't considered a key event by the OS.
1270 if (event.type == WebInputEvent::RawKeyDown &&
1271 [event.os_event type] == NSKeyDown)
1272 return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1276 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1277 const base::string16& text, int plugin_id) {
1278 if (render_widget_host_) {
1279 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1280 render_widget_host_->GetRoutingID(), text, plugin_id));
1284 void RenderWidgetHostViewMac::CompositorSwapBuffers(
1285 uint64 surface_handle,
1286 const gfx::Size& size,
1287 float surface_scale_factor,
1288 const ui::LatencyInfo& latency_info) {
1289 if (render_widget_host_->is_hidden())
1292 NSWindow* window = [cocoa_view_ window];
1293 if (window_number() <= 0) {
1294 // There is no window to present so capturing during present won't work.
1295 // We check if frame subscriber wants this frame and capture manually.
1296 if (compositing_iosurface_ && frame_subscriber_) {
1297 const base::TimeTicks present_time = base::TimeTicks::Now();
1298 scoped_refptr<media::VideoFrame> frame;
1299 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1300 if (frame_subscriber_->ShouldCaptureFrame(present_time,
1301 &frame, &callback)) {
1302 compositing_iosurface_->SetIOSurface(
1303 surface_handle, size, surface_scale_factor, latency_info);
1304 compositing_iosurface_->CopyToVideoFrame(
1305 gfx::Rect(size), frame,
1306 base::Bind(callback, present_time));
1311 // TODO(shess) If the view does not have a window, or the window
1312 // does not have backing, the IOSurface will log "invalid drawable"
1313 // in -setView:. It is not clear how this code is reached with such
1314 // a case, so record some info into breakpad (some subset of
1315 // browsers are likely to crash later for unrelated reasons).
1316 // http://crbug.com/148882
1317 const char* const kCrashKey = "rwhvm_window";
1319 base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1322 base::StringPrintf("window %s delegate %s controller %s",
1323 object_getClassName(window),
1324 object_getClassName([window delegate]),
1325 object_getClassName([window windowController]));
1326 base::debug::SetCrashKeyValue(kCrashKey, value);
1332 if (!CreateCompositedIOSurface()) {
1333 LOG(ERROR) << "Failed to create CompositingIOSurface";
1334 GotAcceleratedCompositingError();
1338 if (!compositing_iosurface_->SetIOSurface(
1339 surface_handle, size, surface_scale_factor, latency_info)) {
1340 LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1341 GotAcceleratedCompositingError();
1345 // Create the layer for the composited content only after the IOSurface has
1346 // been initialized.
1347 if (!CreateCompositedIOSurfaceLayer()) {
1348 LOG(ERROR) << "Failed to create CompositingIOSurface layer";
1349 GotAcceleratedCompositingError();
1353 GotAcceleratedFrame();
1355 gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1356 if (window_size.IsEmpty()) {
1357 // setNeedsDisplay will never display and we'll never ack if the window is
1358 // empty, so ack now and don't bother calling setNeedsDisplay below.
1362 // No need to draw the surface if we are inside a drawRect. It will be done
1364 if (!about_to_validate_and_paint_) {
1365 if (use_core_animation_) {
1366 DCHECK(compositing_iosurface_layer_);
1367 [compositing_iosurface_layer_ setNeedsDisplay];
1369 if (!DrawIOSurfaceWithoutCoreAnimation()) {
1370 [cocoa_view_ setNeedsDisplay:YES];
1371 GotAcceleratedCompositingError();
1378 bool RenderWidgetHostViewMac::DrawIOSurfaceWithoutCoreAnimation() {
1379 CHECK(!use_core_animation_);
1380 CHECK(compositing_iosurface_);
1382 GLint old_gl_surface_order = 0;
1383 GLint new_gl_surface_order = allow_overlapping_views_ ? -1 : 1;
1384 [compositing_iosurface_context_->nsgl_context()
1385 getValues:&old_gl_surface_order
1386 forParameter:NSOpenGLCPSurfaceOrder];
1387 if (old_gl_surface_order != new_gl_surface_order) {
1388 [compositing_iosurface_context_->nsgl_context()
1389 setValues:&new_gl_surface_order
1390 forParameter:NSOpenGLCPSurfaceOrder];
1393 // Instead of drawing, request that underlay view redraws.
1394 if (underlay_view_ &&
1395 underlay_view_->compositing_iosurface_ &&
1396 underlay_view_has_drawn_) {
1397 [underlay_view_->cocoa_view() setNeedsDisplay:YES];
1401 CGLError cgl_error = CGLSetCurrentContext(
1402 compositing_iosurface_context_->cgl_context());
1403 if (cgl_error != kCGLNoError) {
1404 LOG(ERROR) << "CGLSetCurrentContext error in DrawIOSurface: " << cgl_error;
1408 [compositing_iosurface_context_->nsgl_context() setView:cocoa_view_];
1409 bool has_overlay = overlay_view_ && overlay_view_->compositing_iosurface_;
1411 gfx::Rect view_rect(NSRectToCGRect([cocoa_view_ frame]));
1412 if (!compositing_iosurface_->DrawIOSurface(
1413 compositing_iosurface_context_,
1422 overlay_view_->underlay_view_has_drawn_ = true;
1423 gfx::Rect overlay_view_rect(
1424 NSRectToCGRect([overlay_view_->cocoa_view() frame]));
1425 overlay_view_rect.set_x(overlay_view_offset_.x());
1426 overlay_view_rect.set_y(view_rect.height() -
1427 overlay_view_rect.height() -
1428 overlay_view_offset_.y());
1429 return overlay_view_->compositing_iosurface_->DrawIOSurface(
1430 compositing_iosurface_context_,
1432 overlay_view_->scale_factor(),
1433 overlay_view_->frame_subscriber(),
1440 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1441 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1442 // The existing GL contexts may be in a bad state, so don't re-use any of the
1443 // existing ones anymore, rather, allocate new ones.
1444 CompositingIOSurfaceContext::MarkExistingContextsAsNotShareable();
1445 // Request that a new frame be generated.
1446 if (render_widget_host_)
1447 render_widget_host_->ScheduleComposite();
1448 // TODO(ccameron): It may be a good idea to request that the renderer recreate
1449 // its GL context as well, and fall back to software if this happens
1453 void RenderWidgetHostViewMac::SetOverlayView(
1454 RenderWidgetHostViewMac* overlay, const gfx::Point& offset) {
1456 overlay_view_->underlay_view_.reset();
1458 overlay_view_ = overlay->overlay_view_weak_factory_.GetWeakPtr();
1459 overlay_view_offset_ = offset;
1460 overlay_view_->underlay_view_ = overlay_view_weak_factory_.GetWeakPtr();
1461 overlay_view_->underlay_view_has_drawn_ = false;
1463 [cocoa_view_ setNeedsDisplay:YES];
1464 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1467 void RenderWidgetHostViewMac::RemoveOverlayView() {
1468 if (overlay_view_) {
1469 overlay_view_->underlay_view_.reset();
1470 overlay_view_.reset();
1472 [cocoa_view_ setNeedsDisplay:YES];
1473 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1476 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1477 const std::vector<gfx::Rect>& bounds,
1478 const gfx::Range& range,
1479 size_t* line_break_point) {
1480 DCHECK(line_break_point);
1481 if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1484 // We can't check line breaking completely from only rectangle array. Thus we
1485 // assume the line breaking as the next character's y offset is larger than
1486 // a threshold. Currently the threshold is determined as minimum y offset plus
1487 // 75% of maximum height.
1488 // TODO(nona): Check the threshold is reliable or not.
1489 // TODO(nona): Bidi support.
1490 const size_t loop_end_idx = std::min(bounds.size(), range.end());
1492 int min_y_offset = kint32max;
1493 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1494 max_height = std::max(max_height, bounds[idx].height());
1495 min_y_offset = std::min(min_y_offset, bounds[idx].y());
1497 int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1498 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1499 if (bounds[idx].y() > line_break_threshold) {
1500 *line_break_point = idx;
1507 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1508 const gfx::Range& range,
1509 gfx::Range* actual_range) {
1510 DCHECK(actual_range);
1511 DCHECK(!composition_bounds_.empty());
1512 DCHECK(range.start() <= composition_bounds_.size());
1513 DCHECK(range.end() <= composition_bounds_.size());
1515 if (range.is_empty()) {
1516 *actual_range = range;
1517 if (range.start() == composition_bounds_.size()) {
1518 return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1519 composition_bounds_[range.start() - 1].y(),
1521 composition_bounds_[range.start() - 1].height());
1523 return gfx::Rect(composition_bounds_[range.start()].x(),
1524 composition_bounds_[range.start()].y(),
1526 composition_bounds_[range.start()].height());
1531 if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1532 end_idx = range.end();
1534 *actual_range = gfx::Range(range.start(), end_idx);
1535 gfx::Rect rect = composition_bounds_[range.start()];
1536 for (size_t i = range.start() + 1; i < end_idx; ++i) {
1537 rect.Union(composition_bounds_[i]);
1542 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1543 const gfx::Range& request_range) {
1544 if (composition_range_.is_empty())
1545 return gfx::Range::InvalidRange();
1547 if (request_range.is_reversed())
1548 return gfx::Range::InvalidRange();
1550 if (request_range.start() < composition_range_.start() ||
1551 request_range.start() > composition_range_.end() ||
1552 request_range.end() > composition_range_.end()) {
1553 return gfx::Range::InvalidRange();
1557 request_range.start() - composition_range_.start(),
1558 request_range.end() - composition_range_.start());
1561 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1564 NSRange* actual_range) {
1566 // This exists to make IMEs more responsive, see http://crbug.com/115920
1567 TRACE_EVENT0("browser",
1568 "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1570 // If requested range is same as caret location, we can just return it.
1571 if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1573 *actual_range = range;
1574 *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1578 const gfx::Range request_range_in_composition =
1579 ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1580 if (request_range_in_composition == gfx::Range::InvalidRange())
1583 // If firstRectForCharacterRange in WebFrame is failed in renderer,
1584 // ImeCompositionRangeChanged will be sent with empty vector.
1585 if (composition_bounds_.empty())
1587 DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1589 gfx::Range ui_actual_range;
1590 *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1591 request_range_in_composition,
1592 &ui_actual_range).ToCGRect());
1594 *actual_range = gfx::Range(
1595 composition_range_.start() + ui_actual_range.start(),
1596 composition_range_.start() + ui_actual_range.end()).ToNSRange();
1601 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1602 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1604 TRACE_EVENT0("browser",
1605 "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1606 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1608 CompositorSwapBuffers(params.surface_handle,
1610 params.scale_factor,
1611 params.latency_info);
1613 AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
1614 ack_params.sync_point = 0;
1615 ack_params.renderer_id = compositing_iosurface_ ?
1616 compositing_iosurface_->GetRendererID() : 0;
1617 RenderWidgetHostImpl::AcknowledgeBufferPresent(params.route_id,
1620 render_widget_host_->AcknowledgeSwapBuffersToRenderer();
1623 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1624 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1626 TRACE_EVENT0("browser",
1627 "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1628 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1630 CompositorSwapBuffers(params.surface_handle,
1631 params.surface_size,
1632 params.surface_scale_factor,
1633 params.latency_info);
1635 AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
1636 ack_params.sync_point = 0;
1637 ack_params.renderer_id = compositing_iosurface_ ?
1638 compositing_iosurface_->GetRendererID() : 0;
1639 RenderWidgetHostImpl::AcknowledgeBufferPresent(params.route_id,
1642 render_widget_host_->AcknowledgeSwapBuffersToRenderer();
1645 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1646 if (compositing_iosurface_)
1647 compositing_iosurface_->UnrefIOSurface();
1650 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1651 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1654 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1655 const gfx::Size& desired_size) {
1656 if (last_frame_was_accelerated_) {
1657 return compositing_iosurface_ &&
1658 compositing_iosurface_->HasIOSurface() &&
1659 (desired_size.IsEmpty() ||
1660 compositing_iosurface_->dip_io_surface_size() == desired_size);
1662 return (software_frame_manager_->HasCurrentFrame() &&
1663 (desired_size.IsEmpty() ||
1664 software_frame_manager_->GetCurrentFrameSizeInDIP() ==
1670 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1671 uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1672 // Only software compositor frames are accepted.
1673 if (!frame->software_frame_data) {
1674 DLOG(ERROR) << "Received unexpected frame type.";
1676 UserMetricsAction("BadMessageTerminate_UnexpectedFrameType"));
1677 render_widget_host_->GetProcess()->ReceivedBadMessage();
1681 if (!software_frame_manager_->SwapToNewFrame(
1683 frame->software_frame_data.get(),
1684 frame->metadata.device_scale_factor,
1685 render_widget_host_->GetProcess()->GetHandle())) {
1686 render_widget_host_->GetProcess()->ReceivedBadMessage();
1690 cc::CompositorFrameAck ack;
1691 RenderWidgetHostImpl::SendSwapCompositorFrameAck(
1692 render_widget_host_->GetRoutingID(),
1693 software_frame_manager_->GetCurrentFrameOutputSurfaceId(),
1694 render_widget_host_->GetProcess()->GetID(),
1696 for (size_t i = 0; i < frame->metadata.latency_info.size(); i++) {
1697 software_latency_info_.push_back(frame->metadata.latency_info[i]);
1699 software_frame_manager_->SwapToNewFrameComplete(
1700 !render_widget_host_->is_hidden());
1703 [cocoa_view_ setNeedsDisplay:YES];
1706 void RenderWidgetHostViewMac::OnAcceleratedCompositingStateChange() {
1709 void RenderWidgetHostViewMac::AcceleratedSurfaceInitialized(int host_id,
1713 void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) {
1714 *results = GetWebScreenInfo(GetNativeView());
1717 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1718 // TODO(shess): In case of !window, the view has been removed from
1719 // the view hierarchy because the tab isn't main. Could retrieve
1720 // the information from the main tab for our window.
1721 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1722 if (!enclosing_window)
1725 NSRect bounds = [enclosing_window frame];
1726 return FlipNSRectToRectScreen(bounds);
1729 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1730 // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1731 // completely on Mac OS.
1732 return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
1735 void RenderWidgetHostViewMac::SetHasHorizontalScrollbar(
1736 bool has_horizontal_scrollbar) {
1737 [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
1740 void RenderWidgetHostViewMac::SetScrollOffsetPinning(
1741 bool is_pinned_to_left, bool is_pinned_to_right) {
1742 [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
1743 toRight:is_pinned_to_right];
1746 bool RenderWidgetHostViewMac::LockMouse() {
1750 mouse_locked_ = true;
1752 // Lock position of mouse cursor and hide it.
1753 CGAssociateMouseAndMouseCursorPosition(NO);
1756 // Clear the tooltip window.
1757 SetTooltipText(base::string16());
1762 void RenderWidgetHostViewMac::UnlockMouse() {
1765 mouse_locked_ = false;
1767 // Unlock position of mouse cursor and unhide it.
1768 CGAssociateMouseAndMouseCursorPosition(YES);
1771 if (render_widget_host_)
1772 render_widget_host_->LostMouseLock();
1775 void RenderWidgetHostViewMac::UnhandledWheelEvent(
1776 const blink::WebMouseWheelEvent& event) {
1777 // Only record a wheel event as unhandled if JavaScript handlers got a chance
1778 // to see it (no-op wheel events are ignored by the event dispatcher)
1779 if (event.deltaX || event.deltaY)
1780 [cocoa_view_ gotUnhandledWheelEvent];
1783 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
1784 if (render_widget_host_)
1785 return render_widget_host_->Send(message);
1790 void RenderWidgetHostViewMac::SoftwareFrameWasFreed(
1791 uint32 output_surface_id, unsigned frame_id) {
1792 if (!render_widget_host_)
1794 cc::CompositorFrameAck ack;
1795 ack.last_software_frame_id = frame_id;
1796 RenderWidgetHostImpl::SendReclaimCompositorResources(
1797 render_widget_host_->GetRoutingID(),
1799 render_widget_host_->GetProcess()->GetID(),
1803 void RenderWidgetHostViewMac::ReleaseReferencesToSoftwareFrame() {
1806 void RenderWidgetHostViewMac::ShutdownHost() {
1807 weak_factory_.InvalidateWeakPtrs();
1808 render_widget_host_->Shutdown();
1809 // Do not touch any members at this point, |this| has been deleted.
1812 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
1813 // Update the scale factor of the layer to match the scale factor of the
1815 [compositing_iosurface_layer_ updateScaleFactor];
1817 if (!last_frame_was_accelerated_) {
1818 last_frame_was_accelerated_ = true;
1820 if (!use_core_animation_) {
1821 // Need to wipe the software view with transparency to expose the GL
1822 // underlay. Invalidate the whole window to do that.
1823 [cocoa_view_ setNeedsDisplay:YES];
1826 // Delete software backingstore.
1827 BackingStoreManager::RemoveBackingStore(render_widget_host_);
1828 software_frame_manager_->DiscardCurrentFrame();
1832 void RenderWidgetHostViewMac::GotSoftwareFrame() {
1833 if (last_frame_was_accelerated_) {
1834 last_frame_was_accelerated_ = false;
1836 // If overlapping views are allowed, then don't unbind the context
1837 // from the view (that is, don't call clearDrawble -- just delete the
1838 // texture and IOSurface). Rather, let it sit behind the software frame
1839 // that will be put up in front. This will prevent transparent
1841 // http://crbug.com/154531
1842 // Also note that it is necessary that clearDrawable be called if
1843 // overlapping views are not allowed, e.g, for content shell.
1844 // http://crbug.com/178408
1845 // Disable screen updates so that the changes of flashes is minimized.
1846 // http://crbug.com/279472
1847 if (!use_core_animation_)
1848 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1849 if (allow_overlapping_views_)
1850 DestroyCompositedIOSurfaceAndLayer(kLeaveContextBoundToView);
1852 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1856 void RenderWidgetHostViewMac::SetActive(bool active) {
1857 if (render_widget_host_) {
1858 render_widget_host_->SetActive(active);
1861 render_widget_host_->Focus();
1863 render_widget_host_->Blur();
1867 SetTextInputActive(active);
1869 [cocoa_view_ setPluginImeActive:NO];
1874 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
1875 if (render_widget_host_) {
1876 render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
1877 render_widget_host_->GetRoutingID(), visible));
1881 void RenderWidgetHostViewMac::WindowFrameChanged() {
1882 if (render_widget_host_) {
1883 render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
1884 render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
1888 if (compositing_iosurface_) {
1889 scoped_refptr<CompositingIOSurfaceContext> new_context =
1890 CompositingIOSurfaceContext::Get(window_number());
1891 if (new_context && new_context != compositing_iosurface_context_) {
1892 // Un-bind the GL context from this view before binding the new GL
1893 // context. Having two GL contexts bound to a view will result in
1894 // crashes and corruption.
1895 // http://crbug.com/230883
1896 ClearBoundContextDrawable();
1897 compositing_iosurface_context_ = new_context;
1902 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
1903 RenderWidgetHostViewMacDictionaryHelper helper(this);
1904 helper.ShowDefinitionForSelection();
1907 void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) {
1908 RenderWidgetHostViewBase::SetBackground(background);
1909 if (render_widget_host_)
1910 render_widget_host_->Send(new ViewMsg_SetBackground(
1911 render_widget_host_->GetRoutingID(), background));
1914 void RenderWidgetHostViewMac::CreateBrowserAccessibilityManagerIfNeeded() {
1915 if (!GetBrowserAccessibilityManager()) {
1916 SetBrowserAccessibilityManager(
1917 new BrowserAccessibilityManagerMac(
1919 BrowserAccessibilityManagerMac::GetEmptyDocument(),
1924 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
1926 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1927 EnablePasswordInput();
1929 DisablePasswordInput();
1931 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1932 DisablePasswordInput();
1936 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
1938 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
1941 void RenderWidgetHostViewMac::OnStartPluginIme() {
1942 [cocoa_view_ setPluginImeActive:YES];
1945 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
1946 const gfx::Rect& rect) {
1947 gfx::Rect src_gl_subrect = rect;
1948 src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
1950 return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
1954 void RenderWidgetHostViewMac::SendSoftwareLatencyInfoToHost() {
1955 for (size_t i = 0; i < software_latency_info_.size(); i++) {
1956 software_latency_info_[i].AddLatencyNumber(
1957 ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
1958 render_widget_host_->FrameSwapped(software_latency_info_[i]);
1960 software_latency_info_.clear();
1963 } // namespace content
1965 // RenderWidgetHostViewCocoa ---------------------------------------------------
1967 @implementation RenderWidgetHostViewCocoa
1968 @synthesize selectedRange = selectedRange_;
1969 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
1970 @synthesize markedRange = markedRange_;
1971 @synthesize delegate = delegate_;
1973 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
1974 self = [super initWithFrame:NSZeroRect];
1976 self.acceptsTouchEvents = YES;
1977 editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
1978 editCommand_helper_->AddEditingSelectorsToClass([self class]);
1980 renderWidgetHostView_.reset(r);
1981 canBeKeyView_ = YES;
1982 focusedPluginIdentifier_ = -1;
1983 deviceScaleFactor_ = ScaleFactor(self);
1986 if ([self respondsToSelector:
1987 @selector(setWantsBestResolutionOpenGLSurface:)]) {
1988 [self setWantsBestResolutionOpenGLSurface:YES];
1990 handlingGlobalFrameDidChange_ = NO;
1991 [[NSNotificationCenter defaultCenter]
1993 selector:@selector(globalFrameDidChange:)
1994 name:NSViewGlobalFrameDidChangeNotification
2001 // Unbind the GL context from this view. If this is not done before super's
2002 // dealloc is called then the GL context will crash when it reaches into
2003 // the view in its destructor.
2004 // http://crbug.com/255608
2005 if (renderWidgetHostView_)
2006 renderWidgetHostView_->AcceleratedSurfaceRelease();
2008 if (delegate_ && [delegate_ respondsToSelector:@selector(viewGone:)])
2009 [delegate_ viewGone:self];
2010 [[NSNotificationCenter defaultCenter] removeObserver:self];
2015 - (void)resetCursorRects {
2016 if (currentCursor_) {
2017 [self addCursorRect:[self visibleRect] cursor:currentCursor_];
2018 [currentCursor_ setOnMouseEntered:YES];
2022 - (void)gotUnhandledWheelEvent {
2024 [delegate_ respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
2025 [delegate_ gotUnhandledWheelEvent];
2029 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
2030 if (delegate_ && [delegate_
2031 respondsToSelector:@selector(scrollOffsetPinnedToLeft:toRight:)]) {
2032 [delegate_ scrollOffsetPinnedToLeft:left toRight:right];
2036 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
2038 [delegate_ respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
2039 [delegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
2043 - (BOOL)respondsToSelector:(SEL)selector {
2044 // Trickiness: this doesn't mean "does this object's superclass respond to
2045 // this selector" but rather "does the -respondsToSelector impl from the
2046 // superclass say that this class responds to the selector".
2047 if ([super respondsToSelector:selector])
2051 return [delegate_ respondsToSelector:selector];
2056 - (id)forwardingTargetForSelector:(SEL)selector {
2057 if ([delegate_ respondsToSelector:selector])
2060 return [super forwardingTargetForSelector:selector];
2063 - (void)setCanBeKeyView:(BOOL)can {
2064 canBeKeyView_ = can;
2067 - (BOOL)acceptsMouseEventsWhenInactive {
2068 // Some types of windows (balloons, always-on-top panels) want to accept mouse
2069 // clicks w/o the first click being treated as 'activation'. Same applies to
2070 // mouse move events.
2071 return [[self window] level] > NSNormalWindowLevel;
2074 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
2075 return [self acceptsMouseEventsWhenInactive];
2078 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
2079 takesFocusOnlyOnMouseDown_ = b;
2082 - (void)setCloseOnDeactivate:(BOOL)b {
2083 closeOnDeactivate_ = b;
2086 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
2087 NSWindow* window = [self window];
2088 // If this is a background window, don't handle mouse movement events. This
2089 // is the expected behavior on the Mac as evidenced by other applications.
2090 if ([theEvent type] == NSMouseMoved &&
2091 ![self acceptsMouseEventsWhenInactive] &&
2092 ![window isKeyWindow]) {
2096 // Use hitTest to check whether the mouse is over a nonWebContentView - in
2097 // which case the mouse event should not be handled by the render host.
2098 const SEL nonWebContentViewSelector = @selector(nonWebContentView);
2099 NSView* contentView = [window contentView];
2100 NSView* view = [contentView hitTest:[theEvent locationInWindow]];
2101 // Traverse the superview hierarchy as the hitTest will return the frontmost
2102 // view, such as an NSTextView, while nonWebContentView may be specified by
2105 if ([view respondsToSelector:nonWebContentViewSelector] &&
2106 [view performSelector:nonWebContentViewSelector]) {
2107 // The cursor is over a nonWebContentView - ignore this mouse event.
2110 if ([view isKindOfClass:[self class]] && ![view isEqual:self] &&
2111 !hasOpenMouseDown_) {
2112 // The cursor is over an overlapping render widget. This check is done by
2113 // both views so the one that's returned by -hitTest: will end up
2114 // processing the event.
2115 // Note that while dragging, we only get events for the render view where
2116 // drag started, even if mouse is actually over another view or outside
2117 // the window. Cocoa does this for us. We should handle these events and
2118 // not ignore (since there is no other render view to handle them). Thus
2119 // the |!hasOpenMouseDown_| check above.
2122 view = [view superview];
2127 - (void)mouseEvent:(NSEvent*)theEvent {
2128 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
2129 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2130 BOOL handled = [delegate_ handleEvent:theEvent];
2135 if ([self shouldIgnoreMouseEvent:theEvent]) {
2136 // If this is the first such event, send a mouse exit to the host view.
2137 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
2138 WebMouseEvent exitEvent =
2139 WebInputEventFactory::mouseEvent(theEvent, self);
2140 exitEvent.type = WebInputEvent::MouseLeave;
2141 exitEvent.button = WebMouseEvent::ButtonNone;
2142 renderWidgetHostView_->ForwardMouseEvent(exitEvent);
2144 mouseEventWasIgnored_ = YES;
2148 if (mouseEventWasIgnored_) {
2149 // If this is the first mouse event after a previous event that was ignored
2150 // due to the hitTest, send a mouse enter event to the host view.
2151 if (renderWidgetHostView_->render_widget_host_) {
2152 WebMouseEvent enterEvent =
2153 WebInputEventFactory::mouseEvent(theEvent, self);
2154 enterEvent.type = WebInputEvent::MouseMove;
2155 enterEvent.button = WebMouseEvent::ButtonNone;
2156 renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2159 mouseEventWasIgnored_ = NO;
2161 // TODO(rohitrao): Probably need to handle other mouse down events here.
2162 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2163 if (renderWidgetHostView_->render_widget_host_)
2164 renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2166 // Manually take focus after the click but before forwarding it to the
2168 [[self window] makeFirstResponder:self];
2171 // Don't cancel child popups; killing them on a mouse click would prevent the
2172 // user from positioning the insertion point in the text field spawning the
2173 // popup. A click outside the text field would cause the text field to drop
2174 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2175 // the popup anyway, so we're OK.
2177 NSEventType type = [theEvent type];
2178 if (type == NSLeftMouseDown)
2179 hasOpenMouseDown_ = YES;
2180 else if (type == NSLeftMouseUp)
2181 hasOpenMouseDown_ = NO;
2183 // TODO(suzhe): We should send mouse events to the input method first if it
2184 // wants to handle them. But it won't work without implementing method
2185 // - (NSUInteger)characterIndexForPoint:.
2186 // See: http://code.google.com/p/chromium/issues/detail?id=47141
2187 // Instead of sending mouse events to the input method first, we now just
2188 // simply confirm all ongoing composition here.
2189 if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2190 type == NSOtherMouseDown) {
2191 [self confirmComposition];
2194 const WebMouseEvent event =
2195 WebInputEventFactory::mouseEvent(theEvent, self);
2196 renderWidgetHostView_->ForwardMouseEvent(event);
2199 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2200 // |performKeyEquivalent:| is sent to all views of a window, not only down the
2201 // responder chain (cf. "Handling Key Equivalents" in
2202 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2203 // ). We only want to handle key equivalents if we're first responder.
2204 if ([[self window] firstResponder] != self)
2207 // If we return |NO| from this function, cocoa will send the key event to
2208 // the menu and only if the menu does not process the event to |keyDown:|. We
2209 // want to send the event to a renderer _before_ sending it to the menu, so
2210 // we need to return |YES| for all events that might be swallowed by the menu.
2211 // We do not return |YES| for every keypress because we don't get |keyDown:|
2212 // events for keys that we handle this way.
2213 NSUInteger modifierFlags = [theEvent modifierFlags];
2214 if ((modifierFlags & NSCommandKeyMask) == 0) {
2215 // Make sure the menu does not contain key equivalents that don't
2217 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2221 // Command key combinations are sent via performKeyEquivalent rather than
2222 // keyDown:. We just forward this on and if WebCore doesn't want to handle
2223 // it, we let the WebContentsView figure out how to reinject it.
2224 [self keyEvent:theEvent wasKeyEquivalent:YES];
2228 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2229 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2230 // returned NO. If this function returns |YES|, Cocoa sends the event to
2231 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2232 // to us instead of doing key view loop control, ctrl-left/right get handled
2234 // (However, there are still some keys that Cocoa swallows, e.g. the key
2235 // equivalent that Cocoa uses for toggling the input language. In this case,
2236 // that's actually a good thing, though -- see http://crbug.com/26115 .)
2240 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2241 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2242 BOOL handled = [delegate_ handleEvent:theEvent];
2244 return kEventHandled;
2247 [self keyEvent:theEvent wasKeyEquivalent:NO];
2248 return kEventHandled;
2251 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2252 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2253 DCHECK([theEvent type] != NSKeyDown ||
2254 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2256 if ([theEvent type] == NSFlagsChanged) {
2257 // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2258 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2259 int keyCode = [theEvent keyCode];
2260 if (!keyCode || keyCode == 10 || keyCode == 63)
2264 // Don't cancel child popups; the key events are probably what's triggering
2265 // the popup in the first place.
2267 RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2270 NativeWebKeyboardEvent event(theEvent);
2272 // Force fullscreen windows to close on Escape so they won't keep the keyboard
2273 // grabbed or be stuck onscreen if the renderer is hanging.
2274 if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2275 event.windowsKeyCode == ui::VKEY_ESCAPE &&
2276 renderWidgetHostView_->pepper_fullscreen_window()) {
2277 RenderWidgetHostViewMac* parent =
2278 renderWidgetHostView_->fullscreen_parent_host_view();
2280 parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2281 widgetHost->Shutdown();
2285 // Suppress the escape key up event if necessary.
2286 if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2287 if (event.type == NativeWebKeyboardEvent::KeyUp)
2288 suppressNextEscapeKeyUp_ = NO;
2292 // We only handle key down events and just simply forward other events.
2293 if ([theEvent type] != NSKeyDown) {
2294 widgetHost->ForwardKeyboardEvent(event);
2296 // Possibly autohide the cursor.
2297 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2298 [NSCursor setHiddenUntilMouseMoves:YES];
2303 base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2305 // Records the current marked text state, so that we can know if the marked
2306 // text was deleted or not after handling the key down event.
2307 BOOL oldHasMarkedText = hasMarkedText_;
2309 // This method should not be called recursively.
2310 DCHECK(!handlingKeyDown_);
2312 // Tells insertText: and doCommandBySelector: that we are handling a key
2314 handlingKeyDown_ = YES;
2316 // These variables might be set when handling the keyboard event.
2317 // Clear them here so that we can know whether they have changed afterwards.
2318 textToBeInserted_.clear();
2319 markedText_.clear();
2320 underlines_.clear();
2321 unmarkTextCalled_ = NO;
2322 hasEditCommands_ = NO;
2323 editCommands_.clear();
2325 // Before doing anything with a key down, check to see if plugin IME has been
2326 // cancelled, since the plugin host needs to be informed of that before
2327 // receiving the keydown.
2328 if ([theEvent type] == NSKeyDown)
2329 [self checkForPluginImeCancellation];
2331 // Sends key down events to input method first, then we can decide what should
2332 // be done according to input method's feedback.
2333 // If a plugin is active, bypass this step since events are forwarded directly
2334 // to the plugin IME.
2335 if (focusedPluginIdentifier_ == -1)
2336 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2338 handlingKeyDown_ = NO;
2340 // Indicates if we should send the key event and corresponding editor commands
2341 // after processing the input method result.
2342 BOOL delayEventUntilAfterImeCompostion = NO;
2344 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2345 // while an input method is composing or inserting a text.
2346 // Gmail checks this code in its onkeydown handler to stop auto-completing
2347 // e-mail addresses while composing a CJK text.
2348 // If the text to be inserted has only one character, then we don't need this
2349 // trick, because we'll send the text as a key press event instead.
2350 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2351 NativeWebKeyboardEvent fakeEvent = event;
2352 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
2353 fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2354 fakeEvent.skip_in_browser = true;
2355 widgetHost->ForwardKeyboardEvent(fakeEvent);
2356 // If this key event was handled by the input method, but
2357 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2358 // enqueued edit commands, then in order to let webkit handle them
2359 // correctly, we need to send the real key event and corresponding edit
2360 // commands after processing the input method result.
2361 // We shouldn't do this if a new marked text was set by the input method,
2362 // otherwise the new marked text might be cancelled by webkit.
2363 if (hasEditCommands_ && !hasMarkedText_)
2364 delayEventUntilAfterImeCompostion = YES;
2366 if (!editCommands_.empty()) {
2367 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2368 widgetHost->GetRoutingID(), editCommands_));
2370 widgetHost->ForwardKeyboardEvent(event);
2373 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2374 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2375 // be set to NULL. So we check it here and return immediately if it's NULL.
2376 if (!renderWidgetHostView_->render_widget_host_)
2379 // Then send keypress and/or composition related events.
2380 // If there was a marked text or the text to be inserted is longer than 1
2381 // character, then we send the text by calling ConfirmComposition().
2382 // Otherwise, if the text to be inserted only contains 1 character, then we
2383 // can just send a keypress event which is fabricated by changing the type of
2384 // the keydown event, so that we can retain all necessary informations, such
2385 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2386 // prevent the browser from handling it again.
2387 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2388 // handle BMP characters here, as we can always insert non-BMP characters as
2390 BOOL textInserted = NO;
2391 if (textToBeInserted_.length() >
2392 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2393 widgetHost->ImeConfirmComposition(
2394 textToBeInserted_, gfx::Range::InvalidRange(), false);
2398 // Updates or cancels the composition. If some text has been inserted, then
2399 // we don't need to cancel the composition explicitly.
2400 if (hasMarkedText_ && markedText_.length()) {
2401 // Sends the updated marked text to the renderer so it can update the
2402 // composition node in WebKit.
2403 // When marked text is available, |selectedRange_| will be the range being
2404 // selected inside the marked text.
2405 widgetHost->ImeSetComposition(markedText_, underlines_,
2406 selectedRange_.location,
2407 NSMaxRange(selectedRange_));
2408 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2409 if (unmarkTextCalled_) {
2410 widgetHost->ImeConfirmComposition(
2411 base::string16(), gfx::Range::InvalidRange(), false);
2413 widgetHost->ImeCancelComposition();
2417 // If the key event was handled by the input method but it also generated some
2418 // edit commands, then we need to send the real key event and corresponding
2419 // edit commands here. This usually occurs when the input method wants to
2420 // finish current composition session but still wants the application to
2421 // handle the key event. See http://crbug.com/48161 for reference.
2422 if (delayEventUntilAfterImeCompostion) {
2423 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2424 // with windowsKeyCode == 0xE5 has already been sent to webkit.
2425 // So before sending the real key down event, we need to send a fake key up
2426 // event to balance it.
2427 NativeWebKeyboardEvent fakeEvent = event;
2428 fakeEvent.type = blink::WebInputEvent::KeyUp;
2429 fakeEvent.skip_in_browser = true;
2430 widgetHost->ForwardKeyboardEvent(fakeEvent);
2431 // Not checking |renderWidgetHostView_->render_widget_host_| here because
2432 // a key event with |skip_in_browser| == true won't be handled by browser,
2433 // thus it won't destroy the widget.
2435 if (!editCommands_.empty()) {
2436 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2437 widgetHost->GetRoutingID(), editCommands_));
2439 widgetHost->ForwardKeyboardEvent(event);
2441 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2442 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2443 // be set to NULL. So we check it here and return immediately if it's NULL.
2444 if (!renderWidgetHostView_->render_widget_host_)
2448 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2449 // Only send a corresponding key press event if there is no marked text.
2450 if (!hasMarkedText_) {
2451 if (!textInserted && textToBeInserted_.length() == 1) {
2452 // If a single character was inserted, then we just send it as a keypress
2454 event.type = blink::WebInputEvent::Char;
2455 event.text[0] = textToBeInserted_[0];
2457 event.skip_in_browser = true;
2458 widgetHost->ForwardKeyboardEvent(event);
2459 } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2460 [[theEvent characters] length] > 0 &&
2461 (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2462 (hasEditCommands_ && editCommands_.empty()))) {
2463 // We don't get insertText: calls if ctrl or cmd is down, or the key event
2464 // generates an insert command. So synthesize a keypress event for these
2465 // cases, unless the key event generated any other command.
2466 event.type = blink::WebInputEvent::Char;
2467 event.skip_in_browser = true;
2468 widgetHost->ForwardKeyboardEvent(event);
2472 // Possibly autohide the cursor.
2473 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2474 [NSCursor setHiddenUntilMouseMoves:YES];
2477 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2478 DCHECK(base::mac::IsOSLionOrLater());
2480 if ([event phase] != NSEventPhaseEnded &&
2481 [event phase] != NSEventPhaseCancelled) {
2485 if (renderWidgetHostView_->render_widget_host_) {
2486 const WebMouseWheelEvent& webEvent =
2487 WebInputEventFactory::mouseWheelEvent(event, self);
2488 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2491 if (endWheelMonitor_) {
2492 [NSEvent removeMonitor:endWheelMonitor_];
2493 endWheelMonitor_ = nil;
2497 - (void)beginGestureWithEvent:(NSEvent*)event {
2498 [delegate_ beginGestureWithEvent:event];
2500 - (void)endGestureWithEvent:(NSEvent*)event {
2501 [delegate_ endGestureWithEvent:event];
2503 - (void)touchesMovedWithEvent:(NSEvent*)event {
2504 [delegate_ touchesMovedWithEvent:event];
2506 - (void)touchesBeganWithEvent:(NSEvent*)event {
2507 [delegate_ touchesBeganWithEvent:event];
2509 - (void)touchesCancelledWithEvent:(NSEvent*)event {
2510 [delegate_ touchesCancelledWithEvent:event];
2512 - (void)touchesEndedWithEvent:(NSEvent*)event {
2513 [delegate_ touchesEndedWithEvent:event];
2516 // This method handles 2 different types of hardware events.
2517 // (Apple does not distinguish between them).
2518 // a. Scrolling the middle wheel of a mouse.
2519 // b. Swiping on the track pad.
2521 // This method is responsible for 2 types of behavior:
2522 // a. Scrolling the content of window.
2523 // b. Navigating forwards/backwards in history.
2525 // This is a brief description of the logic:
2526 // 1. If the content can be scrolled, scroll the content.
2527 // (This requires a roundtrip to blink to determine whether the content
2528 // can be scrolled.)
2529 // Once this logic is triggered, the navigate logic cannot be triggered
2530 // until the gesture finishes.
2531 // 2. If the user is making a horizontal swipe, start the navigate
2532 // forward/backwards UI.
2533 // Once this logic is triggered, the user can either cancel or complete
2534 // the gesture. If the user completes the gesture, all remaining touches
2535 // are swallowed, and not allowed to scroll the content. If the user
2536 // cancels the gesture, all remaining touches are forwarded to the content
2537 // scroll logic. The user cannot trigger the navigation logic again.
2538 - (void)scrollWheel:(NSEvent*)event {
2539 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2540 BOOL handled = [delegate_ handleEvent:event];
2545 // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
2546 // the event is received even when the mouse cursor is no longer over the view
2547 // when the scrolling ends (e.g. if the tab was switched). This is necessary
2548 // for ending rubber-banding in such cases.
2549 if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
2550 !endWheelMonitor_) {
2552 [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
2553 handler:^(NSEvent* blockEvent) {
2554 [self shortCircuitScrollWheelEvent:blockEvent];
2559 // This is responsible for content scrolling!
2560 if (renderWidgetHostView_->render_widget_host_) {
2561 const WebMouseWheelEvent& webEvent =
2562 WebInputEventFactory::mouseWheelEvent(event, self);
2563 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2567 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
2568 NSWindow* oldWindow = [self window];
2570 // We're messing with the window, so do this to ensure no flashes. This one
2571 // prevents a flash when the current tab is closed.
2572 if (!renderWidgetHostView_->use_core_animation_)
2573 [oldWindow disableScreenUpdatesUntilFlush];
2575 // If the new window for this view is using CoreAnimation then enable
2576 // CoreAnimation on this view.
2577 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY &&
2578 [[newWindow contentView] wantsLayer]) {
2579 renderWidgetHostView_->EnableCoreAnimation();
2582 NSNotificationCenter* notificationCenter =
2583 [NSNotificationCenter defaultCenter];
2585 // Backing property notifications crash on 10.6 when building with the 10.7
2586 // SDK, see http://crbug.com/260595.
2587 static BOOL supportsBackingPropertiesNotification =
2588 SupportsBackingPropertiesChangedNotification();
2591 if (supportsBackingPropertiesNotification) {
2594 name:NSWindowDidChangeBackingPropertiesNotification
2599 name:NSWindowDidMoveNotification
2603 name:NSWindowDidEndLiveResizeNotification
2607 if (supportsBackingPropertiesNotification) {
2610 selector:@selector(windowDidChangeBackingProperties:)
2611 name:NSWindowDidChangeBackingPropertiesNotification
2616 selector:@selector(windowChangedGlobalFrame:)
2617 name:NSWindowDidMoveNotification
2621 selector:@selector(windowChangedGlobalFrame:)
2622 name:NSWindowDidEndLiveResizeNotification
2627 - (void)updateTabBackingStoreScaleFactor {
2628 if (!renderWidgetHostView_->render_widget_host_)
2631 float scaleFactor = ScaleFactor(self);
2632 if (scaleFactor == deviceScaleFactor_)
2634 deviceScaleFactor_ = scaleFactor;
2636 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
2637 renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
2638 if (backingStore) // NULL in hardware path.
2639 backingStore->ScaleFactorChanged(scaleFactor);
2641 [self updateSoftwareLayerScaleFactor];
2642 renderWidgetHostView_->render_widget_host_->NotifyScreenInfoChanged();
2645 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
2646 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
2647 NSWindow* window = (NSWindow*)[notification object];
2649 CGFloat newBackingScaleFactor = [window backingScaleFactor];
2650 CGFloat oldBackingScaleFactor = [base::mac::ObjCCast<NSNumber>(
2651 [[notification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey])
2653 if (newBackingScaleFactor != oldBackingScaleFactor) {
2654 // Background tabs check if their scale factor changed when they are added
2657 // Allocating a CGLayerRef with the current scale factor immediately from
2658 // this handler doesn't work. Schedule the backing store update on the
2659 // next runloop cycle, then things are read for CGLayerRef allocations to
2661 [self performSelector:@selector(updateTabBackingStoreScaleFactor)
2667 - (void)globalFrameDidChange:(NSNotification*)notification {
2668 if (handlingGlobalFrameDidChange_)
2671 handlingGlobalFrameDidChange_ = YES;
2672 if (renderWidgetHostView_->compositing_iosurface_context_) {
2673 [renderWidgetHostView_->compositing_iosurface_context_->nsgl_context()
2676 handlingGlobalFrameDidChange_ = NO;
2679 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
2680 renderWidgetHostView_->UpdateScreenInfo(
2681 renderWidgetHostView_->GetNativeView());
2684 - (void)setFrameSize:(NSSize)newSize {
2685 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
2687 // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
2688 // -setFrame: isn't neccessary.
2689 [super setFrameSize:newSize];
2690 if (renderWidgetHostView_->render_widget_host_) {
2691 renderWidgetHostView_->render_widget_host_->SendScreenRects();
2692 renderWidgetHostView_->render_widget_host_->WasResized();
2695 // This call is necessary to make the window wait for a new frame at the new
2696 // size to be available before the resize completes. Calling only
2697 // setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay on
2698 // this is not sufficient.
2699 ScopedCAActionDisabler disabler;
2700 CGRect frame = NSRectToCGRect([renderWidgetHostView_->cocoa_view() bounds]);
2701 [renderWidgetHostView_->software_layer_ setFrame:frame];
2702 [renderWidgetHostView_->software_layer_ setNeedsDisplay];
2703 [renderWidgetHostView_->compositing_iosurface_layer_ setFrame:frame];
2704 [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
2707 - (void)callSetNeedsDisplayInRect {
2708 DCHECK([NSThread isMainThread]);
2709 DCHECK(renderWidgetHostView_->call_set_needs_display_in_rect_pending_);
2710 [self setNeedsDisplayInRect:renderWidgetHostView_->invalid_rect_];
2711 renderWidgetHostView_->call_set_needs_display_in_rect_pending_ = false;
2712 renderWidgetHostView_->invalid_rect_ = NSZeroRect;
2714 if (renderWidgetHostView_->compositing_iosurface_layer_)
2715 [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
2718 // Fills with white the parts of the area to the right and bottom for |rect|
2719 // that intersect |damagedRect|.
2720 - (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
2721 dirtyRect:(gfx::Rect)damagedRect
2722 inContext:(CGContextRef)context {
2723 if (damagedRect.right() > rect.right()) {
2724 int x = std::max(rect.right(), damagedRect.x());
2725 int y = std::min(rect.bottom(), damagedRect.bottom());
2726 int width = damagedRect.right() - x;
2727 int height = damagedRect.y() - y;
2729 // Extra fun to get around the fact that gfx::Rects can't have
2740 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
2741 CGContextSetFillColorWithColor(context,
2742 CGColorGetConstantColor(kCGColorWhite));
2743 CGContextFillRect(context, NSRectToCGRect(r));
2745 if (damagedRect.bottom() > rect.bottom()) {
2746 int x = damagedRect.x();
2747 int y = damagedRect.bottom();
2748 int width = damagedRect.right() - x;
2749 int height = std::max(rect.bottom(), damagedRect.y()) - y;
2751 // Extra fun to get around the fact that gfx::Rects can't have
2762 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
2763 CGContextSetFillColorWithColor(context,
2764 CGColorGetConstantColor(kCGColorWhite));
2765 CGContextFillRect(context, NSRectToCGRect(r));
2769 - (void)drawRect:(NSRect)dirtyRect {
2770 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::drawRect");
2771 CHECK(!renderWidgetHostView_->use_core_animation_);
2773 if (!renderWidgetHostView_->render_widget_host_) {
2774 // TODO(shess): Consider using something more noticable?
2775 [[NSColor whiteColor] set];
2776 NSRectFill(dirtyRect);
2780 DCHECK(!renderWidgetHostView_->about_to_validate_and_paint_);
2782 // GetBackingStore works for both software and accelerated frames. If a
2783 // SwapBuffers occurs while GetBackingStore is blocking, we will continue to
2784 // blit the IOSurface below.
2785 renderWidgetHostView_->about_to_validate_and_paint_ = true;
2786 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
2787 renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
2788 renderWidgetHostView_->about_to_validate_and_paint_ = false;
2790 const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
2792 if (renderWidgetHostView_->last_frame_was_accelerated_ &&
2793 renderWidgetHostView_->compositing_iosurface_) {
2794 if (renderWidgetHostView_->allow_overlapping_views_) {
2795 CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
2797 // If overlapping views need to be allowed, punch a hole in the window
2798 // to expose the GL underlay.
2799 TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
2800 "h", damagedRect.height());
2801 // NSRectFill is extremely slow (15ms for a window on a fast MacPro), so
2802 // this is only done when it's a real invalidation from window damage (not
2803 // when a BuffersSwapped was received). Note that even a 1x1 NSRectFill
2804 // can take many milliseconds sometimes (!) so this is skipped completely
2805 // for drawRects that are triggered by BuffersSwapped messages.
2806 [[NSColor clearColor] set];
2807 NSRectFill(dirtyRect);
2810 if (renderWidgetHostView_->DrawIOSurfaceWithoutCoreAnimation())
2813 // On error, fall back to software and fall through to the non-accelerated
2815 renderWidgetHostView_->GotAcceleratedCompositingError();
2818 CGContextRef context = static_cast<CGContextRef>(
2819 [[NSGraphicsContext currentContext] graphicsPort]);
2820 [self drawBackingStore:backingStore
2821 dirtyRect:NSRectToCGRect(dirtyRect)
2825 - (void)drawBackingStore:(BackingStoreMac*)backingStore
2826 dirtyRect:(CGRect)dirtyRect
2827 inContext:(CGContextRef)context {
2828 content::SoftwareFrameManager* software_frame_manager =
2829 renderWidgetHostView_->software_frame_manager_.get();
2830 // There should never be both a legacy software and software composited
2832 DCHECK(!backingStore || !software_frame_manager->HasCurrentFrame());
2834 if (backingStore || software_frame_manager->HasCurrentFrame()) {
2835 // Note: All coordinates are in view units, not pixels.
2836 gfx::Rect bitmapRect(
2837 software_frame_manager->HasCurrentFrame() ?
2838 software_frame_manager->GetCurrentFrameSizeInDIP() :
2839 backingStore->size());
2841 // Specify the proper y offset to ensure that the view is rooted to the
2842 // upper left corner. This can be negative, if the window was resized
2843 // smaller and the renderer hasn't yet repainted.
2844 int yOffset = NSHeight([self bounds]) - bitmapRect.height();
2846 NSRect nsDirtyRect = NSRectFromCGRect(dirtyRect);
2847 const gfx::Rect damagedRect([self flipNSRectToRect:nsDirtyRect]);
2849 gfx::Rect paintRect = gfx::IntersectRects(bitmapRect, damagedRect);
2850 if (!paintRect.IsEmpty()) {
2851 if (software_frame_manager->HasCurrentFrame()) {
2852 // If a software compositor framebuffer is present, draw using that.
2853 gfx::Size sizeInPixels =
2854 software_frame_manager->GetCurrentFrameSizeInPixels();
2855 base::ScopedCFTypeRef<CGDataProviderRef> dataProvider(
2856 CGDataProviderCreateWithData(
2858 software_frame_manager->GetCurrentFramePixels(),
2859 4 * sizeInPixels.width() * sizeInPixels.height(),
2861 base::ScopedCFTypeRef<CGImageRef> image(
2863 sizeInPixels.width(),
2864 sizeInPixels.height(),
2867 4 * sizeInPixels.width(),
2868 base::mac::GetSystemColorSpace(),
2869 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
2873 kCGRenderingIntentDefault));
2874 CGRect imageRect = bitmapRect.ToCGRect();
2875 imageRect.origin.y = yOffset;
2876 CGContextDrawImage(context, imageRect, image);
2877 } else if (backingStore->cg_layer()) {
2878 // If we have a CGLayer, draw that into the window
2879 // TODO: add clipping to dirtyRect if it improves drawing performance.
2880 CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset),
2881 backingStore->cg_layer());
2883 // If we haven't created a layer yet, draw the cached bitmap into
2884 // the window. The CGLayer will be created the next time the renderer
2886 base::ScopedCFTypeRef<CGImageRef> image(
2887 CGBitmapContextCreateImage(backingStore->cg_bitmap()));
2888 CGRect imageRect = bitmapRect.ToCGRect();
2889 imageRect.origin.y = yOffset;
2890 CGContextDrawImage(context, imageRect, image);
2894 renderWidgetHostView_->SendSoftwareLatencyInfoToHost();
2896 // Fill the remaining portion of the damagedRect with white
2897 [self fillBottomRightRemainderOfRect:bitmapRect
2898 dirtyRect:damagedRect
2901 if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
2902 base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
2903 renderWidgetHostView_->whiteout_start_time_;
2904 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
2906 // Reset the start time to 0 so that we start recording again the next
2907 // time the backing store is NULL...
2908 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
2910 if (!renderWidgetHostView_->web_contents_switch_paint_time_.is_null()) {
2911 base::TimeDelta web_contents_switch_paint_duration =
2912 base::TimeTicks::Now() -
2913 renderWidgetHostView_->web_contents_switch_paint_time_;
2914 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
2915 web_contents_switch_paint_duration);
2916 // Reset contents_switch_paint_time_ to 0 so future tab selections are
2918 renderWidgetHostView_->web_contents_switch_paint_time_ =
2922 CGContextSetFillColorWithColor(context,
2923 CGColorGetConstantColor(kCGColorWhite));
2924 CGContextFillRect(context, dirtyRect);
2925 if (renderWidgetHostView_->whiteout_start_time_.is_null())
2926 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
2930 - (BOOL)canBecomeKeyView {
2931 if (!renderWidgetHostView_->render_widget_host_)
2934 return canBeKeyView_;
2937 - (BOOL)acceptsFirstResponder {
2938 if (!renderWidgetHostView_->render_widget_host_)
2941 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
2944 - (BOOL)becomeFirstResponder {
2945 if (!renderWidgetHostView_->render_widget_host_)
2948 renderWidgetHostView_->render_widget_host_->Focus();
2949 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
2950 renderWidgetHostView_->SetTextInputActive(true);
2952 // Cancel any onging composition text which was left before we lost focus.
2953 // TODO(suzhe): We should do it in -resignFirstResponder: method, but
2954 // somehow that method won't be called when switching among different tabs.
2955 // See http://crbug.com/47209
2956 [self cancelComposition];
2958 NSNumber* direction = [NSNumber numberWithUnsignedInteger:
2959 [[self window] keyViewSelectionDirection]];
2960 NSDictionary* userInfo =
2961 [NSDictionary dictionaryWithObject:direction
2962 forKey:kSelectionDirection];
2963 [[NSNotificationCenter defaultCenter]
2964 postNotificationName:kViewDidBecomeFirstResponder
2971 - (BOOL)resignFirstResponder {
2972 renderWidgetHostView_->SetTextInputActive(false);
2973 if (!renderWidgetHostView_->render_widget_host_)
2976 if (closeOnDeactivate_)
2977 renderWidgetHostView_->KillSelf();
2979 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
2980 renderWidgetHostView_->render_widget_host_->Blur();
2982 // We should cancel any onging composition whenever RWH's Blur() method gets
2983 // called, because in this case, webkit will confirm the ongoing composition
2985 [self cancelComposition];
2990 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
2992 [delegate_ respondsToSelector:@selector(validateUserInterfaceItem:
2995 BOOL known = [delegate_ validateUserInterfaceItem:item
2996 isValidItem:&valid];
3001 SEL action = [item action];
3003 if (action == @selector(stopSpeaking:)) {
3004 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3005 renderWidgetHostView_->IsSpeaking();
3007 if (action == @selector(startSpeaking:)) {
3008 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3009 renderWidgetHostView_->SupportsSpeech();
3012 // For now, these actions are always enabled for render view,
3013 // this is sub-optimal.
3014 // TODO(suzhe): Plumb the "can*" methods up from WebCore.
3015 if (action == @selector(undo:) ||
3016 action == @selector(redo:) ||
3017 action == @selector(cut:) ||
3018 action == @selector(copy:) ||
3019 action == @selector(copyToFindPboard:) ||
3020 action == @selector(paste:) ||
3021 action == @selector(pasteAndMatchStyle:)) {
3022 return renderWidgetHostView_->render_widget_host_->IsRenderView();
3025 return editCommand_helper_->IsMenuItemEnabled(action, self);
3028 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
3029 return renderWidgetHostView_.get();
3032 // Determine whether we should autohide the cursor (i.e., hide it until mouse
3033 // move) for the given event. Customize here to be more selective about which
3034 // key presses to autohide on.
3035 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
3036 return ([event type] == NSKeyDown &&
3037 !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
3040 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
3041 index:(NSUInteger)index
3042 maxCount:(NSUInteger)maxCount {
3043 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3044 NSUInteger totalLength = [fullArray count];
3045 if (index >= totalLength)
3047 NSUInteger length = MIN(totalLength - index, maxCount);
3048 return [fullArray subarrayWithRange:NSMakeRange(index, length)];
3051 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
3052 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3053 return [fullArray count];
3056 - (id)accessibilityAttributeValue:(NSString *)attribute {
3057 BrowserAccessibilityManager* manager =
3058 renderWidgetHostView_->GetBrowserAccessibilityManager();
3060 // Contents specifies document view of RenderWidgetHostViewCocoa provided by
3061 // BrowserAccessibilityManager. Children includes all subviews in addition to
3062 // contents. Currently we do not have subviews besides the document view.
3063 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
3064 [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
3066 return [NSArray arrayWithObjects:manager->
3067 GetRoot()->ToBrowserAccessibilityCocoa(), nil];
3068 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
3069 return NSAccessibilityScrollAreaRole;
3071 id ret = [super accessibilityAttributeValue:attribute];
3075 - (NSArray*)accessibilityAttributeNames {
3076 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
3077 [ret addObject:NSAccessibilityContentsAttribute];
3078 [ret addObjectsFromArray:[super accessibilityAttributeNames]];
3082 - (id)accessibilityHitTest:(NSPoint)point {
3083 if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
3085 NSPoint pointInWindow = [[self window] convertScreenToBase:point];
3086 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
3087 localPoint.y = NSHeight([self bounds]) - localPoint.y;
3088 BrowserAccessibilityCocoa* root = renderWidgetHostView_->
3089 GetBrowserAccessibilityManager()->
3090 GetRoot()->ToBrowserAccessibilityCocoa();
3091 id obj = [root accessibilityHitTest:localPoint];
3095 - (BOOL)accessibilityIsIgnored {
3096 return !renderWidgetHostView_->GetBrowserAccessibilityManager();
3099 - (NSUInteger)accessibilityGetIndexOf:(id)child {
3100 BrowserAccessibilityManager* manager =
3101 renderWidgetHostView_->GetBrowserAccessibilityManager();
3102 // Only child is root.
3104 manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
3111 - (id)accessibilityFocusedUIElement {
3112 BrowserAccessibilityManager* manager =
3113 renderWidgetHostView_->GetBrowserAccessibilityManager();
3115 BrowserAccessibility* focused_item = manager->GetFocus(NULL);
3116 DCHECK(focused_item);
3118 BrowserAccessibilityCocoa* focused_item_cocoa =
3119 focused_item->ToBrowserAccessibilityCocoa();
3120 DCHECK(focused_item_cocoa);
3121 if (focused_item_cocoa)
3122 return focused_item_cocoa;
3125 return [super accessibilityFocusedUIElement];
3128 - (void)doDefaultAction:(int32)accessibilityObjectId {
3129 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3130 rwh->Send(new AccessibilityMsg_DoDefaultAction(
3131 rwh->GetRoutingID(), accessibilityObjectId));
3134 // VoiceOver uses this method to move the caret to the beginning of the next
3135 // word in a text field.
3136 - (void)accessibilitySetTextSelection:(int32)accId
3137 startOffset:(int32)startOffset
3138 endOffset:(int32)endOffset {
3139 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3140 rwh->AccessibilitySetTextSelection(accId, startOffset, endOffset);
3143 // Convert a web accessibility's location in web coordinates into a cocoa
3144 // screen coordinate.
3145 - (NSPoint)accessibilityPointInScreen:(NSPoint)origin
3147 origin.y = NSHeight([self bounds]) - origin.y;
3148 NSPoint originInWindow = [self convertPoint:origin toView:nil];
3149 NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow];
3150 originInScreen.y = originInScreen.y - size.height;
3151 return originInScreen;
3154 - (void)setAccessibilityFocus:(BOOL)focus
3155 accessibilityId:(int32)accessibilityObjectId {
3157 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3158 rwh->Send(new AccessibilityMsg_SetFocus(
3159 rwh->GetRoutingID(), accessibilityObjectId));
3161 // Immediately set the focused item even though we have not officially set
3162 // focus on it as VoiceOver expects to get the focused item after this
3164 BrowserAccessibilityManager* manager =
3165 renderWidgetHostView_->GetBrowserAccessibilityManager();
3166 manager->SetFocus(manager->GetFromRendererID(accessibilityObjectId), false);
3170 - (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
3171 // Performs a right click copying WebKit's
3172 // accessibilityPerformShowMenuAction.
3173 NSPoint origin = [accessibility origin];
3174 NSSize size = [[accessibility size] sizeValue];
3175 NSPoint location = [self accessibilityPointInScreen:origin size:size];
3176 location = [[self window] convertScreenToBase:location];
3177 location.x += size.width/2;
3178 location.y += size.height/2;
3180 NSEvent* fakeRightClick = [NSEvent
3181 mouseEventWithType:NSRightMouseDown
3185 windowNumber:[[self window] windowNumber]
3186 context:[NSGraphicsContext currentContext]
3191 [self mouseEvent:fakeRightClick];
3194 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
3195 // with minor modifications for code style and commenting.
3197 // The 'public' interface is -setToolTipAtMousePoint:. This differs from
3198 // -setToolTip: in that the updated tooltip takes effect immediately,
3199 // without the user's having to move the mouse out of and back into the view.
3201 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
3202 // the view, which in turn requires overriding some internal tracking-rect
3203 // methods (to keep track of its owner & userdata, which need to be filled out
3204 // in the fake events.) --snej 7/6/09
3208 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3209 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
3211 * Redistribution and use in source and binary forms, with or without
3212 * modification, are permitted provided that the following conditions
3215 * 1. Redistributions of source code must retain the above copyright
3216 * notice, this list of conditions and the following disclaimer.
3217 * 2. Redistributions in binary form must reproduce the above copyright
3218 * notice, this list of conditions and the following disclaimer in the
3219 * documentation and/or other materials provided with the distribution.
3220 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
3221 * its contributors may be used to endorse or promote products derived
3222 * from this software without specific prior written permission.
3224 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
3225 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3226 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3227 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
3228 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3229 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
3230 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3231 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3232 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3233 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3236 // Any non-zero value will do, but using something recognizable might help us
3238 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
3240 // Override of a public NSView method, replacing the inherited functionality.
3241 // See above for rationale.
3242 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
3244 userData:(void *)data
3245 assumeInside:(BOOL)assumeInside {
3246 DCHECK(trackingRectOwner_ == nil);
3247 trackingRectOwner_ = owner;
3248 trackingRectUserData_ = data;
3249 return kTrackingRectTag;
3252 // Override of (apparently) a private NSView method(!) See above for rationale.
3253 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
3255 userData:(void *)data
3256 assumeInside:(BOOL)assumeInside
3257 useTrackingNum:(int)tag {
3258 DCHECK(tag == 0 || tag == kTrackingRectTag);
3259 DCHECK(trackingRectOwner_ == nil);
3260 trackingRectOwner_ = owner;
3261 trackingRectUserData_ = data;
3262 return kTrackingRectTag;
3265 // Override of (apparently) a private NSView method(!) See above for rationale.
3266 - (void)_addTrackingRects:(NSRect *)rects
3268 userDataList:(void **)userDataList
3269 assumeInsideList:(BOOL *)assumeInsideList
3270 trackingNums:(NSTrackingRectTag *)trackingNums
3273 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3274 DCHECK(trackingRectOwner_ == nil);
3275 trackingRectOwner_ = owner;
3276 trackingRectUserData_ = userDataList[0];
3277 trackingNums[0] = kTrackingRectTag;
3280 // Override of a public NSView method, replacing the inherited functionality.
3281 // See above for rationale.
3282 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3286 if (tag == kTrackingRectTag) {
3287 trackingRectOwner_ = nil;
3291 if (tag == lastToolTipTag_) {
3292 [super removeTrackingRect:tag];
3293 lastToolTipTag_ = 0;
3297 // If any other tracking rect is being removed, we don't know how it was
3298 // created and it's possible there's a leak involved (see Radar 3500217).
3302 // Override of (apparently) a private NSView method(!)
3303 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3304 for (int i = 0; i < count; ++i) {
3308 DCHECK(tag == kTrackingRectTag);
3309 trackingRectOwner_ = nil;
3313 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3314 - (void)_sendToolTipMouseExited {
3315 // Nothing matters except window, trackingNumber, and userData.
3316 int windowNumber = [[self window] windowNumber];
3317 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3318 location:NSZeroPoint
3321 windowNumber:windowNumber
3324 trackingNumber:kTrackingRectTag
3325 userData:trackingRectUserData_];
3326 [trackingRectOwner_ mouseExited:fakeEvent];
3329 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3330 - (void)_sendToolTipMouseEntered {
3331 // Nothing matters except window, trackingNumber, and userData.
3332 int windowNumber = [[self window] windowNumber];
3333 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3334 location:NSZeroPoint
3337 windowNumber:windowNumber
3340 trackingNumber:kTrackingRectTag
3341 userData:trackingRectUserData_];
3342 [trackingRectOwner_ mouseEntered:fakeEvent];
3345 // Sets the view's current tooltip, to be displayed at the current mouse
3346 // location. (This does not make the tooltip appear -- as usual, it only
3347 // appears after a delay.) Pass null to remove the tooltip.
3348 - (void)setToolTipAtMousePoint:(NSString *)string {
3349 NSString *toolTip = [string length] == 0 ? nil : string;
3350 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3351 (!toolTip && !toolTip_)) {
3356 [self _sendToolTipMouseExited];
3359 toolTip_.reset([toolTip copy]);
3362 // See radar 3500217 for why we remove all tooltips
3363 // rather than just the single one we created.
3364 [self removeAllToolTips];
3365 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3366 lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3369 [self _sendToolTipMouseEntered];
3373 // NSView calls this to get the text when displaying the tooltip.
3374 - (NSString *)view:(NSView *)view
3375 stringForToolTip:(NSToolTipTag)tag
3376 point:(NSPoint)point
3377 userData:(void *)data {
3378 return [[toolTip_ copy] autorelease];
3381 // Below is our NSTextInputClient implementation.
3383 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3384 // functions to process this event.
3386 // [WebHTMLView keyDown] ->
3387 // EventHandler::keyEvent() ->
3389 // [WebEditorClient handleKeyboardEvent] ->
3390 // [WebHTMLView _interceptEditingKeyEvent] ->
3391 // [NSResponder interpretKeyEvents] ->
3392 // [WebHTMLView insertText] ->
3393 // Editor::insertText()
3395 // Unfortunately, it is hard for Chromium to use this implementation because
3396 // it causes key-typing jank.
3397 // RenderWidgetHostViewMac is running in a browser process. On the other
3398 // hand, Editor and EventHandler are running in a renderer process.
3399 // So, if we used this implementation, a NSKeyDown event is dispatched to
3400 // the following functions of Chromium.
3402 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3403 // |Sync IPC (KeyDown)| (*1) ->
3404 // EventHandler::keyEvent() (renderer) ->
3406 // EditorClientImpl::handleKeyboardEvent() (renderer) ->
3407 // |Sync IPC| (*2) ->
3408 // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3409 // [self interpretKeyEvents] ->
3410 // [RenderWidgetHostViewMac insertText] (browser) ->
3412 // Editor::insertText() (renderer)
3414 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3415 // result of EventHandler::keyEvent().
3416 // (*2) we need to wait until this call finishes since WebEditorClient uses
3417 // the result of [WebHTMLView _interceptEditingKeyEvent].
3419 // This needs many sync IPC messages sent between a browser and a renderer for
3420 // each key event, which would probably result in key-typing jank.
3421 // To avoid this problem, this implementation processes key events (and input
3422 // method events) totally in a browser process and sends asynchronous input
3423 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3424 // renderer process.
3426 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3427 // |Async IPC (RawKeyDown)| ->
3428 // [self interpretKeyEvents] ->
3429 // [RenderWidgetHostViewMac insertText] (browser) ->
3430 // |Async IPC (Char)| ->
3431 // Editor::insertText() (renderer)
3433 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3434 // make any key-typing jank. --hbono 7/23/09
3437 extern NSString *NSTextInputReplacementRangeAttributeName;
3440 - (NSArray *)validAttributesForMarkedText {
3441 // This code is just copied from WebKit except renaming variables.
3442 if (!validAttributesForMarkedText_) {
3443 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3444 NSUnderlineStyleAttributeName,
3445 NSUnderlineColorAttributeName,
3446 NSMarkedClauseSegmentAttributeName,
3447 NSTextInputReplacementRangeAttributeName,
3450 return validAttributesForMarkedText_.get();
3453 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3454 DCHECK([self window]);
3455 // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3456 // coordinates (upper left origin). Scroll offsets will be taken care of in
3458 thePoint = [[self window] convertScreenToBase:thePoint];
3459 thePoint = [self convertPoint:thePoint fromView:nil];
3460 thePoint.y = NSHeight([self frame]) - thePoint.y;
3463 TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3464 renderWidgetHostView_->render_widget_host_,
3465 gfx::Point(thePoint.x, thePoint.y));
3469 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3470 actualRange:(NSRangePointer)actualRange {
3472 if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3476 rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3477 renderWidgetHostView_->render_widget_host_, theRange);
3479 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3481 *actualRange = theRange;
3484 // The returned rectangle is in WebKit coordinates (upper left origin), so
3485 // flip the coordinate system.
3486 NSRect viewFrame = [self frame];
3487 rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3491 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3492 actualRange:(NSRangePointer)actualRange {
3493 NSRect rect = [self firstViewRectForCharacterRange:theRange
3494 actualRange:actualRange];
3496 // Convert into screen coordinates for return.
3497 rect = [self convertRect:rect toView:nil];
3498 rect.origin = [[self window] convertBaseToScreen:rect.origin];
3502 - (NSRange)markedRange {
3503 // An input method calls this method to check if an application really has
3504 // a text being composed when hasMarkedText call returns true.
3505 // Returns the range saved in the setMarkedText method so the input method
3506 // calls the setMarkedText method and we can update the composition node
3507 // there. (When this method returns an empty range, the input method doesn't
3508 // call the setMarkedText method.)
3509 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3512 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3513 actualRange:(NSRangePointer)actualRange {
3514 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3516 *actualRange = range;
3517 NSAttributedString* str =
3518 TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3519 renderWidgetHostView_->render_widget_host_, range);
3523 - (NSInteger)conversationIdentifier {
3524 return reinterpret_cast<NSInteger>(self);
3527 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3528 // nil when the caret is in non-editable content or password box to avoid
3529 // making input methods do their work.
3530 - (NSTextInputContext *)inputContext {
3531 if (focusedPluginIdentifier_ != -1)
3532 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3534 switch(renderWidgetHostView_->text_input_type_) {
3535 case ui::TEXT_INPUT_TYPE_NONE:
3536 case ui::TEXT_INPUT_TYPE_PASSWORD:
3539 return [super inputContext];
3543 - (BOOL)hasMarkedText {
3544 // An input method calls this function to figure out whether or not an
3545 // application is really composing a text. If it is composing, it calls
3546 // the markedRange method, and maybe calls the setMarkedText method.
3547 // It seems an input method usually calls this function when it is about to
3548 // cancel an ongoing composition. If an application has a non-empty marked
3549 // range, it calls the setMarkedText method to delete the range.
3550 return hasMarkedText_;
3553 - (void)unmarkText {
3554 // Delete the composition node of the renderer and finish an ongoing
3556 // It seems an input method calls the setMarkedText method and set an empty
3557 // text when it cancels an ongoing composition, i.e. I have never seen an
3558 // input method calls this method.
3559 hasMarkedText_ = NO;
3560 markedText_.clear();
3561 underlines_.clear();
3563 // If we are handling a key down event, then ConfirmComposition() will be
3564 // called in keyEvent: method.
3565 if (!handlingKeyDown_) {
3566 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3567 base::string16(), gfx::Range::InvalidRange(), false);
3569 unmarkTextCalled_ = YES;
3573 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3574 replacementRange:(NSRange)replacementRange {
3575 // An input method updates the composition string.
3576 // We send the given text and range to the renderer so it can update the
3577 // composition node of WebKit.
3578 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3579 // the full web content.
3580 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3581 NSString* im_text = isAttributedString ? [string string] : string;
3582 int length = [im_text length];
3584 // |markedRange_| will get set on a callback from ImeSetComposition().
3585 selectedRange_ = newSelRange;
3586 markedText_ = base::SysNSStringToUTF16(im_text);
3587 hasMarkedText_ = (length > 0);
3589 underlines_.clear();
3590 if (isAttributedString) {
3591 ExtractUnderlines(string, &underlines_);
3593 // Use a thin black underline by default.
3594 underlines_.push_back(
3595 blink::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
3598 // If we are handling a key down event, then SetComposition() will be
3599 // called in keyEvent: method.
3600 // Input methods of Mac use setMarkedText calls with an empty text to cancel
3601 // an ongoing composition. So, we should check whether or not the given text
3602 // is empty to update the input method state. (Our input method backend can
3603 // automatically cancels an ongoing composition when we send an empty text.
3604 // So, it is OK to send an empty text to the renderer.)
3605 if (!handlingKeyDown_) {
3606 renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3607 markedText_, underlines_,
3608 newSelRange.location, NSMaxRange(newSelRange));
3612 - (void)doCommandBySelector:(SEL)selector {
3613 // An input method calls this function to dispatch an editing command to be
3614 // handled by this view.
3615 if (selector == @selector(noop:))
3618 std::string command(
3619 [RenderWidgetHostViewMacEditCommandHelper::
3620 CommandNameForSelector(selector) UTF8String]);
3622 // If this method is called when handling a key down event, then we need to
3623 // handle the command in the key event handler. Otherwise we can just handle
3625 if (handlingKeyDown_) {
3626 hasEditCommands_ = YES;
3627 // We ignore commands that insert characters, because this was causing
3628 // strange behavior (e.g. tab always inserted a tab rather than moving to
3629 // the next field on the page).
3630 if (!StartsWithASCII(command, "insert", false))
3631 editCommands_.push_back(EditCommand(command, ""));
3633 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3634 rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
3639 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
3640 // An input method has characters to be inserted.
3641 // Same as Linux, Mac calls this method not only:
3642 // * when an input method finishs composing text, but also;
3643 // * when we type an ASCII character (without using input methods).
3644 // When we aren't using input methods, we should send the given character as
3645 // a Char event so it is dispatched to an onkeypress() event handler of
3647 // On the other hand, when we are using input methods, we should send the
3648 // given characters as an input method event and prevent the characters from
3649 // being dispatched to onkeypress() event handlers.
3650 // Text inserting might be initiated by other source instead of keyboard
3651 // events, such as the Characters dialog. In this case the text should be
3652 // sent as an input method event as well.
3653 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3654 // the full web content.
3655 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3656 NSString* im_text = isAttributedString ? [string string] : string;
3657 if (handlingKeyDown_) {
3658 textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
3660 gfx::Range replacement_range(replacementRange);
3661 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3662 base::SysNSStringToUTF16(im_text), replacement_range, false);
3665 // Inserting text will delete all marked text automatically.
3666 hasMarkedText_ = NO;
3669 - (void)insertText:(id)string {
3670 [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
3673 - (void)viewDidMoveToWindow {
3675 [self updateTabBackingStoreScaleFactor];
3677 if (canBeKeyView_) {
3678 NSWindow* newWindow = [self window];
3679 // Pointer comparison only, since we don't know if lastWindow_ is still
3682 // If we move into a new window, refresh the frame information. We
3683 // don't need to do it if it was the same window as it used to be in,
3684 // since that case is covered by WasShown(). We only want to do this for
3685 // real browser views, not popups.
3686 if (newWindow != lastWindow_) {
3687 lastWindow_ = newWindow;
3688 renderWidgetHostView_->WindowFrameChanged();
3693 // If we switch windows (or are removed from the view hierarchy), cancel any
3694 // open mouse-downs.
3695 if (hasOpenMouseDown_) {
3696 WebMouseEvent event;
3697 event.type = WebInputEvent::MouseUp;
3698 event.button = WebMouseEvent::ButtonLeft;
3699 renderWidgetHostView_->ForwardMouseEvent(event);
3701 hasOpenMouseDown_ = NO;
3704 // Resize the view's layers to match the new window size.
3705 ScopedCAActionDisabler disabler;
3706 [renderWidgetHostView_->software_layer_
3707 setFrame:NSRectToCGRect([self bounds])];
3708 [renderWidgetHostView_->compositing_iosurface_layer_
3709 setFrame:NSRectToCGRect([self bounds])];
3712 - (void)undo:(id)sender {
3713 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3714 static_cast<RenderViewHostImpl*>(
3715 renderWidgetHostView_->render_widget_host_)->Undo();
3719 - (void)redo:(id)sender {
3720 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3721 static_cast<RenderViewHostImpl*>(
3722 renderWidgetHostView_->render_widget_host_)->Redo();
3726 - (void)cut:(id)sender {
3727 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3728 static_cast<RenderViewHostImpl*>(
3729 renderWidgetHostView_->render_widget_host_)->Cut();
3733 - (void)copy:(id)sender {
3734 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3735 static_cast<RenderViewHostImpl*>(
3736 renderWidgetHostView_->render_widget_host_)->Copy();
3740 - (void)copyToFindPboard:(id)sender {
3741 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3742 static_cast<RenderViewHostImpl*>(
3743 renderWidgetHostView_->render_widget_host_)->CopyToFindPboard();
3747 - (void)paste:(id)sender {
3748 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3749 static_cast<RenderViewHostImpl*>(
3750 renderWidgetHostView_->render_widget_host_)->Paste();
3754 - (void)pasteAndMatchStyle:(id)sender {
3755 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3756 static_cast<RenderViewHostImpl*>(
3757 renderWidgetHostView_->render_widget_host_)->PasteAndMatchStyle();
3761 - (void)selectAll:(id)sender {
3762 // editCommand_helper_ adds implementations for most NSResponder methods
3763 // dynamically. But the renderer side only sends selection results back to
3764 // the browser if they were triggered by a keyboard event or went through
3765 // one of the Select methods on RWH. Since selectAll: is called from the
3766 // menu handler, neither is true.
3767 // Explicitly call SelectAll() here to make sure the renderer returns
3768 // selection results.
3769 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3770 static_cast<RenderViewHostImpl*>(
3771 renderWidgetHostView_->render_widget_host_)->SelectAll();
3775 - (void)startSpeaking:(id)sender {
3776 renderWidgetHostView_->SpeakSelection();
3779 - (void)stopSpeaking:(id)sender {
3780 renderWidgetHostView_->StopSpeaking();
3783 - (void)cancelComposition {
3784 if (!hasMarkedText_)
3787 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
3788 // doesn't call any NSTextInput functions, such as setMarkedText or
3789 // insertText. So, we need to send an IPC message to a renderer so it can
3790 // delete the composition node.
3791 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3792 [currentInputManager markedTextAbandoned:self];
3794 hasMarkedText_ = NO;
3795 // Should not call [self unmarkText] here, because it'll send unnecessary
3796 // cancel composition IPC message to the renderer.
3799 - (void)confirmComposition {
3800 if (!hasMarkedText_)
3803 if (renderWidgetHostView_->render_widget_host_)
3804 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3805 base::string16(), gfx::Range::InvalidRange(), false);
3807 [self cancelComposition];
3810 - (void)setPluginImeActive:(BOOL)active {
3811 if (active == pluginImeActive_)
3814 pluginImeActive_ = active;
3816 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
3817 renderWidgetHostView_->PluginImeCompositionCompleted(
3818 base::string16(), focusedPluginIdentifier_);
3822 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
3824 focusedPluginIdentifier_ = pluginId;
3825 else if (focusedPluginIdentifier_ == pluginId)
3826 focusedPluginIdentifier_ = -1;
3828 // Whenever plugin focus changes, plugin IME resets.
3829 [self setPluginImeActive:NO];
3832 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
3833 if (!pluginImeActive_)
3836 ComplexTextInputPanel* inputPanel =
3837 [ComplexTextInputPanel sharedComplexTextInputPanel];
3838 NSString* composited_string = nil;
3839 BOOL handled = [inputPanel interpretKeyEvent:event
3840 string:&composited_string];
3841 if (composited_string) {
3842 renderWidgetHostView_->PluginImeCompositionCompleted(
3843 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
3844 pluginImeActive_ = NO;
3849 - (void)checkForPluginImeCancellation {
3850 if (pluginImeActive_ &&
3851 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
3852 renderWidgetHostView_->PluginImeCompositionCompleted(
3853 base::string16(), focusedPluginIdentifier_);
3854 pluginImeActive_ = NO;
3858 // Overriding a NSResponder method to support application services.
3860 - (id)validRequestorForSendType:(NSString*)sendType
3861 returnType:(NSString*)returnType {
3863 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
3864 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
3865 BOOL hasText = !renderWidgetHostView_->selected_text().empty();
3867 renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
3869 if (sendTypeIsString && hasText && !returnType) {
3871 } else if (!sendType && returnTypeIsString && takesText) {
3873 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
3876 requestor = [super validRequestorForSendType:sendType
3877 returnType:returnType];
3882 - (void)viewWillStartLiveResize {
3883 [super viewWillStartLiveResize];
3884 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3886 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
3889 - (void)viewDidEndLiveResize {
3890 [super viewDidEndLiveResize];
3891 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3893 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
3896 - (void)updateCursor:(NSCursor*)cursor {
3897 if (currentCursor_ == cursor)
3900 currentCursor_.reset([cursor retain]);
3901 [[self window] invalidateCursorRectsForView:self];
3904 - (void)popupWindowWillClose:(NSNotification *)notification {
3905 renderWidgetHostView_->KillSelf();
3908 - (void)updateSoftwareLayerScaleFactor {
3909 if (![renderWidgetHostView_->software_layer_
3910 respondsToSelector:@selector(setContentsScale:)])
3913 ScopedCAActionDisabler disabler;
3914 [renderWidgetHostView_->software_layer_ setContentsScale:deviceScaleFactor_];
3917 // Delegate methods for the software CALayer
3918 - (void)drawLayer:(CALayer*)layer
3919 inContext:(CGContextRef)context {
3920 TRACE_EVENT0("browser", "CompositingIOSurfaceLayer::drawLayer");
3922 DCHECK(renderWidgetHostView_->use_core_animation_);
3923 DCHECK([layer isEqual:renderWidgetHostView_->software_layer_]);
3925 CGRect clipRect = CGContextGetClipBoundingBox(context);
3927 if (!renderWidgetHostView_->render_widget_host_ ||
3928 renderWidgetHostView_->render_widget_host_->is_hidden()) {
3929 CGContextSetFillColorWithColor(context,
3930 CGColorGetConstantColor(kCGColorWhite));
3931 CGContextFillRect(context, clipRect);
3935 renderWidgetHostView_->about_to_validate_and_paint_ = true;
3936 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
3937 renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
3938 renderWidgetHostView_->about_to_validate_and_paint_ = false;
3940 [self drawBackingStore:backingStore
3945 - (void)setNeedsDisplay:(BOOL)flag {
3946 [renderWidgetHostView_->software_layer_ setNeedsDisplay];
3947 [super setNeedsDisplay:flag];
3950 - (void)setNeedsDisplayInRect:(NSRect)rect {
3951 [renderWidgetHostView_->software_layer_
3952 setNeedsDisplayInRect:NSRectToCGRect(rect)];
3953 [super setNeedsDisplayInRect:rect];
3959 // Supporting application services
3961 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
3963 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
3964 types:(NSArray*)types {
3965 const std::string& str = renderWidgetHostView_->selected_text();
3966 if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
3968 base::scoped_nsobject<NSString> text(
3969 [[NSString alloc] initWithUTF8String:str.c_str()]);
3970 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
3971 [pboard declareTypes:toDeclare owner:nil];
3972 return [pboard setString:text forType:NSStringPboardType];
3975 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
3976 NSString *string = [pboard stringForType:NSStringPboardType];
3977 if (!string) return NO;
3979 // If the user is currently using an IME, confirm the IME input,
3980 // and then insert the text from the service, the same as TextEdit and Safari.
3981 [self confirmComposition];
3982 [self insertText:string];
3987 if (renderWidgetHostView_->use_core_animation_)
3989 return [super isOpaque];