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/compositor/browser_compositor_view_mac.h"
31 #include "content/browser/compositor/resize_lock.h"
32 #include "content/browser/frame_host/frame_tree.h"
33 #include "content/browser/frame_host/frame_tree_node.h"
34 #include "content/browser/frame_host/render_frame_host_impl.h"
35 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
36 #include "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
37 #include "content/browser/renderer_host/compositing_iosurface_mac.h"
38 #include "content/browser/renderer_host/render_widget_helper.h"
39 #include "content/browser/renderer_host/render_view_host_impl.h"
40 #import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
41 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
42 #import "content/browser/renderer_host/software_layer_mac.h"
43 #import "content/browser/renderer_host/text_input_client_mac.h"
44 #include "content/common/accessibility_messages.h"
45 #include "content/common/edit_command.h"
46 #include "content/common/gpu/gpu_messages.h"
47 #include "content/common/input_messages.h"
48 #include "content/common/view_messages.h"
49 #include "content/common/webplugin_geometry.h"
50 #include "content/public/browser/browser_thread.h"
51 #include "content/public/browser/native_web_keyboard_event.h"
52 #include "content/public/browser/notification_service.h"
53 #include "content/public/browser/notification_types.h"
54 #include "content/public/browser/render_widget_host_view_frame_subscriber.h"
55 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
56 #include "content/public/browser/user_metrics.h"
57 #include "content/public/browser/web_contents.h"
58 #include "skia/ext/platform_canvas.h"
59 #include "third_party/WebKit/public/platform/WebScreenInfo.h"
60 #include "third_party/WebKit/public/web/WebInputEvent.h"
61 #include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
62 #import "third_party/mozilla/ComplexTextInputPanel.h"
63 #include "ui/base/cocoa/animation_utils.h"
64 #import "ui/base/cocoa/fullscreen_window_manager.h"
65 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
66 #include "ui/events/keycodes/keyboard_codes.h"
67 #include "ui/base/layout.h"
68 #include "ui/compositor/compositor.h"
69 #include "ui/compositor/layer.h"
70 #include "ui/gfx/display.h"
71 #include "ui/gfx/point.h"
72 #include "ui/gfx/rect_conversions.h"
73 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
74 #include "ui/gfx/screen.h"
75 #include "ui/gfx/size_conversions.h"
76 #include "ui/gl/gl_switches.h"
77 #include "ui/gl/io_surface_support_mac.h"
79 using content::BrowserAccessibility;
80 using content::BrowserAccessibilityManager;
81 using content::EditCommand;
82 using content::FrameTreeNode;
83 using content::NativeWebKeyboardEvent;
84 using content::RenderFrameHost;
85 using content::RenderViewHost;
86 using content::RenderViewHostImpl;
87 using content::RenderWidgetHostImpl;
88 using content::RenderWidgetHostViewMac;
89 using content::RenderWidgetHostViewMacEditCommandHelper;
90 using content::TextInputClientMac;
91 using content::WebContents;
92 using blink::WebInputEvent;
93 using blink::WebInputEventFactory;
94 using blink::WebMouseEvent;
95 using blink::WebMouseWheelEvent;
96 using blink::WebGestureEvent;
98 // These are not documented, so use only after checking -respondsToSelector:.
99 @interface NSApplication (UndocumentedSpeechMethods)
100 - (void)speakString:(NSString*)string;
101 - (void)stopSpeaking:(id)sender;
105 // Declare things that are part of the 10.7 SDK.
106 #if !defined(MAC_OS_X_VERSION_10_7) || \
107 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
108 @interface NSView (NSOpenGLSurfaceResolutionLionAPI)
109 - (void)setWantsBestResolutionOpenGLSurface:(BOOL)flag;
112 static NSString* const NSWindowDidChangeBackingPropertiesNotification =
113 @"NSWindowDidChangeBackingPropertiesNotification";
117 // Declare things that are part of the 10.9 SDK.
118 #if !defined(MAC_OS_X_VERSION_10_9) || \
119 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9
121 NSWindowOcclusionStateVisible = 1UL << 1,
123 typedef NSUInteger NSWindowOcclusionState;
125 @interface NSWindow (MavericksAPI)
126 - (NSWindowOcclusionState)occlusionState;
131 // This method will return YES for OS X versions 10.7.3 and later, and NO
133 // Used to prevent a crash when building with the 10.7 SDK and accessing the
134 // notification below. See: http://crbug.com/260595.
135 static BOOL SupportsBackingPropertiesChangedNotification() {
136 // windowDidChangeBackingProperties: method has been added to the
137 // NSWindowDelegate protocol in 10.7.3, at the same time as the
138 // NSWindowDidChangeBackingPropertiesNotification notification was added.
139 // If the protocol contains this method description, the notification should
140 // be supported as well.
141 Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
142 struct objc_method_description methodDescription =
143 protocol_getMethodDescription(
144 windowDelegateProtocol,
145 @selector(windowDidChangeBackingProperties:),
149 // If the protocol does not contain the method, the returned method
150 // description is {NULL, NULL}
151 return methodDescription.name != NULL || methodDescription.types != NULL;
155 @interface RenderWidgetHostViewCocoa ()
156 @property(nonatomic, assign) NSRange selectedRange;
157 @property(nonatomic, assign) NSRange markedRange;
159 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
160 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
161 - (void)gotUnhandledWheelEvent;
162 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right;
163 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar;
164 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
165 - (void)windowDidChangeBackingProperties:(NSNotification*)notification;
166 - (void)windowChangedGlobalFrame:(NSNotification*)notification;
167 - (void)drawWithDirtyRect:(CGRect)dirtyRect
168 inContext:(CGContextRef)context;
169 - (void)checkForPluginImeCancellation;
170 - (void)updateScreenProperties;
171 - (void)setResponderDelegate:
172 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate;
175 // A window subclass that allows the fullscreen window to become main and gain
176 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
177 // handled by the browser.
178 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
181 @implementation PepperFlashFullscreenWindow
183 - (BOOL)canBecomeKeyWindow {
187 - (BOOL)canBecomeMainWindow {
193 @interface RenderWidgetPopupWindow : NSWindow {
194 // The event tap that allows monitoring of all events, to properly close with
195 // a click outside the bounds of the window.
200 @implementation RenderWidgetPopupWindow
202 - (id)initWithContentRect:(NSRect)contentRect
203 styleMask:(NSUInteger)windowStyle
204 backing:(NSBackingStoreType)bufferingType
205 defer:(BOOL)deferCreation {
206 if (self = [super initWithContentRect:contentRect
207 styleMask:windowStyle
208 backing:bufferingType
209 defer:deferCreation]) {
210 DCHECK_EQ(content::CORE_ANIMATION_DISABLED,
211 content::GetCoreAnimationStatus());
213 [self setBackgroundColor:[NSColor clearColor]];
214 [self startObservingClicks];
220 [self stopObservingClicks];
224 // Gets called when the menubar is clicked.
225 // Needed because the local event monitor doesn't see the click on the menubar.
226 - (void)beganTracking:(NSNotification*)notification {
230 // Install the callback.
231 - (void)startObservingClicks {
232 clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
233 handler:^NSEvent* (NSEvent* event) {
234 if ([event window] == self)
236 NSEventType eventType = [event type];
237 if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
242 NSNotificationCenter* notificationCenter =
243 [NSNotificationCenter defaultCenter];
244 [notificationCenter addObserver:self
245 selector:@selector(beganTracking:)
246 name:NSMenuDidBeginTrackingNotification
247 object:[NSApp mainMenu]];
250 // Remove the callback.
251 - (void)stopObservingClicks {
255 [NSEvent removeMonitor:clickEventTap_];
256 clickEventTap_ = nil;
258 NSNotificationCenter* notificationCenter =
259 [NSNotificationCenter defaultCenter];
260 [notificationCenter removeObserver:self
261 name:NSMenuDidBeginTrackingNotification
262 object:[NSApp mainMenu]];
269 // Maximum number of characters we allow in a tooltip.
270 const size_t kMaxTooltipLength = 1024;
272 // TODO(suzhe): Upstream this function.
273 blink::WebColor WebColorFromNSColor(NSColor *color) {
275 [color getRed:&r green:&g blue:&b alpha:&a];
278 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
279 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
280 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
281 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
284 // Extract underline information from an attributed string. Mostly copied from
285 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
286 void ExtractUnderlines(
287 NSAttributedString* string,
288 std::vector<blink::WebCompositionUnderline>* underlines) {
289 int length = [[string string] length];
293 NSDictionary* attrs = [string attributesAtIndex:i
294 longestEffectiveRange:&range
295 inRange:NSMakeRange(i, length - i)];
296 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
297 blink::WebColor color = SK_ColorBLACK;
298 if (NSColor *colorAttr =
299 [attrs objectForKey:NSUnderlineColorAttributeName]) {
300 color = WebColorFromNSColor(
301 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
303 underlines->push_back(blink::WebCompositionUnderline(
304 range.location, NSMaxRange(range), color, [style intValue] > 1));
306 i = range.location + range.length;
310 // EnablePasswordInput() and DisablePasswordInput() are copied from
311 // enableSecureTextInput() and disableSecureTextInput() functions in
312 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
313 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
314 // here, because they are already called in webkit and they are system wide
316 void EnablePasswordInput() {
317 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
318 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
319 sizeof(CFArrayRef), &inputSources);
320 CFRelease(inputSources);
323 void DisablePasswordInput() {
324 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
327 // Calls to [NSScreen screens], required by FlipYFromRectToScreen and
328 // FlipNSRectToRectScreen, can take several milliseconds. Only re-compute this
329 // value when screen info changes.
330 // TODO(ccameron): An observer on every RWHVCocoa will set this to false
331 // on NSApplicationDidChangeScreenParametersNotification. Only one observer
333 bool g_screen_info_up_to_date = false;
335 float FlipYFromRectToScreen(float y, float rect_height) {
336 TRACE_EVENT0("browser", "FlipYFromRectToScreen");
337 static CGFloat screen_zero_height = 0;
338 if (!g_screen_info_up_to_date) {
339 if ([[NSScreen screens] count] > 0) {
341 [[[NSScreen screens] objectAtIndex:0] frame].size.height;
342 g_screen_info_up_to_date = true;
347 return screen_zero_height - y - rect_height;
350 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
351 // left of the primary screen (Carbon coordinates), and stuffs it into a
353 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
354 gfx::Rect new_rect(NSRectToCGRect(rect));
355 new_rect.set_y(FlipYFromRectToScreen(new_rect.y(), new_rect.height()));
359 // Returns the window that visually contains the given view. This is different
360 // from [view window] in the case of tab dragging, where the view's owning
361 // window is a floating panel attached to the actual browser window that the tab
362 // is visually part of.
363 NSWindow* ApparentWindowForView(NSView* view) {
364 // TODO(shess): In case of !window, the view has been removed from
365 // the view hierarchy because the tab isn't main. Could retrieve
366 // the information from the main tab for our window.
367 NSWindow* enclosing_window = [view window];
369 // See if this is a tab drag window. The width check is to distinguish that
370 // case from extension popup windows.
371 NSWindow* ancestor_window = [enclosing_window parentWindow];
372 if (ancestor_window && (NSWidth([enclosing_window frame]) ==
373 NSWidth([ancestor_window frame]))) {
374 enclosing_window = ancestor_window;
377 return enclosing_window;
380 blink::WebScreenInfo GetWebScreenInfo(NSView* view) {
381 gfx::Display display =
382 gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
384 NSScreen* screen = [NSScreen deepestScreen];
386 blink::WebScreenInfo results;
388 results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
389 results.depth = NSBitsPerPixelFromDepth([screen depth]);
390 results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
391 results.isMonochrome =
392 [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
393 results.rect = display.bounds();
394 results.availableRect = display.work_area();
395 results.orientationAngle = display.RotationAsDegree();
400 void RemoveLayerFromSuperlayer(
401 base::scoped_nsobject<CompositingIOSurfaceLayer> layer) {
402 // Disable the fade-out animation as the layer is removed.
403 ScopedCAActionDisabler disabler;
404 [layer removeFromSuperlayer];
411 ////////////////////////////////////////////////////////////////////////////////
412 // DelegatedFrameHost, public:
414 ui::Compositor* RenderWidgetHostViewMac::GetCompositor() const {
415 return [browser_compositor_view_ compositor];
418 ui::Layer* RenderWidgetHostViewMac::GetLayer() {
419 return root_layer_.get();
422 RenderWidgetHostImpl* RenderWidgetHostViewMac::GetHost() {
423 return render_widget_host_;
426 void RenderWidgetHostViewMac::SchedulePaintInRect(
427 const gfx::Rect& damage_rect_in_dip) {
428 [browser_compositor_view_ compositor]->ScheduleFullRedraw();
431 bool RenderWidgetHostViewMac::IsVisible() {
432 return !render_widget_host_->is_hidden();
435 gfx::Size RenderWidgetHostViewMac::DesiredFrameSize() {
436 return GetViewBounds().size();
439 float RenderWidgetHostViewMac::CurrentDeviceScaleFactor() {
440 return ViewScaleFactor();
443 gfx::Size RenderWidgetHostViewMac::ConvertViewSizeToPixel(
444 const gfx::Size& size) {
445 return gfx::ToEnclosingRect(gfx::ScaleRect(gfx::Rect(size),
446 ViewScaleFactor())).size();
449 scoped_ptr<ResizeLock> RenderWidgetHostViewMac::CreateResizeLock(
450 bool defer_compositor_lock) {
452 ResizeLock* lock = NULL;
453 return scoped_ptr<ResizeLock>(lock);
456 DelegatedFrameHost* RenderWidgetHostViewMac::GetDelegatedFrameHost() const {
457 return delegated_frame_host_.get();
460 ///////////////////////////////////////////////////////////////////////////////
461 // RenderWidgetHostViewBase, public:
464 void RenderWidgetHostViewBase::GetDefaultScreenInfo(
465 blink::WebScreenInfo* results) {
466 *results = GetWebScreenInfo(NULL);
469 ///////////////////////////////////////////////////////////////////////////////
470 // RenderWidgetHostViewMac, public:
472 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
473 : render_widget_host_(RenderWidgetHostImpl::From(widget)),
474 last_frame_was_accelerated_(false),
475 text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
476 can_compose_inline_(true),
477 allow_overlapping_views_(false),
478 use_core_animation_(false),
479 pending_latency_info_delay_(0),
480 pending_latency_info_delay_weak_ptr_factory_(this),
481 backing_store_scale_factor_(1),
484 fullscreen_parent_host_view_(NULL),
485 underlay_view_has_drawn_(false),
486 overlay_view_weak_factory_(this),
487 software_frame_weak_ptr_factory_(this) {
488 software_frame_manager_.reset(new SoftwareFrameManager(
489 software_frame_weak_ptr_factory_.GetWeakPtr()));
490 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
491 // goes away. Since we autorelease it, our caller must put
492 // |GetNativeView()| into the view hierarchy right after calling us.
493 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
494 initWithRenderWidgetHostViewMac:this] autorelease];
496 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED) {
497 use_core_animation_ = true;
498 background_layer_.reset([[CALayer alloc] init]);
500 setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
501 [cocoa_view_ setLayer:background_layer_];
502 [cocoa_view_ setWantsLayer:YES];
505 render_widget_host_->SetView(this);
508 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
509 // This is being called from |cocoa_view_|'s destructor, so invalidate the
513 // Delete the delegated frame state.
514 delegated_frame_host_.reset();
519 // Make sure that the layer doesn't reach into the now-invalid object.
520 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
521 DestroySoftwareLayer();
523 // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
524 // RenderWidgetHost does we need to tell it not to hold a stale pointer to
526 if (render_widget_host_)
527 render_widget_host_->SetView(NULL);
530 void RenderWidgetHostViewMac::SetDelegate(
531 NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
532 [cocoa_view_ setResponderDelegate:delegate];
535 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
536 if (allow_overlapping_views_ == overlapping)
538 allow_overlapping_views_ = overlapping;
539 [cocoa_view_ setNeedsDisplay:YES];
540 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
543 ///////////////////////////////////////////////////////////////////////////////
544 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
546 bool RenderWidgetHostViewMac::EnsureCompositedIOSurface() {
547 // If the context or the IOSurface's context has had an error, re-build
548 // everything from scratch.
549 if (compositing_iosurface_context_ &&
550 compositing_iosurface_context_->HasBeenPoisoned()) {
551 LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
552 << "context was poisoned";
555 if (compositing_iosurface_ &&
556 compositing_iosurface_->HasBeenPoisoned()) {
557 LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
558 << "surface was poisoned";
562 int current_window_number = use_core_animation_ ?
563 CompositingIOSurfaceContext::kOffscreenContextWindowNumber :
565 bool new_surface_needed = !compositing_iosurface_;
566 bool new_context_needed =
567 !compositing_iosurface_context_ ||
568 (compositing_iosurface_context_ &&
569 compositing_iosurface_context_->window_number() !=
570 current_window_number);
572 if (!new_surface_needed && !new_context_needed)
575 // Create the GL context and shaders.
576 if (new_context_needed) {
577 scoped_refptr<CompositingIOSurfaceContext> new_context =
578 CompositingIOSurfaceContext::Get(current_window_number);
579 // Un-bind the GL context from this view before binding the new GL
580 // context. Having two GL contexts bound to a view will result in
581 // crashes and corruption.
582 // http://crbug.com/230883
583 ClearBoundContextDrawable();
585 LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
588 compositing_iosurface_context_ = new_context;
591 // Create the IOSurface texture.
592 if (new_surface_needed) {
593 compositing_iosurface_ = CompositingIOSurfaceMac::Create();
594 if (!compositing_iosurface_) {
595 LOG(ERROR) << "Failed to create CompositingIOSurface";
603 void RenderWidgetHostViewMac::EnsureSoftwareLayer() {
604 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::EnsureSoftwareLayer");
605 if (software_layer_ || !use_core_animation_)
608 software_layer_.reset([[SoftwareLayer alloc] init]);
609 DCHECK(software_layer_);
611 // Disable the fade-in animation as the layer is added.
612 ScopedCAActionDisabler disabler;
613 [background_layer_ addSublayer:software_layer_];
616 void RenderWidgetHostViewMac::DestroySoftwareLayer() {
617 if (!software_layer_)
620 // Disable the fade-out animation as the layer is removed.
621 ScopedCAActionDisabler disabler;
622 [software_layer_ removeFromSuperlayer];
623 software_layer_.reset();
626 void RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer() {
627 TRACE_EVENT0("browser",
628 "RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer");
629 DCHECK(compositing_iosurface_context_);
630 if (compositing_iosurface_layer_ || !use_core_animation_)
633 compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
634 initWithIOSurface:compositing_iosurface_
636 DCHECK(compositing_iosurface_layer_);
638 // Disable the fade-in animation as the layer is added.
639 ScopedCAActionDisabler disabler;
640 [background_layer_ addSublayer:compositing_iosurface_layer_];
643 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceLayer(
644 DestroyCompositedIOSurfaceLayerBehavior destroy_layer_behavior) {
645 if (!compositing_iosurface_layer_)
648 if (destroy_layer_behavior == kRemoveLayerFromHierarchy) {
649 // Disable the fade-out animation as the layer is removed.
650 ScopedCAActionDisabler disabler;
651 [compositing_iosurface_layer_ removeFromSuperlayer];
653 [compositing_iosurface_layer_ resetClient];
654 compositing_iosurface_layer_.reset();
657 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer(
658 DestroyContextBehavior destroy_context_behavior) {
659 // Any pending frames will not be displayed, so ack them now.
660 SendPendingSwapAck();
662 DestroyCompositedIOSurfaceLayer(kRemoveLayerFromHierarchy);
663 compositing_iosurface_ = NULL;
665 switch (destroy_context_behavior) {
666 case kLeaveContextBoundToView:
668 case kDestroyContext:
669 ClearBoundContextDrawable();
670 compositing_iosurface_context_ = NULL;
678 void RenderWidgetHostViewMac::ClearBoundContextDrawable() {
679 if (use_core_animation_)
682 if (compositing_iosurface_context_ &&
684 [[compositing_iosurface_context_->nsgl_context() view]
685 isEqual:cocoa_view_]) {
686 // Disable screen updates because removing the GL context from below can
688 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
689 [compositing_iosurface_context_->nsgl_context() clearDrawable];
693 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
695 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
696 IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
697 IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
698 IPC_MESSAGE_HANDLER(ViewHostMsg_DidChangeScrollbarsForMainFrame,
699 OnDidChangeScrollbarsForMainFrame)
700 IPC_MESSAGE_UNHANDLED(handled = false)
701 IPC_END_MESSAGE_MAP()
705 void RenderWidgetHostViewMac::InitAsChild(
706 gfx::NativeView parent_view) {
709 void RenderWidgetHostViewMac::InitAsPopup(
710 RenderWidgetHostView* parent_host_view,
711 const gfx::Rect& pos) {
712 bool activatable = popup_type_ == blink::WebPopupTypeNone;
713 [cocoa_view_ setCloseOnDeactivate:YES];
714 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
716 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
717 origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height());
719 popup_window_.reset([[RenderWidgetPopupWindow alloc]
720 initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
721 pos.width(), pos.height())
722 styleMask:NSBorderlessWindowMask
723 backing:NSBackingStoreBuffered
725 [popup_window_ setLevel:NSPopUpMenuWindowLevel];
726 [popup_window_ setReleasedWhenClosed:NO];
727 [popup_window_ makeKeyAndOrderFront:nil];
728 [[popup_window_ contentView] addSubview:cocoa_view_];
729 [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
730 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
731 [[NSNotificationCenter defaultCenter]
732 addObserver:cocoa_view_
733 selector:@selector(popupWindowWillClose:)
734 name:NSWindowWillCloseNotification
735 object:popup_window_];
738 // This function creates the fullscreen window and hides the dock and menubar if
739 // necessary. Note, this codepath is only used for pepper flash when
740 // pp::FlashFullScreen::SetFullscreen() is called. If
741 // pp::FullScreen::SetFullscreen() is called then the entire browser window
742 // will enter fullscreen instead.
743 void RenderWidgetHostViewMac::InitAsFullscreen(
744 RenderWidgetHostView* reference_host_view) {
745 fullscreen_parent_host_view_ =
746 static_cast<RenderWidgetHostViewMac*>(reference_host_view);
747 NSWindow* parent_window = nil;
748 if (reference_host_view)
749 parent_window = [reference_host_view->GetNativeView() window];
750 NSScreen* screen = [parent_window screen];
752 screen = [NSScreen mainScreen];
754 pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
755 initWithContentRect:[screen frame]
756 styleMask:NSBorderlessWindowMask
757 backing:NSBackingStoreBuffered
759 [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
760 [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
761 [cocoa_view_ setCanBeKeyView:YES];
762 [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
763 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
764 // If the pepper fullscreen window isn't opaque then there are performance
765 // issues when it's on the discrete GPU and the Chrome window is being drawn
766 // to. http://crbug.com/171911
767 [pepper_fullscreen_window_ setOpaque:YES];
769 // Note that this forms a reference cycle between the fullscreen window and
770 // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
771 // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
772 // This cycle is normally broken when -keyEvent: receives an <esc> key, which
773 // explicitly calls Shutdown on the render_widget_host_, which calls
774 // Destroy() on RWHVMac, which drops the reference to
775 // pepper_fullscreen_window_.
776 [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
778 // Note that this keeps another reference to pepper_fullscreen_window_.
779 fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
780 initWithWindow:pepper_fullscreen_window_.get()
781 desiredScreen:screen]);
782 [fullscreen_window_manager_ enterFullscreenMode];
783 [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
786 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
787 // See comment in InitAsFullscreen(): There is a reference cycle between
788 // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
789 // Tests that test pepper fullscreen mode without sending an <esc> event
790 // need to call this method to break the reference cycle.
791 [fullscreen_window_manager_ exitFullscreenMode];
792 fullscreen_window_manager_.reset();
793 [pepper_fullscreen_window_ close];
794 pepper_fullscreen_window_.reset();
797 int RenderWidgetHostViewMac::window_number() const {
798 NSWindow* window = [cocoa_view_ window];
801 return [window windowNumber];
804 float RenderWidgetHostViewMac::ViewScaleFactor() const {
805 return ui::GetScaleFactorForNativeView(cocoa_view_);
808 void RenderWidgetHostViewMac::UpdateDisplayLink() {
809 static bool is_vsync_disabled =
810 CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync);
811 if (is_vsync_disabled)
814 NSScreen* screen = [[cocoa_view_ window] screen];
815 NSDictionary* screen_description = [screen deviceDescription];
816 NSNumber* screen_number = [screen_description objectForKey:@"NSScreenNumber"];
817 CGDirectDisplayID display_id = [screen_number unsignedIntValue];
819 display_link_ = DisplayLinkMac::GetForDisplay(display_id);
820 if (!display_link_) {
821 // Note that on some headless systems, the display link will fail to be
822 // created, so this should not be a fatal error.
823 LOG(ERROR) << "Failed to create display link.";
827 void RenderWidgetHostViewMac::SendVSyncParametersToRenderer() {
828 if (!render_widget_host_ || !display_link_)
831 base::TimeTicks timebase;
832 base::TimeDelta interval;
833 if (!display_link_->GetVSyncParameters(&timebase, &interval))
836 render_widget_host_->UpdateVSyncParameters(timebase, interval);
839 void RenderWidgetHostViewMac::UpdateBackingStoreScaleFactor() {
840 if (!render_widget_host_)
843 float new_scale_factor = ui::GetScaleFactorForNativeView(cocoa_view_);
844 if (new_scale_factor == backing_store_scale_factor_)
846 backing_store_scale_factor_ = new_scale_factor;
848 render_widget_host_->NotifyScreenInfoChanged();
851 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
852 return render_widget_host_;
855 void RenderWidgetHostViewMac::WasShown() {
856 if (!render_widget_host_->is_hidden())
859 if (web_contents_switch_paint_time_.is_null())
860 web_contents_switch_paint_time_ = base::TimeTicks::Now();
861 render_widget_host_->WasShown();
862 software_frame_manager_->SetVisibility(true);
863 if (delegated_frame_host_)
864 delegated_frame_host_->WasShown();
866 // Call setNeedsDisplay before pausing for new frames to come in -- if any
867 // do, and are drawn, then the needsDisplay bit will be cleared.
868 [compositing_iosurface_layer_ setNeedsDisplay];
869 PauseForPendingResizeOrRepaintsAndDraw();
871 // We're messing with the window, so do this to ensure no flashes.
872 if (!use_core_animation_)
873 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
876 void RenderWidgetHostViewMac::WasHidden() {
877 if (render_widget_host_->is_hidden())
880 // Any pending frames will not be displayed until this is shown again. Ack
882 SendPendingSwapAck();
884 // If we have a renderer, then inform it that we are being hidden so it can
885 // reduce its resource utilization.
886 render_widget_host_->WasHidden();
887 software_frame_manager_->SetVisibility(false);
888 if (delegated_frame_host_)
889 delegated_frame_host_->WasHidden();
891 // There can be a transparent flash as this view is removed and the next is
892 // added, because of OSX windowing races between displaying the contents of
893 // the NSView and its corresponding OpenGL context.
894 // disableScreenUpdatesUntilFlush prevents the transparent flash by avoiding
895 // screen updates until the next tab draws.
896 if (!use_core_animation_)
897 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
899 web_contents_switch_paint_time_ = base::TimeTicks();
902 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
903 gfx::Rect rect = GetViewBounds();
908 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
909 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
910 // TODO(thakis): fix, http://crbug.com/73362
911 if (render_widget_host_->is_hidden())
914 // During the initial creation of the RenderWidgetHostView in
915 // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
916 // an empty size. In the Windows code flow, it is not ignored because
917 // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
918 // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
919 // flags to keep things sized properly. On the other hand, if the size is not
920 // empty then this is a valid request for a pop-up.
921 if (rect.size().IsEmpty())
924 // Ignore the position of |rect| for non-popup rwhvs. This is because
925 // background tabs do not have a window, but the window is required for the
926 // coordinate conversions. Popups are always for a visible tab.
928 // Note: If |cocoa_view_| has been removed from the view hierarchy, it's still
929 // valid for resizing to be requested (e.g., during tab capture, to size the
930 // view to screen-capture resolution). In this case, simply treat the view as
931 // relative to the screen.
932 BOOL isRelativeToScreen = IsPopup() ||
933 ![[cocoa_view_ superview] isKindOfClass:[BaseView class]];
934 if (isRelativeToScreen) {
935 // The position of |rect| is screen coordinate system and we have to
936 // consider Cocoa coordinate system is upside-down and also multi-screen.
937 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
938 NSSize size = NSMakeSize(rect.width(), rect.height());
939 size = [cocoa_view_ convertSize:size toView:nil];
940 origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height);
941 NSRect frame = NSMakeRect(origin_global.x, origin_global.y,
942 size.width, size.height);
944 [popup_window_ setFrame:frame display:YES];
946 [cocoa_view_ setFrame:frame];
948 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
949 gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
950 rect2.set_width(rect.width());
951 rect2.set_height(rect.height());
952 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
956 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
960 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
961 return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
964 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
966 return static_cast<gfx::NativeViewAccessible>(NULL);
969 void RenderWidgetHostViewMac::MovePluginWindows(
970 const std::vector<WebPluginGeometry>& moves) {
971 // Must be overridden, but unused on this platform. Core Animation
972 // plugins are drawn by the GPU process (through the compositor),
973 // and Core Graphics plugins are drawn by the renderer process.
974 DCHECK_CURRENTLY_ON(BrowserThread::UI);
977 void RenderWidgetHostViewMac::Focus() {
978 [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
981 void RenderWidgetHostViewMac::Blur() {
983 [[cocoa_view_ window] makeFirstResponder:nil];
986 bool RenderWidgetHostViewMac::HasFocus() const {
987 return [[cocoa_view_ window] firstResponder] == cocoa_view_;
990 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
991 if (delegated_frame_host_)
992 return delegated_frame_host_->CanCopyToBitmap();
994 return software_frame_manager_->HasCurrentFrame() ||
995 (compositing_iosurface_ && compositing_iosurface_->HasIOSurface());
998 void RenderWidgetHostViewMac::Show() {
999 [cocoa_view_ setHidden:NO];
1004 void RenderWidgetHostViewMac::Hide() {
1005 // We're messing with the window, so do this to ensure no flashes.
1006 if (!use_core_animation_)
1007 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1009 [cocoa_view_ setHidden:YES];
1014 bool RenderWidgetHostViewMac::IsShowing() {
1015 return ![cocoa_view_ isHidden];
1018 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
1019 NSRect bounds = [cocoa_view_ bounds];
1020 // TODO(shess): In case of !window, the view has been removed from
1021 // the view hierarchy because the tab isn't main. Could retrieve
1022 // the information from the main tab for our window.
1023 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1024 if (!enclosing_window)
1025 return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
1027 bounds = [cocoa_view_ convertRect:bounds toView:nil];
1028 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
1029 return FlipNSRectToRectScreen(bounds);
1032 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
1033 WebCursor web_cursor = cursor;
1034 [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
1037 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
1038 is_loading_ = is_loading;
1039 // If we ever decide to show the waiting cursor while the page is loading
1040 // like Chrome does on Windows, call |UpdateCursor()| here.
1043 void RenderWidgetHostViewMac::TextInputTypeChanged(
1044 ui::TextInputType type,
1045 ui::TextInputMode input_mode,
1046 bool can_compose_inline) {
1047 if (text_input_type_ != type
1048 || can_compose_inline_ != can_compose_inline) {
1049 text_input_type_ = type;
1050 can_compose_inline_ = can_compose_inline;
1052 SetTextInputActive(true);
1054 // Let AppKit cache the new input context to make IMEs happy.
1055 // See http://crbug.com/73039.
1056 [NSApp updateWindows];
1059 UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
1065 void RenderWidgetHostViewMac::ImeCancelComposition() {
1066 [cocoa_view_ cancelComposition];
1069 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
1070 const gfx::Range& range,
1071 const std::vector<gfx::Rect>& character_bounds) {
1072 // The RangeChanged message is only sent with valid values. The current
1073 // caret position (start == end) will be sent if there is no IME range.
1074 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1075 composition_range_ = range;
1076 composition_bounds_ = character_bounds;
1079 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
1084 void RenderWidgetHostViewMac::Destroy() {
1085 [[NSNotificationCenter defaultCenter]
1086 removeObserver:cocoa_view_
1087 name:NSWindowWillCloseNotification
1088 object:popup_window_];
1090 // We've been told to destroy.
1091 [cocoa_view_ retain];
1092 [cocoa_view_ removeFromSuperview];
1093 [cocoa_view_ autorelease];
1095 [popup_window_ close];
1096 popup_window_.autorelease();
1098 [fullscreen_window_manager_ exitFullscreenMode];
1099 fullscreen_window_manager_.reset();
1100 [pepper_fullscreen_window_ close];
1102 // This can be called as part of processing the window's responder
1103 // chain, for instance |-performKeyEquivalent:|. In that case the
1104 // object needs to survive until the stack unwinds.
1105 pepper_fullscreen_window_.autorelease();
1107 // We get this call just before |render_widget_host_| deletes
1108 // itself. But we are owned by |cocoa_view_|, which may be retained
1109 // by some other code. Examples are WebContentsViewMac's
1110 // |latent_focus_view_| and TabWindowController's
1111 // |cachedContentView_|.
1112 render_widget_host_ = NULL;
1115 // Called from the renderer to tell us what the tooltip text should be. It
1116 // calls us frequently so we need to cache the value to prevent doing a lot
1118 void RenderWidgetHostViewMac::SetTooltipText(
1119 const base::string16& tooltip_text) {
1120 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1121 tooltip_text_ = tooltip_text;
1123 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1124 // Windows; we're just trying to be polite. Don't persist the trimmed
1125 // string, as then the comparison above will always fail and we'll try to
1126 // set it again every single time the mouse moves.
1127 base::string16 display_text = tooltip_text_;
1128 if (tooltip_text_.length() > kMaxTooltipLength)
1129 display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1131 NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1132 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1136 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1137 return [NSApp respondsToSelector:@selector(speakString:)] &&
1138 [NSApp respondsToSelector:@selector(stopSpeaking:)];
1141 void RenderWidgetHostViewMac::SpeakSelection() {
1142 if ([NSApp respondsToSelector:@selector(speakString:)])
1143 [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
1146 bool RenderWidgetHostViewMac::IsSpeaking() const {
1147 return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1151 void RenderWidgetHostViewMac::StopSpeaking() {
1152 if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1153 [NSApp stopSpeaking:cocoa_view_];
1157 // RenderWidgetHostViewCocoa uses the stored selection text,
1158 // which implements NSServicesRequests protocol.
1160 void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text,
1162 const gfx::Range& range) {
1163 if (range.is_empty() || text.empty()) {
1164 selected_text_.clear();
1166 size_t pos = range.GetMin() - offset;
1167 size_t n = range.length();
1169 DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1170 if (pos >= text.length()) {
1171 DCHECK(false) << "The text can not cover range.";
1174 selected_text_ = base::UTF16ToUTF8(text.substr(pos, n));
1177 [cocoa_view_ setSelectedRange:range.ToNSRange()];
1178 // Updates markedRange when there is no marked text so that retrieving
1179 // markedRange immediately after calling setMarkdText: returns the current
1181 if (![cocoa_view_ hasMarkedText]) {
1182 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1185 RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1188 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1189 const ViewHostMsg_SelectionBounds_Params& params) {
1190 if (params.anchor_rect == params.focus_rect)
1191 caret_rect_ = params.anchor_rect;
1194 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1197 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1198 RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1200 // Create a fake mouse event to inform the render widget that the mouse
1202 NSWindow* window = [cocoa_view_ window];
1203 // TODO(asvitkine): If the location outside of the event stream doesn't
1204 // correspond to the current event (due to delayed event processing), then
1205 // this may result in a cursor flicker if there are later mouse move events
1206 // in the pipeline. Find a way to use the mouse location from the event that
1207 // dismissed the context menu.
1208 NSPoint location = [window mouseLocationOutsideOfEventStream];
1209 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1213 windowNumber:window_number()
1218 WebMouseEvent web_event =
1219 WebInputEventFactory::mouseEvent(event, cocoa_view_);
1221 web_event.type = WebInputEvent::MouseLeave;
1222 ForwardMouseEvent(web_event);
1225 bool RenderWidgetHostViewMac::IsPopup() const {
1226 return popup_type_ != blink::WebPopupTypeNone;
1229 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1230 const gfx::Rect& src_subrect,
1231 const gfx::Size& dst_size,
1232 const base::Callback<void(bool, const SkBitmap&)>& callback,
1233 const SkBitmap::Config config) {
1234 if (delegated_frame_host_) {
1235 delegated_frame_host_->CopyFromCompositingSurface(
1236 src_subrect, dst_size, callback, config);
1240 if (config != SkBitmap::kARGB_8888_Config) {
1242 callback.Run(false, SkBitmap());
1244 base::ScopedClosureRunner scoped_callback_runner(
1245 base::Bind(callback, false, SkBitmap()));
1246 float scale = ui::GetScaleFactorForNativeView(cocoa_view_);
1247 gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1248 gfx::ScaleSize(dst_size, scale));
1249 if (compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) {
1250 ignore_result(scoped_callback_runner.Release());
1251 compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1254 } else if (software_frame_manager_->HasCurrentFrame()) {
1255 gfx::Rect src_pixel_rect = gfx::ToEnclosingRect(gfx::ScaleRect(
1257 software_frame_manager_->GetCurrentFrameDeviceScaleFactor()));
1258 SkBitmap source_bitmap;
1259 source_bitmap.setConfig(
1260 SkBitmap::kARGB_8888_Config,
1261 software_frame_manager_->GetCurrentFrameSizeInPixels().width(),
1262 software_frame_manager_->GetCurrentFrameSizeInPixels().height(),
1264 kOpaque_SkAlphaType);
1265 source_bitmap.setPixels(software_frame_manager_->GetCurrentFramePixels());
1267 SkBitmap target_bitmap;
1268 target_bitmap.setConfig(
1269 SkBitmap::kARGB_8888_Config,
1270 dst_pixel_size.width(),
1271 dst_pixel_size.height(),
1273 kOpaque_SkAlphaType);
1274 if (!target_bitmap.allocPixels())
1277 SkCanvas target_canvas(target_bitmap);
1278 SkRect src_pixel_skrect = SkRect::MakeXYWH(
1279 src_pixel_rect.x(), src_pixel_rect.y(),
1280 src_pixel_rect.width(), src_pixel_rect.height());
1281 target_canvas.drawBitmapRectToRect(
1284 SkRect::MakeXYWH(0, 0, dst_pixel_size.width(), dst_pixel_size.height()),
1286 SkCanvas::kNone_DrawBitmapRectFlag);
1288 ignore_result(scoped_callback_runner.Release());
1289 callback.Run(true, target_bitmap);
1291 callback.Run(false, SkBitmap());
1295 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1296 const gfx::Rect& src_subrect,
1297 const scoped_refptr<media::VideoFrame>& target,
1298 const base::Callback<void(bool)>& callback) {
1299 if (delegated_frame_host_) {
1300 delegated_frame_host_->CopyFromCompositingSurfaceToVideoFrame(
1301 src_subrect, target, callback);
1305 base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1306 if (!compositing_iosurface_ || !compositing_iosurface_->HasIOSurface())
1309 if (!target.get()) {
1314 if (target->format() != media::VideoFrame::YV12 &&
1315 target->format() != media::VideoFrame::I420) {
1320 if (src_subrect.IsEmpty())
1323 ignore_result(scoped_callback_runner.Release());
1324 compositing_iosurface_->CopyToVideoFrame(
1325 GetScaledOpenGLPixelRect(src_subrect),
1330 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1331 if (delegated_frame_host_)
1332 return delegated_frame_host_->CanCopyToVideoFrame();
1334 return (!software_frame_manager_->HasCurrentFrame() &&
1335 compositing_iosurface_ &&
1336 compositing_iosurface_->HasIOSurface());
1339 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1340 if (delegated_frame_host_)
1341 return delegated_frame_host_->CanSubscribeFrame();
1343 return !software_frame_manager_->HasCurrentFrame();
1346 void RenderWidgetHostViewMac::BeginFrameSubscription(
1347 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1348 if (delegated_frame_host_) {
1349 delegated_frame_host_->BeginFrameSubscription(subscriber.Pass());
1352 frame_subscriber_ = subscriber.Pass();
1355 void RenderWidgetHostViewMac::EndFrameSubscription() {
1356 if (delegated_frame_host_) {
1357 delegated_frame_host_->EndFrameSubscription();
1361 frame_subscriber_.reset();
1364 // Sets whether or not to accept first responder status.
1365 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1366 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1369 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1370 if (render_widget_host_)
1371 render_widget_host_->ForwardMouseEvent(event);
1373 if (event.type == WebInputEvent::MouseLeave) {
1374 [cocoa_view_ setToolTipAtMousePoint:nil];
1375 tooltip_text_.clear();
1379 void RenderWidgetHostViewMac::KillSelf() {
1380 if (!weak_factory_.HasWeakPtrs()) {
1381 [cocoa_view_ setHidden:YES];
1382 base::MessageLoop::current()->PostTask(FROM_HERE,
1383 base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1384 weak_factory_.GetWeakPtr()));
1388 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1389 const NativeWebKeyboardEvent& event) {
1390 // Check WebInputEvent type since multiple types of events can be sent into
1391 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1392 // necessary to avoid double processing.
1393 // Also check the native type, since NSFlagsChanged is considered a key event
1394 // for WebKit purposes, but isn't considered a key event by the OS.
1395 if (event.type == WebInputEvent::RawKeyDown &&
1396 [event.os_event type] == NSKeyDown)
1397 return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1401 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1402 const base::string16& text, int plugin_id) {
1403 if (render_widget_host_) {
1404 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1405 render_widget_host_->GetRoutingID(), text, plugin_id));
1409 void RenderWidgetHostViewMac::CompositorSwapBuffers(
1410 uint64 surface_handle,
1411 const gfx::Size& size,
1412 float surface_scale_factor,
1413 const std::vector<ui::LatencyInfo>& latency_info) {
1414 // Ensure that the frame be acked unless it is explicitly passed to a
1415 // display function.
1416 base::ScopedClosureRunner scoped_ack(
1417 base::Bind(&RenderWidgetHostViewMac::SendPendingSwapAck,
1418 weak_factory_.GetWeakPtr()));
1420 if (render_widget_host_->is_hidden())
1423 // Ensure that if this function exits before the frame is set up (but not
1424 // necessarily drawn) then it is treated as an error.
1425 base::ScopedClosureRunner scoped_error(
1426 base::Bind(&RenderWidgetHostViewMac::GotAcceleratedCompositingError,
1427 weak_factory_.GetWeakPtr()));
1429 AddPendingLatencyInfo(latency_info);
1431 // If compositing_iosurface_ exists and has been poisoned, destroy it
1432 // and allow EnsureCompositedIOSurface to recreate it below. Keep a
1433 // reference to the destroyed layer around until after the below call
1434 // to LayoutLayers, to avoid flickers.
1435 base::ScopedClosureRunner scoped_layer_remover;
1436 if (compositing_iosurface_context_ &&
1437 compositing_iosurface_context_->HasBeenPoisoned()) {
1438 scoped_layer_remover.Reset(
1439 base::Bind(RemoveLayerFromSuperlayer, compositing_iosurface_layer_));
1440 DestroyCompositedIOSurfaceLayer(kLeaveLayerInHierarchy);
1441 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1444 // Ensure compositing_iosurface_ and compositing_iosurface_context_ be
1446 if (!EnsureCompositedIOSurface()) {
1447 LOG(ERROR) << "Failed EnsureCompositingIOSurface";
1451 // Make the context current and update the IOSurface with the handle
1452 // passed in by the swap command.
1454 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1455 compositing_iosurface_context_->cgl_context());
1456 if (!compositing_iosurface_->SetIOSurfaceWithContextCurrent(
1457 compositing_iosurface_context_, surface_handle, size,
1458 surface_scale_factor)) {
1459 LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1464 // Grab video frames now that the IOSurface has been set up. Note that this
1465 // will be done in an offscreen context, so it is necessary to re-set the
1466 // current context afterward.
1467 bool frame_was_captured = false;
1468 if (frame_subscriber_) {
1469 const base::TimeTicks present_time = base::TimeTicks::Now();
1470 scoped_refptr<media::VideoFrame> frame;
1471 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1472 if (frame_subscriber_->ShouldCaptureFrame(present_time,
1473 &frame, &callback)) {
1474 // Flush the context that updated the IOSurface, to ensure that the
1475 // context that does the copy picks up the correct version.
1477 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1478 compositing_iosurface_context_->cgl_context());
1481 compositing_iosurface_->CopyToVideoFrame(
1482 gfx::Rect(size), frame,
1483 base::Bind(callback, present_time));
1484 frame_was_captured = true;
1488 // At this point the surface, its context, and its layer have been set up, so
1489 // don't generate an error (one may be generated when drawing).
1490 ignore_result(scoped_error.Release());
1492 GotAcceleratedFrame();
1494 gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1495 if (window_size.IsEmpty()) {
1496 // setNeedsDisplay will never display and we'll never ack if the window is
1497 // empty, so ack now and don't bother calling setNeedsDisplay below.
1500 if (window_number() <= 0) {
1501 // It's normal for a backgrounded tab that is being captured to have no
1502 // window but not be hidden. Immediately ack the frame, and don't try to
1504 if (frame_was_captured)
1507 // If this frame was not captured, there is likely some sort of bug. Ack
1508 // the frame and hope for the best. Because the IOSurface and layer are
1509 // populated, it will likely be displayed when the view is added to a
1510 // window's hierarchy.
1512 // TODO(shess) If the view does not have a window, or the window
1513 // does not have backing, the IOSurface will log "invalid drawable"
1514 // in -setView:. It is not clear how this code is reached with such
1515 // a case, so record some info into breakpad (some subset of
1516 // browsers are likely to crash later for unrelated reasons).
1517 // http://crbug.com/148882
1518 const char* const kCrashKey = "rwhvm_window";
1519 NSWindow* window = [cocoa_view_ window];
1521 base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1524 base::StringPrintf("window %s delegate %s controller %s",
1525 object_getClassName(window),
1526 object_getClassName([window delegate]),
1527 object_getClassName([window windowController]));
1528 base::debug::SetCrashKeyValue(kCrashKey, value);
1533 // If the window is occluded, then this frame's display call may be severely
1534 // throttled. This is a good thing, unless tab capture may be active,
1535 // because the broadcast will be inappropriately throttled.
1536 // http://crbug.com/350410
1537 NSWindow* window = [cocoa_view_ window];
1538 if (window && [window respondsToSelector:@selector(occlusionState)]) {
1539 bool window_is_occluded =
1540 !([window occlusionState] & NSWindowOcclusionStateVisible);
1541 // Note that we aggressively ack even if this particular frame is not being
1543 if (window_is_occluded && frame_subscriber_)
1547 // If we reach here, then the frame will be displayed by a future draw
1548 // call, so don't make the callback.
1549 ignore_result(scoped_ack.Release());
1550 if (use_core_animation_) {
1551 DCHECK(compositing_iosurface_layer_);
1552 [compositing_iosurface_layer_ gotNewFrame];
1554 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1555 compositing_iosurface_context_->cgl_context());
1556 DrawIOSurfaceWithoutCoreAnimation();
1559 // Try to finish previous copy requests after draw to get better pipelining.
1560 if (compositing_iosurface_)
1561 compositing_iosurface_->CheckIfAllCopiesAreFinished(false);
1563 // The IOSurface's size may have changed, so re-layout the layers to take
1564 // this into account. This may force an immediate draw.
1568 void RenderWidgetHostViewMac::DrawIOSurfaceWithoutCoreAnimation() {
1569 CHECK(!use_core_animation_);
1570 CHECK(compositing_iosurface_);
1572 // If there is a pending frame, it should be acked by the end of this
1573 // function. Note that the ack should happen only after all drawing is
1574 // complete, so that the ack happens after any blocking due to vsync.
1575 base::ScopedClosureRunner scoped_ack(
1576 base::Bind(&RenderWidgetHostViewMac::SendPendingSwapAck,
1577 weak_factory_.GetWeakPtr()));
1579 GLint old_gl_surface_order = 0;
1580 GLint new_gl_surface_order = allow_overlapping_views_ ? -1 : 1;
1581 [compositing_iosurface_context_->nsgl_context()
1582 getValues:&old_gl_surface_order
1583 forParameter:NSOpenGLCPSurfaceOrder];
1584 if (old_gl_surface_order != new_gl_surface_order) {
1585 [compositing_iosurface_context_->nsgl_context()
1586 setValues:&new_gl_surface_order
1587 forParameter:NSOpenGLCPSurfaceOrder];
1590 // Instead of drawing, request that underlay view redraws.
1591 if (underlay_view_ &&
1592 underlay_view_->compositing_iosurface_ &&
1593 underlay_view_has_drawn_) {
1594 [underlay_view_->cocoa_view() setNeedsDisplayInRect:NSMakeRect(0, 0, 1, 1)];
1598 bool has_overlay = overlay_view_ && overlay_view_->compositing_iosurface_;
1600 // Un-bind the overlay view's OpenGL context, since its content will be
1601 // drawn by this context. Not doing this can result in corruption.
1602 // http://crbug.com/330701
1603 overlay_view_->ClearBoundContextDrawable();
1605 [compositing_iosurface_context_->nsgl_context() setView:cocoa_view_];
1607 gfx::Rect view_rect(NSRectToCGRect([cocoa_view_ frame]));
1608 if (!compositing_iosurface_->DrawIOSurface(
1609 compositing_iosurface_context_, view_rect,
1610 ViewScaleFactor(), !has_overlay)) {
1611 GotAcceleratedCompositingError();
1616 overlay_view_->underlay_view_has_drawn_ = true;
1617 gfx::Rect overlay_view_rect(
1618 NSRectToCGRect([overlay_view_->cocoa_view() frame]));
1619 overlay_view_rect.set_x(overlay_view_offset_.x());
1620 overlay_view_rect.set_y(view_rect.height() -
1621 overlay_view_rect.height() -
1622 overlay_view_offset_.y());
1623 if (!overlay_view_->compositing_iosurface_->DrawIOSurface(
1624 compositing_iosurface_context_, overlay_view_rect,
1625 overlay_view_->ViewScaleFactor(), true)) {
1626 GotAcceleratedCompositingError();
1631 SendPendingLatencyInfoToHost();
1634 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1635 LOG(ERROR) << "Encountered accelerated compositing error";
1636 base::MessageLoop::current()->PostTask(
1638 base::Bind(&RenderWidgetHostViewMac::DestroyCompositingStateOnError,
1639 weak_factory_.GetWeakPtr()));
1642 void RenderWidgetHostViewMac::DestroyCompositingStateOnError() {
1643 // This should be called with a clean stack. Make sure that no context is
1645 DCHECK(!CGLGetCurrentContext());
1647 // The existing GL contexts may be in a bad state, so don't re-use any of the
1648 // existing ones anymore, rather, allocate new ones.
1649 if (compositing_iosurface_context_)
1650 compositing_iosurface_context_->PoisonContextAndSharegroup();
1652 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1654 // Request that a new frame be generated and dirty the view.
1655 if (render_widget_host_)
1656 render_widget_host_->ScheduleComposite();
1657 [cocoa_view_ setNeedsDisplay:YES];
1659 // Mark the last frame as not accelerated (so that the window is prepared for
1660 // an underlay next time an accelerated frame comes in).
1661 last_frame_was_accelerated_ = false;
1663 // TODO(ccameron): It may be a good idea to request that the renderer recreate
1664 // its GL context as well, and fall back to software if this happens
1668 void RenderWidgetHostViewMac::SetOverlayView(
1669 RenderWidgetHostViewMac* overlay, const gfx::Point& offset) {
1671 overlay_view_->underlay_view_.reset();
1673 overlay_view_ = overlay->overlay_view_weak_factory_.GetWeakPtr();
1674 overlay_view_->underlay_view_ = overlay_view_weak_factory_.GetWeakPtr();
1675 if (use_core_animation_)
1678 overlay_view_offset_ = offset;
1679 overlay_view_->underlay_view_has_drawn_ = false;
1681 [cocoa_view_ setNeedsDisplay:YES];
1682 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1685 void RenderWidgetHostViewMac::RemoveOverlayView() {
1686 if (overlay_view_) {
1687 overlay_view_->underlay_view_.reset();
1688 overlay_view_.reset();
1690 if (use_core_animation_)
1693 [cocoa_view_ setNeedsDisplay:YES];
1694 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1697 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1698 const std::vector<gfx::Rect>& bounds,
1699 const gfx::Range& range,
1700 size_t* line_break_point) {
1701 DCHECK(line_break_point);
1702 if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1705 // We can't check line breaking completely from only rectangle array. Thus we
1706 // assume the line breaking as the next character's y offset is larger than
1707 // a threshold. Currently the threshold is determined as minimum y offset plus
1708 // 75% of maximum height.
1709 // TODO(nona): Check the threshold is reliable or not.
1710 // TODO(nona): Bidi support.
1711 const size_t loop_end_idx = std::min(bounds.size(), range.end());
1713 int min_y_offset = kint32max;
1714 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1715 max_height = std::max(max_height, bounds[idx].height());
1716 min_y_offset = std::min(min_y_offset, bounds[idx].y());
1718 int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1719 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1720 if (bounds[idx].y() > line_break_threshold) {
1721 *line_break_point = idx;
1728 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1729 const gfx::Range& range,
1730 gfx::Range* actual_range) {
1731 DCHECK(actual_range);
1732 DCHECK(!composition_bounds_.empty());
1733 DCHECK(range.start() <= composition_bounds_.size());
1734 DCHECK(range.end() <= composition_bounds_.size());
1736 if (range.is_empty()) {
1737 *actual_range = range;
1738 if (range.start() == composition_bounds_.size()) {
1739 return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1740 composition_bounds_[range.start() - 1].y(),
1742 composition_bounds_[range.start() - 1].height());
1744 return gfx::Rect(composition_bounds_[range.start()].x(),
1745 composition_bounds_[range.start()].y(),
1747 composition_bounds_[range.start()].height());
1752 if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1753 end_idx = range.end();
1755 *actual_range = gfx::Range(range.start(), end_idx);
1756 gfx::Rect rect = composition_bounds_[range.start()];
1757 for (size_t i = range.start() + 1; i < end_idx; ++i) {
1758 rect.Union(composition_bounds_[i]);
1763 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1764 const gfx::Range& request_range) {
1765 if (composition_range_.is_empty())
1766 return gfx::Range::InvalidRange();
1768 if (request_range.is_reversed())
1769 return gfx::Range::InvalidRange();
1771 if (request_range.start() < composition_range_.start() ||
1772 request_range.start() > composition_range_.end() ||
1773 request_range.end() > composition_range_.end()) {
1774 return gfx::Range::InvalidRange();
1778 request_range.start() - composition_range_.start(),
1779 request_range.end() - composition_range_.start());
1782 WebContents* RenderWidgetHostViewMac::GetWebContents() {
1783 if (!render_widget_host_->IsRenderView())
1786 return WebContents::FromRenderViewHost(
1787 RenderViewHost::From(render_widget_host_));
1790 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1793 NSRange* actual_range) {
1795 // This exists to make IMEs more responsive, see http://crbug.com/115920
1796 TRACE_EVENT0("browser",
1797 "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1799 // If requested range is same as caret location, we can just return it.
1800 if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1802 *actual_range = range;
1803 *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1807 const gfx::Range request_range_in_composition =
1808 ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1809 if (request_range_in_composition == gfx::Range::InvalidRange())
1812 // If firstRectForCharacterRange in WebFrame is failed in renderer,
1813 // ImeCompositionRangeChanged will be sent with empty vector.
1814 if (composition_bounds_.empty())
1816 DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1818 gfx::Range ui_actual_range;
1819 *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1820 request_range_in_composition,
1821 &ui_actual_range).ToCGRect());
1823 *actual_range = gfx::Range(
1824 composition_range_.start() + ui_actual_range.start(),
1825 composition_range_.start() + ui_actual_range.end()).ToNSRange();
1830 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1831 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1833 TRACE_EVENT0("browser",
1834 "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1835 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1837 AddPendingSwapAck(params.route_id,
1839 compositing_iosurface_ ?
1840 compositing_iosurface_->GetRendererID() : 0);
1841 CompositorSwapBuffers(params.surface_handle,
1843 params.scale_factor,
1844 params.latency_info);
1847 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1848 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1850 TRACE_EVENT0("browser",
1851 "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1852 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1854 AddPendingSwapAck(params.route_id,
1856 compositing_iosurface_ ?
1857 compositing_iosurface_->GetRendererID() : 0);
1858 CompositorSwapBuffers(params.surface_handle,
1859 params.surface_size,
1860 params.surface_scale_factor,
1861 params.latency_info);
1864 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1865 if (compositing_iosurface_)
1866 compositing_iosurface_->UnrefIOSurface();
1869 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1870 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1873 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1874 const gfx::Size& desired_size) {
1875 if (last_frame_was_accelerated_) {
1876 return compositing_iosurface_ &&
1877 compositing_iosurface_->HasIOSurface() &&
1878 (desired_size.IsEmpty() ||
1879 compositing_iosurface_->dip_io_surface_size() == desired_size);
1881 return (software_frame_manager_->HasCurrentFrame() &&
1882 (desired_size.IsEmpty() ||
1883 software_frame_manager_->GetCurrentFrameSizeInDIP() ==
1889 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1890 uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1891 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::OnSwapCompositorFrame");
1893 if (frame->delegated_frame_data) {
1894 if (!browser_compositor_view_) {
1895 browser_compositor_view_.reset(
1896 [[BrowserCompositorViewMac alloc] initWithSuperview:cocoa_view_]);
1897 root_layer_.reset(new ui::Layer(ui::LAYER_TEXTURED));
1898 delegated_frame_host_.reset(new DelegatedFrameHost(this));
1901 // TODO(ccameron): Having the root layer set while swapping the frame will
1902 // result in frames not appearing. Fix this.
1903 [browser_compositor_view_ compositor]->SetRootLayer(NULL);
1904 delegated_frame_host_->SwapDelegatedFrame(
1906 frame->delegated_frame_data.Pass(),
1907 frame->metadata.device_scale_factor,
1908 frame->metadata.latency_info);
1909 [browser_compositor_view_ compositor]->SetRootLayer(root_layer_.get());
1911 // Update the compositor and root layer size and scale factor to match
1912 // the frame just received.
1913 float scale_factor = frame->metadata.device_scale_factor;
1914 gfx::Size dip_size = ToCeiledSize(frame->metadata.viewport_size);
1915 gfx::Size pixel_size = ConvertSizeToPixel(
1916 scale_factor, dip_size);
1917 [browser_compositor_view_ compositor]->SetScaleAndSize(
1918 scale_factor, pixel_size);
1919 root_layer_->SetBounds(gfx::Rect(dip_size));
1920 } else if (frame->software_frame_data) {
1921 if (!software_frame_manager_->SwapToNewFrame(
1923 frame->software_frame_data.get(),
1924 frame->metadata.device_scale_factor,
1925 render_widget_host_->GetProcess()->GetHandle())) {
1926 render_widget_host_->GetProcess()->ReceivedBadMessage();
1930 // Add latency info to report when the frame finishes drawing.
1931 AddPendingLatencyInfo(frame->metadata.latency_info);
1933 if (use_core_animation_) {
1934 const void* pixels = software_frame_manager_->GetCurrentFramePixels();
1935 gfx::Size size_in_pixels =
1936 software_frame_manager_->GetCurrentFrameSizeInPixels();
1938 EnsureSoftwareLayer();
1939 [software_layer_ setContentsToData:pixels
1940 withRowBytes:4 * size_in_pixels.width()
1941 withPixelSize:size_in_pixels
1942 withScaleFactor:frame->metadata.device_scale_factor];
1944 // Send latency information to the host immediately, as there will be no
1945 // subsequent draw call in which to do so.
1946 SendPendingLatencyInfoToHost();
1951 cc::CompositorFrameAck ack;
1952 RenderWidgetHostImpl::SendSwapCompositorFrameAck(
1953 render_widget_host_->GetRoutingID(),
1954 software_frame_manager_->GetCurrentFrameOutputSurfaceId(),
1955 render_widget_host_->GetProcess()->GetID(),
1957 software_frame_manager_->SwapToNewFrameComplete(
1958 !render_widget_host_->is_hidden());
1960 // Notify observers, tab capture observers in particular, that a new
1961 // software frame has come in.
1962 NotificationService::current()->Notify(
1963 NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
1964 Source<RenderWidgetHost>(render_widget_host_),
1965 NotificationService::NoDetails());
1967 DLOG(ERROR) << "Received unexpected frame type.";
1969 base::UserMetricsAction("BadMessageTerminate_UnexpectedFrameType"));
1970 render_widget_host_->GetProcess()->ReceivedBadMessage();
1974 void RenderWidgetHostViewMac::AcceleratedSurfaceInitialized(int host_id,
1978 void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) {
1979 *results = GetWebScreenInfo(GetNativeView());
1982 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1983 // TODO(shess): In case of !window, the view has been removed from
1984 // the view hierarchy because the tab isn't main. Could retrieve
1985 // the information from the main tab for our window.
1986 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1987 if (!enclosing_window)
1990 NSRect bounds = [enclosing_window frame];
1991 return FlipNSRectToRectScreen(bounds);
1994 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1995 // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1996 // completely on Mac OS.
1997 return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
2000 void RenderWidgetHostViewMac::SetScrollOffsetPinning(
2001 bool is_pinned_to_left, bool is_pinned_to_right) {
2002 [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
2003 toRight:is_pinned_to_right];
2006 bool RenderWidgetHostViewMac::LockMouse() {
2010 mouse_locked_ = true;
2012 // Lock position of mouse cursor and hide it.
2013 CGAssociateMouseAndMouseCursorPosition(NO);
2016 // Clear the tooltip window.
2017 SetTooltipText(base::string16());
2022 void RenderWidgetHostViewMac::UnlockMouse() {
2025 mouse_locked_ = false;
2027 // Unlock position of mouse cursor and unhide it.
2028 CGAssociateMouseAndMouseCursorPosition(YES);
2031 if (render_widget_host_)
2032 render_widget_host_->LostMouseLock();
2035 void RenderWidgetHostViewMac::UnhandledWheelEvent(
2036 const blink::WebMouseWheelEvent& event) {
2037 // Only record a wheel event as unhandled if JavaScript handlers got a chance
2038 // to see it (no-op wheel events are ignored by the event dispatcher)
2039 if (event.deltaX || event.deltaY)
2040 [cocoa_view_ gotUnhandledWheelEvent];
2043 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
2044 if (render_widget_host_)
2045 return render_widget_host_->Send(message);
2050 void RenderWidgetHostViewMac::SoftwareFrameWasFreed(
2051 uint32 output_surface_id, unsigned frame_id) {
2052 if (!render_widget_host_)
2054 cc::CompositorFrameAck ack;
2055 ack.last_software_frame_id = frame_id;
2056 RenderWidgetHostImpl::SendReclaimCompositorResources(
2057 render_widget_host_->GetRoutingID(),
2059 render_widget_host_->GetProcess()->GetID(),
2063 void RenderWidgetHostViewMac::ReleaseReferencesToSoftwareFrame() {
2064 DestroySoftwareLayer();
2067 void RenderWidgetHostViewMac::ShutdownHost() {
2068 weak_factory_.InvalidateWeakPtrs();
2069 render_widget_host_->Shutdown();
2070 // Do not touch any members at this point, |this| has been deleted.
2073 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
2074 EnsureCompositedIOSurfaceLayer();
2075 SendVSyncParametersToRenderer();
2076 if (!last_frame_was_accelerated_) {
2077 last_frame_was_accelerated_ = true;
2079 if (!use_core_animation_) {
2080 // Need to wipe the software view with transparency to expose the GL
2081 // underlay. Invalidate the whole window to do that.
2082 [cocoa_view_ setNeedsDisplay:YES];
2085 // Delete software backingstore and layer.
2086 software_frame_manager_->DiscardCurrentFrame();
2087 DestroySoftwareLayer();
2091 void RenderWidgetHostViewMac::GotSoftwareFrame() {
2092 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::GotSoftwareFrame");
2094 if (!render_widget_host_)
2097 EnsureSoftwareLayer();
2099 SendVSyncParametersToRenderer();
2101 // Draw the contents of the frame immediately. It is critical that this
2102 // happen before the frame be acked, otherwise the new frame will likely be
2103 // ready before the drawing is complete, thrashing the browser main thread.
2104 if (use_core_animation_) {
2105 [software_layer_ displayIfNeeded];
2107 [cocoa_view_ setNeedsDisplay:YES];
2108 [cocoa_view_ displayIfNeeded];
2111 if (last_frame_was_accelerated_) {
2112 last_frame_was_accelerated_ = false;
2114 // If overlapping views are allowed, then don't unbind the context
2115 // from the view (that is, don't call clearDrawble -- just delete the
2116 // texture and IOSurface). Rather, let it sit behind the software frame
2117 // that will be put up in front. This will prevent transparent
2119 // http://crbug.com/154531
2120 // Also note that it is necessary that clearDrawable be called if
2121 // overlapping views are not allowed, e.g, for content shell.
2122 // http://crbug.com/178408
2123 // Disable screen updates so that the changes of flashes is minimized.
2124 // http://crbug.com/279472
2125 if (!use_core_animation_)
2126 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
2127 if (allow_overlapping_views_)
2128 DestroyCompositedIOSurfaceAndLayer(kLeaveContextBoundToView);
2130 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
2134 void RenderWidgetHostViewMac::SetActive(bool active) {
2135 if (render_widget_host_) {
2136 render_widget_host_->SetActive(active);
2139 render_widget_host_->Focus();
2141 render_widget_host_->Blur();
2145 SetTextInputActive(active);
2147 [cocoa_view_ setPluginImeActive:NO];
2152 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
2153 if (render_widget_host_) {
2154 render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
2155 render_widget_host_->GetRoutingID(), visible));
2159 void RenderWidgetHostViewMac::WindowFrameChanged() {
2160 if (render_widget_host_) {
2161 render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
2162 render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
2166 if (compositing_iosurface_ && !use_core_animation_) {
2167 // This will migrate the context to the appropriate window.
2168 if (!EnsureCompositedIOSurface())
2169 GotAcceleratedCompositingError();
2173 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
2174 RenderWidgetHostViewMacDictionaryHelper helper(this);
2175 helper.ShowDefinitionForSelection();
2178 void RenderWidgetHostViewMac::SetBackgroundOpaque(bool opaque) {
2179 RenderWidgetHostViewBase::SetBackgroundOpaque(opaque);
2180 if (render_widget_host_)
2181 render_widget_host_->SetBackgroundOpaque(opaque);
2184 void RenderWidgetHostViewMac::CreateBrowserAccessibilityManagerIfNeeded() {
2185 if (!GetBrowserAccessibilityManager()) {
2186 SetBrowserAccessibilityManager(
2187 new BrowserAccessibilityManagerMac(
2189 BrowserAccessibilityManagerMac::GetEmptyDocument(),
2190 render_widget_host_));
2194 gfx::Point RenderWidgetHostViewMac::AccessibilityOriginInScreen(
2195 const gfx::Rect& bounds) {
2196 NSPoint origin = NSMakePoint(bounds.x(), bounds.y());
2197 NSSize size = NSMakeSize(bounds.width(), bounds.height());
2198 origin.y = NSHeight([cocoa_view_ bounds]) - origin.y;
2199 NSPoint originInWindow = [cocoa_view_ convertPoint:origin toView:nil];
2200 NSPoint originInScreen =
2201 [[cocoa_view_ window] convertBaseToScreen:originInWindow];
2202 originInScreen.y = originInScreen.y - size.height;
2203 return gfx::Point(originInScreen.x, originInScreen.y);
2206 void RenderWidgetHostViewMac::OnAccessibilitySetFocus(int accObjId) {
2207 // Immediately set the focused item even though we have not officially set
2208 // focus on it as VoiceOver expects to get the focused item after this
2210 BrowserAccessibilityManager* manager = GetBrowserAccessibilityManager();
2212 manager->SetFocus(manager->GetFromID(accObjId), false);
2215 void RenderWidgetHostViewMac::AccessibilityShowMenu(int accObjId) {
2216 BrowserAccessibilityManager* manager = GetBrowserAccessibilityManager();
2219 BrowserAccessibilityCocoa* obj =
2220 manager->GetFromID(accObjId)->ToBrowserAccessibilityCocoa();
2222 // Performs a right click copying WebKit's
2223 // accessibilityPerformShowMenuAction.
2224 NSPoint objOrigin = [obj origin];
2225 NSSize size = [[obj size] sizeValue];
2226 gfx::Point origin = AccessibilityOriginInScreen(
2227 gfx::Rect(objOrigin.x, objOrigin.y, size.width, size.height));
2228 NSPoint location = NSMakePoint(origin.x(), origin.y());
2229 location = [[cocoa_view_ window] convertScreenToBase:location];
2230 location.x += size.width/2;
2231 location.y += size.height/2;
2233 NSEvent* fakeRightClick = [NSEvent
2234 mouseEventWithType:NSRightMouseDown
2238 windowNumber:[[cocoa_view_ window] windowNumber]
2239 context:[NSGraphicsContext currentContext]
2244 [cocoa_view_ mouseEvent:fakeRightClick];
2249 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
2251 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2252 EnablePasswordInput();
2254 DisablePasswordInput();
2256 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2257 DisablePasswordInput();
2261 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
2263 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
2266 void RenderWidgetHostViewMac::OnStartPluginIme() {
2267 [cocoa_view_ setPluginImeActive:YES];
2270 void RenderWidgetHostViewMac::OnDidChangeScrollbarsForMainFrame(
2271 bool has_horizontal_scrollbar, bool has_vertical_scrollbar) {
2272 [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
2275 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
2276 const gfx::Rect& rect) {
2277 gfx::Rect src_gl_subrect = rect;
2278 src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
2280 return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
2281 ViewScaleFactor()));
2284 void RenderWidgetHostViewMac::AddPendingLatencyInfo(
2285 const std::vector<ui::LatencyInfo>& latency_info) {
2286 // If a screenshot is being taken when using CoreAnimation, send a few extra
2287 // calls to setNeedsDisplay and wait for their resulting display calls,
2288 // before reporting that the frame has reached the screen.
2289 if (use_core_animation_) {
2290 bool should_defer = false;
2291 for (size_t i = 0; i < latency_info.size(); i++) {
2292 if (latency_info[i].FindLatency(
2293 ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT,
2294 render_widget_host_->GetLatencyComponentId(),
2296 should_defer = true;
2300 // Multiple pending screenshot requests will work, but if every frame
2301 // requests a screenshot, then the delay will never expire. Assert this
2302 // here to avoid this.
2303 CHECK_EQ(pending_latency_info_delay_, 0u);
2304 // Wait a fixed number of frames (calls to CALayer::display) before
2305 // claiming that the screenshot has reached the screen. This number
2306 // comes from taking the first number where tests didn't fail (six),
2308 const uint32 kScreenshotLatencyDelayInFrames = 12;
2309 pending_latency_info_delay_ = kScreenshotLatencyDelayInFrames;
2310 TickPendingLatencyInfoDelay();
2314 for (size_t i = 0; i < latency_info.size(); i++) {
2315 pending_latency_info_.push_back(latency_info[i]);
2319 void RenderWidgetHostViewMac::SendPendingLatencyInfoToHost() {
2320 if (pending_latency_info_delay_) {
2321 pending_latency_info_delay_ -= 1;
2324 pending_latency_info_delay_weak_ptr_factory_.InvalidateWeakPtrs();
2326 for (size_t i = 0; i < pending_latency_info_.size(); i++) {
2327 pending_latency_info_[i].AddLatencyNumber(
2328 ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
2329 render_widget_host_->FrameSwapped(pending_latency_info_[i]);
2331 pending_latency_info_.clear();
2334 void RenderWidgetHostViewMac::TickPendingLatencyInfoDelay() {
2335 if (compositing_iosurface_layer_) {
2336 // Keep calling gotNewFrame in a loop until enough display calls come in.
2337 // Each call will be separated by about a vsync.
2338 base::MessageLoop::current()->PostTask(
2340 base::Bind(&RenderWidgetHostViewMac::TickPendingLatencyInfoDelay,
2341 pending_latency_info_delay_weak_ptr_factory_.GetWeakPtr()));
2342 [compositing_iosurface_layer_ gotNewFrame];
2344 if (software_layer_) {
2345 // In software mode there is not an explicit setNeedsDisplay/display loop,
2346 // so just wait a pretend-vsync at 60 Hz.
2347 base::MessageLoop::current()->PostDelayedTask(
2349 base::Bind(&RenderWidgetHostViewMac::TickPendingLatencyInfoDelay,
2350 pending_latency_info_delay_weak_ptr_factory_.GetWeakPtr()),
2351 base::TimeDelta::FromMilliseconds(1000/60));
2352 SendPendingLatencyInfoToHost();
2356 void RenderWidgetHostViewMac::AddPendingSwapAck(
2357 int32 route_id, int gpu_host_id, int32 renderer_id) {
2358 // Note that multiple un-acked swaps can come in the event of a GPU process
2359 // loss. Drop the old acks.
2360 pending_swap_ack_.reset(new PendingSwapAck(
2361 route_id, gpu_host_id, renderer_id));
2363 // A trace value of 2 indicates that there is a pending swap ack. See
2364 // CompositingIOSurfaceLayer's canDrawInCGLContext for other value meanings.
2365 TRACE_COUNTER_ID1("browser", "PendingSwapAck",
2366 compositing_iosurface_layer_.get(), 2);
2369 void RenderWidgetHostViewMac::SendPendingSwapAck() {
2370 if (!pending_swap_ack_)
2373 AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
2374 ack_params.sync_point = 0;
2375 ack_params.renderer_id = pending_swap_ack_->renderer_id;
2376 RenderWidgetHostImpl::AcknowledgeBufferPresent(pending_swap_ack_->route_id,
2377 pending_swap_ack_->gpu_host_id,
2379 pending_swap_ack_.reset();
2380 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this, 0);
2383 void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() {
2384 if (!render_widget_host_ || render_widget_host_->is_hidden())
2387 // Pausing for the overlay/underlay view prevents the other one from receiving
2388 // frames. This may lead to large delays, causing overlaps.
2389 // See crbug.com/352020.
2390 if (underlay_view_ || overlay_view_)
2393 // Ensure that all frames are acked before waiting for a frame to come in.
2394 // Note that we will draw a frame at the end of this function, so it is safe
2395 // to ack a never-drawn frame here.
2396 SendPendingSwapAck();
2398 // Wait for a frame of the right size to come in.
2399 render_widget_host_->PauseForPendingResizeOrRepaints();
2401 // Immediately draw any frames that haven't been drawn yet. This is necessary
2402 // to keep the window and the window's contents in sync.
2403 [cocoa_view_ displayIfNeeded];
2404 [software_layer_ displayIfNeeded];
2405 [compositing_iosurface_layer_ displayIfNeeded];
2408 void RenderWidgetHostViewMac::LayoutLayers() {
2409 if (!use_core_animation_)
2412 if (browser_compositor_view_) {
2413 [browser_compositor_view_ layoutLayers];
2417 // Disable animation of the layer's resizing or change in contents scale.
2418 ScopedCAActionDisabler disabler;
2420 CGRect new_background_frame = NSRectToCGRect([cocoa_view() bounds]);
2422 // Dynamically calling setContentsScale on a CAOpenGLLayer for which
2423 // setAsynchronous is dynamically toggled can result in flashes of corrupt
2424 // content. Work around this by replacing the entire layer when the scale
2426 if (compositing_iosurface_ &&
2427 [compositing_iosurface_layer_
2428 respondsToSelector:(@selector(contentsScale))]) {
2429 if (compositing_iosurface_->scale_factor() !=
2430 [compositing_iosurface_layer_ contentsScale]) {
2431 DestroyCompositedIOSurfaceLayer(kRemoveLayerFromHierarchy);
2432 EnsureCompositedIOSurfaceLayer();
2435 if (compositing_iosurface_ &&
2436 compositing_iosurface_->HasIOSurface() &&
2437 compositing_iosurface_layer_) {
2438 CGRect layer_bounds = CGRectMake(
2441 compositing_iosurface_->dip_io_surface_size().width(),
2442 compositing_iosurface_->dip_io_surface_size().height());
2443 CGPoint layer_position = CGPointMake(
2445 CGRectGetHeight(new_background_frame) - CGRectGetHeight(layer_bounds));
2446 bool bounds_changed = !CGRectEqualToRect(
2447 layer_bounds, [compositing_iosurface_layer_ bounds]);
2448 [compositing_iosurface_layer_ setPosition:layer_position];
2449 [compositing_iosurface_layer_ setBounds:layer_bounds];
2451 // If the bounds changed, then draw the frame immediately, to ensure that
2452 // content displayed is in sync with the window size.
2453 if (bounds_changed) {
2454 // Also, sometimes, especially when infobars are being removed, the
2455 // setNeedsDisplay calls are dropped on the floor, and stale content is
2456 // displayed. Calling displayIfNeeded will ensure that the right size
2457 // frame is drawn to the screen.
2458 // http://crbug.com/350817
2459 [compositing_iosurface_layer_ setNeedsDisplay];
2460 [compositing_iosurface_layer_ displayIfNeeded];
2464 // Changing the software layer's bounds and position doesn't always result
2465 // in the layer being anchored to the top-left. Set the layer's frame
2466 // explicitly, since this is more reliable in practice.
2467 if (software_layer_) {
2468 bool frame_changed = !CGRectEqualToRect(
2469 new_background_frame, [software_layer_ frame]);
2470 if (frame_changed) {
2471 [software_layer_ setFrame:new_background_frame];
2476 SkBitmap::Config RenderWidgetHostViewMac::PreferredReadbackFormat() {
2477 return SkBitmap::kARGB_8888_Config;
2480 ////////////////////////////////////////////////////////////////////////////////
2481 // CompositingIOSurfaceLayerClient, public:
2483 void RenderWidgetHostViewMac::AcceleratedLayerDidDrawFrame(bool succeeded) {
2484 SendPendingLatencyInfoToHost();
2485 SendPendingSwapAck();
2487 GotAcceleratedCompositingError();
2490 bool RenderWidgetHostViewMac::AcceleratedLayerHasNotAckedPendingFrame() const {
2491 return pending_swap_ack_;
2494 } // namespace content
2496 // RenderWidgetHostViewCocoa ---------------------------------------------------
2498 @implementation RenderWidgetHostViewCocoa
2499 @synthesize selectedRange = selectedRange_;
2500 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
2501 @synthesize markedRange = markedRange_;
2503 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
2504 self = [super initWithFrame:NSZeroRect];
2506 self.acceptsTouchEvents = YES;
2507 editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
2508 editCommand_helper_->AddEditingSelectorsToClass([self class]);
2510 renderWidgetHostView_.reset(r);
2511 canBeKeyView_ = YES;
2512 focusedPluginIdentifier_ = -1;
2513 renderWidgetHostView_->backing_store_scale_factor_ =
2514 ui::GetScaleFactorForNativeView(self);
2517 if ([self respondsToSelector:
2518 @selector(setWantsBestResolutionOpenGLSurface:)]) {
2519 [self setWantsBestResolutionOpenGLSurface:YES];
2521 handlingGlobalFrameDidChange_ = NO;
2522 [[NSNotificationCenter defaultCenter]
2524 selector:@selector(globalFrameDidChange:)
2525 name:NSViewGlobalFrameDidChangeNotification
2527 [[NSNotificationCenter defaultCenter]
2529 selector:@selector(didChangeScreenParameters:)
2530 name:NSApplicationDidChangeScreenParametersNotification
2537 // Unbind the GL context from this view. If this is not done before super's
2538 // dealloc is called then the GL context will crash when it reaches into
2539 // the view in its destructor.
2540 // http://crbug.com/255608
2541 if (renderWidgetHostView_)
2542 renderWidgetHostView_->AcceleratedSurfaceRelease();
2544 if (responderDelegate_ &&
2545 [responderDelegate_ respondsToSelector:@selector(viewGone:)])
2546 [responderDelegate_ viewGone:self];
2547 responderDelegate_.reset();
2549 [[NSNotificationCenter defaultCenter] removeObserver:self];
2554 - (void)didChangeScreenParameters:(NSNotification*)notify {
2555 g_screen_info_up_to_date = false;
2558 - (void)setResponderDelegate:
2559 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate {
2560 DCHECK(!responderDelegate_);
2561 responderDelegate_.reset([delegate retain]);
2564 - (void)resetCursorRects {
2565 if (currentCursor_) {
2566 [self addCursorRect:[self visibleRect] cursor:currentCursor_];
2567 [currentCursor_ setOnMouseEntered:YES];
2571 - (void)gotUnhandledWheelEvent {
2572 if (responderDelegate_ &&
2574 respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
2575 [responderDelegate_ gotUnhandledWheelEvent];
2579 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
2580 if (responderDelegate_ &&
2582 respondsToSelector:@selector(scrollOffsetPinnedToLeft:toRight:)]) {
2583 [responderDelegate_ scrollOffsetPinnedToLeft:left toRight:right];
2587 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
2588 if (responderDelegate_ &&
2590 respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
2591 [responderDelegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
2595 - (BOOL)respondsToSelector:(SEL)selector {
2596 // Trickiness: this doesn't mean "does this object's superclass respond to
2597 // this selector" but rather "does the -respondsToSelector impl from the
2598 // superclass say that this class responds to the selector".
2599 if ([super respondsToSelector:selector])
2602 if (responderDelegate_)
2603 return [responderDelegate_ respondsToSelector:selector];
2608 - (id)forwardingTargetForSelector:(SEL)selector {
2609 if ([responderDelegate_ respondsToSelector:selector])
2610 return responderDelegate_.get();
2612 return [super forwardingTargetForSelector:selector];
2615 - (void)setCanBeKeyView:(BOOL)can {
2616 canBeKeyView_ = can;
2619 - (BOOL)acceptsMouseEventsWhenInactive {
2620 // Some types of windows (balloons, always-on-top panels) want to accept mouse
2621 // clicks w/o the first click being treated as 'activation'. Same applies to
2622 // mouse move events.
2623 return [[self window] level] > NSNormalWindowLevel;
2626 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
2627 return [self acceptsMouseEventsWhenInactive];
2630 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
2631 takesFocusOnlyOnMouseDown_ = b;
2634 - (void)setCloseOnDeactivate:(BOOL)b {
2635 closeOnDeactivate_ = b;
2638 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
2639 NSWindow* window = [self window];
2640 // If this is a background window, don't handle mouse movement events. This
2641 // is the expected behavior on the Mac as evidenced by other applications.
2642 if ([theEvent type] == NSMouseMoved &&
2643 ![self acceptsMouseEventsWhenInactive] &&
2644 ![window isKeyWindow]) {
2648 // Use hitTest to check whether the mouse is over a nonWebContentView - in
2649 // which case the mouse event should not be handled by the render host.
2650 const SEL nonWebContentViewSelector = @selector(nonWebContentView);
2651 NSView* contentView = [window contentView];
2652 NSView* view = [contentView hitTest:[theEvent locationInWindow]];
2653 // Traverse the superview hierarchy as the hitTest will return the frontmost
2654 // view, such as an NSTextView, while nonWebContentView may be specified by
2657 if ([view respondsToSelector:nonWebContentViewSelector] &&
2658 [view performSelector:nonWebContentViewSelector]) {
2659 // The cursor is over a nonWebContentView - ignore this mouse event.
2662 if ([view isKindOfClass:[self class]] && ![view isEqual:self] &&
2663 !hasOpenMouseDown_) {
2664 // The cursor is over an overlapping render widget. This check is done by
2665 // both views so the one that's returned by -hitTest: will end up
2666 // processing the event.
2667 // Note that while dragging, we only get events for the render view where
2668 // drag started, even if mouse is actually over another view or outside
2669 // the window. Cocoa does this for us. We should handle these events and
2670 // not ignore (since there is no other render view to handle them). Thus
2671 // the |!hasOpenMouseDown_| check above.
2674 view = [view superview];
2679 - (void)mouseEvent:(NSEvent*)theEvent {
2680 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
2681 if (responderDelegate_ &&
2682 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2683 BOOL handled = [responderDelegate_ handleEvent:theEvent];
2688 if ([self shouldIgnoreMouseEvent:theEvent]) {
2689 // If this is the first such event, send a mouse exit to the host view.
2690 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
2691 WebMouseEvent exitEvent =
2692 WebInputEventFactory::mouseEvent(theEvent, self);
2693 exitEvent.type = WebInputEvent::MouseLeave;
2694 exitEvent.button = WebMouseEvent::ButtonNone;
2695 renderWidgetHostView_->ForwardMouseEvent(exitEvent);
2697 mouseEventWasIgnored_ = YES;
2701 if (mouseEventWasIgnored_) {
2702 // If this is the first mouse event after a previous event that was ignored
2703 // due to the hitTest, send a mouse enter event to the host view.
2704 if (renderWidgetHostView_->render_widget_host_) {
2705 WebMouseEvent enterEvent =
2706 WebInputEventFactory::mouseEvent(theEvent, self);
2707 enterEvent.type = WebInputEvent::MouseMove;
2708 enterEvent.button = WebMouseEvent::ButtonNone;
2709 renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2712 mouseEventWasIgnored_ = NO;
2714 // TODO(rohitrao): Probably need to handle other mouse down events here.
2715 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2716 if (renderWidgetHostView_->render_widget_host_)
2717 renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2719 // Manually take focus after the click but before forwarding it to the
2721 [[self window] makeFirstResponder:self];
2724 // Don't cancel child popups; killing them on a mouse click would prevent the
2725 // user from positioning the insertion point in the text field spawning the
2726 // popup. A click outside the text field would cause the text field to drop
2727 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2728 // the popup anyway, so we're OK.
2730 NSEventType type = [theEvent type];
2731 if (type == NSLeftMouseDown)
2732 hasOpenMouseDown_ = YES;
2733 else if (type == NSLeftMouseUp)
2734 hasOpenMouseDown_ = NO;
2736 // TODO(suzhe): We should send mouse events to the input method first if it
2737 // wants to handle them. But it won't work without implementing method
2738 // - (NSUInteger)characterIndexForPoint:.
2739 // See: http://code.google.com/p/chromium/issues/detail?id=47141
2740 // Instead of sending mouse events to the input method first, we now just
2741 // simply confirm all ongoing composition here.
2742 if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2743 type == NSOtherMouseDown) {
2744 [self confirmComposition];
2747 const WebMouseEvent event =
2748 WebInputEventFactory::mouseEvent(theEvent, self);
2749 renderWidgetHostView_->ForwardMouseEvent(event);
2752 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2753 // |performKeyEquivalent:| is sent to all views of a window, not only down the
2754 // responder chain (cf. "Handling Key Equivalents" in
2755 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2756 // ). We only want to handle key equivalents if we're first responder.
2757 if ([[self window] firstResponder] != self)
2760 // If we return |NO| from this function, cocoa will send the key event to
2761 // the menu and only if the menu does not process the event to |keyDown:|. We
2762 // want to send the event to a renderer _before_ sending it to the menu, so
2763 // we need to return |YES| for all events that might be swallowed by the menu.
2764 // We do not return |YES| for every keypress because we don't get |keyDown:|
2765 // events for keys that we handle this way.
2766 NSUInteger modifierFlags = [theEvent modifierFlags];
2767 if ((modifierFlags & NSCommandKeyMask) == 0) {
2768 // Make sure the menu does not contain key equivalents that don't
2770 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2774 // Command key combinations are sent via performKeyEquivalent rather than
2775 // keyDown:. We just forward this on and if WebCore doesn't want to handle
2776 // it, we let the WebContentsView figure out how to reinject it.
2777 [self keyEvent:theEvent wasKeyEquivalent:YES];
2781 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2782 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2783 // returned NO. If this function returns |YES|, Cocoa sends the event to
2784 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2785 // to us instead of doing key view loop control, ctrl-left/right get handled
2787 // (However, there are still some keys that Cocoa swallows, e.g. the key
2788 // equivalent that Cocoa uses for toggling the input language. In this case,
2789 // that's actually a good thing, though -- see http://crbug.com/26115 .)
2793 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2794 if (responderDelegate_ &&
2795 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2796 BOOL handled = [responderDelegate_ handleEvent:theEvent];
2798 return kEventHandled;
2801 [self keyEvent:theEvent wasKeyEquivalent:NO];
2802 return kEventHandled;
2805 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2806 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2807 DCHECK([theEvent type] != NSKeyDown ||
2808 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2810 if ([theEvent type] == NSFlagsChanged) {
2811 // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2812 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2813 int keyCode = [theEvent keyCode];
2814 if (!keyCode || keyCode == 10 || keyCode == 63)
2818 // Don't cancel child popups; the key events are probably what's triggering
2819 // the popup in the first place.
2821 RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2824 NativeWebKeyboardEvent event(theEvent);
2826 // Force fullscreen windows to close on Escape so they won't keep the keyboard
2827 // grabbed or be stuck onscreen if the renderer is hanging.
2828 if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2829 event.windowsKeyCode == ui::VKEY_ESCAPE &&
2830 renderWidgetHostView_->pepper_fullscreen_window()) {
2831 RenderWidgetHostViewMac* parent =
2832 renderWidgetHostView_->fullscreen_parent_host_view();
2834 parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2835 widgetHost->Shutdown();
2839 // Suppress the escape key up event if necessary.
2840 if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2841 if (event.type == NativeWebKeyboardEvent::KeyUp)
2842 suppressNextEscapeKeyUp_ = NO;
2846 // We only handle key down events and just simply forward other events.
2847 if ([theEvent type] != NSKeyDown) {
2848 widgetHost->ForwardKeyboardEvent(event);
2850 // Possibly autohide the cursor.
2851 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2852 [NSCursor setHiddenUntilMouseMoves:YES];
2857 base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2859 // Records the current marked text state, so that we can know if the marked
2860 // text was deleted or not after handling the key down event.
2861 BOOL oldHasMarkedText = hasMarkedText_;
2863 // This method should not be called recursively.
2864 DCHECK(!handlingKeyDown_);
2866 // Tells insertText: and doCommandBySelector: that we are handling a key
2868 handlingKeyDown_ = YES;
2870 // These variables might be set when handling the keyboard event.
2871 // Clear them here so that we can know whether they have changed afterwards.
2872 textToBeInserted_.clear();
2873 markedText_.clear();
2874 underlines_.clear();
2875 unmarkTextCalled_ = NO;
2876 hasEditCommands_ = NO;
2877 editCommands_.clear();
2879 // Before doing anything with a key down, check to see if plugin IME has been
2880 // cancelled, since the plugin host needs to be informed of that before
2881 // receiving the keydown.
2882 if ([theEvent type] == NSKeyDown)
2883 [self checkForPluginImeCancellation];
2885 // Sends key down events to input method first, then we can decide what should
2886 // be done according to input method's feedback.
2887 // If a plugin is active, bypass this step since events are forwarded directly
2888 // to the plugin IME.
2889 if (focusedPluginIdentifier_ == -1)
2890 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2892 handlingKeyDown_ = NO;
2894 // Indicates if we should send the key event and corresponding editor commands
2895 // after processing the input method result.
2896 BOOL delayEventUntilAfterImeCompostion = NO;
2898 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2899 // while an input method is composing or inserting a text.
2900 // Gmail checks this code in its onkeydown handler to stop auto-completing
2901 // e-mail addresses while composing a CJK text.
2902 // If the text to be inserted has only one character, then we don't need this
2903 // trick, because we'll send the text as a key press event instead.
2904 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2905 NativeWebKeyboardEvent fakeEvent = event;
2906 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
2907 fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2908 fakeEvent.skip_in_browser = true;
2909 widgetHost->ForwardKeyboardEvent(fakeEvent);
2910 // If this key event was handled by the input method, but
2911 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2912 // enqueued edit commands, then in order to let webkit handle them
2913 // correctly, we need to send the real key event and corresponding edit
2914 // commands after processing the input method result.
2915 // We shouldn't do this if a new marked text was set by the input method,
2916 // otherwise the new marked text might be cancelled by webkit.
2917 if (hasEditCommands_ && !hasMarkedText_)
2918 delayEventUntilAfterImeCompostion = YES;
2920 if (!editCommands_.empty()) {
2921 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2922 widgetHost->GetRoutingID(), editCommands_));
2924 widgetHost->ForwardKeyboardEvent(event);
2927 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2928 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2929 // be set to NULL. So we check it here and return immediately if it's NULL.
2930 if (!renderWidgetHostView_->render_widget_host_)
2933 // Then send keypress and/or composition related events.
2934 // If there was a marked text or the text to be inserted is longer than 1
2935 // character, then we send the text by calling ConfirmComposition().
2936 // Otherwise, if the text to be inserted only contains 1 character, then we
2937 // can just send a keypress event which is fabricated by changing the type of
2938 // the keydown event, so that we can retain all necessary informations, such
2939 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2940 // prevent the browser from handling it again.
2941 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2942 // handle BMP characters here, as we can always insert non-BMP characters as
2944 BOOL textInserted = NO;
2945 if (textToBeInserted_.length() >
2946 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2947 widgetHost->ImeConfirmComposition(
2948 textToBeInserted_, gfx::Range::InvalidRange(), false);
2952 // Updates or cancels the composition. If some text has been inserted, then
2953 // we don't need to cancel the composition explicitly.
2954 if (hasMarkedText_ && markedText_.length()) {
2955 // Sends the updated marked text to the renderer so it can update the
2956 // composition node in WebKit.
2957 // When marked text is available, |selectedRange_| will be the range being
2958 // selected inside the marked text.
2959 widgetHost->ImeSetComposition(markedText_, underlines_,
2960 selectedRange_.location,
2961 NSMaxRange(selectedRange_));
2962 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2963 if (unmarkTextCalled_) {
2964 widgetHost->ImeConfirmComposition(
2965 base::string16(), gfx::Range::InvalidRange(), false);
2967 widgetHost->ImeCancelComposition();
2971 // If the key event was handled by the input method but it also generated some
2972 // edit commands, then we need to send the real key event and corresponding
2973 // edit commands here. This usually occurs when the input method wants to
2974 // finish current composition session but still wants the application to
2975 // handle the key event. See http://crbug.com/48161 for reference.
2976 if (delayEventUntilAfterImeCompostion) {
2977 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2978 // with windowsKeyCode == 0xE5 has already been sent to webkit.
2979 // So before sending the real key down event, we need to send a fake key up
2980 // event to balance it.
2981 NativeWebKeyboardEvent fakeEvent = event;
2982 fakeEvent.type = blink::WebInputEvent::KeyUp;
2983 fakeEvent.skip_in_browser = true;
2984 widgetHost->ForwardKeyboardEvent(fakeEvent);
2985 // Not checking |renderWidgetHostView_->render_widget_host_| here because
2986 // a key event with |skip_in_browser| == true won't be handled by browser,
2987 // thus it won't destroy the widget.
2989 if (!editCommands_.empty()) {
2990 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2991 widgetHost->GetRoutingID(), editCommands_));
2993 widgetHost->ForwardKeyboardEvent(event);
2995 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2996 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2997 // be set to NULL. So we check it here and return immediately if it's NULL.
2998 if (!renderWidgetHostView_->render_widget_host_)
3002 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
3003 // Only send a corresponding key press event if there is no marked text.
3004 if (!hasMarkedText_) {
3005 if (!textInserted && textToBeInserted_.length() == 1) {
3006 // If a single character was inserted, then we just send it as a keypress
3008 event.type = blink::WebInputEvent::Char;
3009 event.text[0] = textToBeInserted_[0];
3011 event.skip_in_browser = true;
3012 widgetHost->ForwardKeyboardEvent(event);
3013 } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
3014 [[theEvent characters] length] > 0 &&
3015 (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
3016 (hasEditCommands_ && editCommands_.empty()))) {
3017 // We don't get insertText: calls if ctrl or cmd is down, or the key event
3018 // generates an insert command. So synthesize a keypress event for these
3019 // cases, unless the key event generated any other command.
3020 event.type = blink::WebInputEvent::Char;
3021 event.skip_in_browser = true;
3022 widgetHost->ForwardKeyboardEvent(event);
3026 // Possibly autohide the cursor.
3027 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
3028 [NSCursor setHiddenUntilMouseMoves:YES];
3031 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
3032 DCHECK(base::mac::IsOSLionOrLater());
3034 if ([event phase] != NSEventPhaseEnded &&
3035 [event phase] != NSEventPhaseCancelled) {
3039 if (renderWidgetHostView_->render_widget_host_) {
3040 // History-swiping is not possible if the logic reaches this point.
3041 // Allow rubber-banding in both directions.
3042 bool canRubberbandLeft = true;
3043 bool canRubberbandRight = true;
3044 const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
3045 event, self, canRubberbandLeft, canRubberbandRight);
3046 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
3049 if (endWheelMonitor_) {
3050 [NSEvent removeMonitor:endWheelMonitor_];
3051 endWheelMonitor_ = nil;
3055 - (void)beginGestureWithEvent:(NSEvent*)event {
3056 [responderDelegate_ beginGestureWithEvent:event];
3058 - (void)endGestureWithEvent:(NSEvent*)event {
3059 [responderDelegate_ endGestureWithEvent:event];
3061 - (void)touchesMovedWithEvent:(NSEvent*)event {
3062 [responderDelegate_ touchesMovedWithEvent:event];
3064 - (void)touchesBeganWithEvent:(NSEvent*)event {
3065 [responderDelegate_ touchesBeganWithEvent:event];
3067 - (void)touchesCancelledWithEvent:(NSEvent*)event {
3068 [responderDelegate_ touchesCancelledWithEvent:event];
3070 - (void)touchesEndedWithEvent:(NSEvent*)event {
3071 [responderDelegate_ touchesEndedWithEvent:event];
3074 // This is invoked only on 10.8 or newer when the user taps a word using
3076 - (void)quickLookWithEvent:(NSEvent*)event {
3077 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
3078 TextInputClientMac::GetInstance()->GetStringAtPoint(
3079 renderWidgetHostView_->render_widget_host_,
3080 gfx::Point(point.x, NSHeight([self frame]) - point.y),
3081 ^(NSAttributedString* string, NSPoint baselinePoint) {
3082 if (string && [string length] > 0) {
3083 dispatch_async(dispatch_get_main_queue(), ^{
3084 [self showDefinitionForAttributedString:string
3085 atPoint:baselinePoint];
3092 // This method handles 2 different types of hardware events.
3093 // (Apple does not distinguish between them).
3094 // a. Scrolling the middle wheel of a mouse.
3095 // b. Swiping on the track pad.
3097 // This method is responsible for 2 types of behavior:
3098 // a. Scrolling the content of window.
3099 // b. Navigating forwards/backwards in history.
3101 // This is a brief description of the logic:
3102 // 1. If the content can be scrolled, scroll the content.
3103 // (This requires a roundtrip to blink to determine whether the content
3104 // can be scrolled.)
3105 // Once this logic is triggered, the navigate logic cannot be triggered
3106 // until the gesture finishes.
3107 // 2. If the user is making a horizontal swipe, start the navigate
3108 // forward/backwards UI.
3109 // Once this logic is triggered, the user can either cancel or complete
3110 // the gesture. If the user completes the gesture, all remaining touches
3111 // are swallowed, and not allowed to scroll the content. If the user
3112 // cancels the gesture, all remaining touches are forwarded to the content
3113 // scroll logic. The user cannot trigger the navigation logic again.
3114 - (void)scrollWheel:(NSEvent*)event {
3115 if (responderDelegate_ &&
3116 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
3117 BOOL handled = [responderDelegate_ handleEvent:event];
3122 // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
3123 // the event is received even when the mouse cursor is no longer over the view
3124 // when the scrolling ends (e.g. if the tab was switched). This is necessary
3125 // for ending rubber-banding in such cases.
3126 if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
3127 !endWheelMonitor_) {
3129 [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
3130 handler:^(NSEvent* blockEvent) {
3131 [self shortCircuitScrollWheelEvent:blockEvent];
3136 // This is responsible for content scrolling!
3137 if (renderWidgetHostView_->render_widget_host_) {
3138 BOOL canRubberbandLeft = [responderDelegate_ canRubberbandLeft:self];
3139 BOOL canRubberbandRight = [responderDelegate_ canRubberbandRight:self];
3140 const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
3141 event, self, canRubberbandLeft, canRubberbandRight);
3142 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
3146 // Called repeatedly during a pinch gesture, with incremental change values.
3147 - (void)magnifyWithEvent:(NSEvent*)event {
3148 if (renderWidgetHostView_->render_widget_host_) {
3149 // Send a GesturePinchUpdate event.
3150 // Note that we don't attempt to bracket these by GesturePinchBegin/End (or
3151 // GestureSrollBegin/End) as is done for touchscreen. Keeping track of when
3152 // a pinch is active would take a little more work here, and we don't need
3153 // it for anything yet.
3154 const WebGestureEvent& webEvent =
3155 WebInputEventFactory::gestureEvent(event, self);
3156 renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(webEvent);
3160 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
3161 NSWindow* oldWindow = [self window];
3163 // We're messing with the window, so do this to ensure no flashes. This one
3164 // prevents a flash when the current tab is closed.
3165 if (!renderWidgetHostView_->use_core_animation_)
3166 [oldWindow disableScreenUpdatesUntilFlush];
3168 NSNotificationCenter* notificationCenter =
3169 [NSNotificationCenter defaultCenter];
3171 // Backing property notifications crash on 10.6 when building with the 10.7
3172 // SDK, see http://crbug.com/260595.
3173 static BOOL supportsBackingPropertiesNotification =
3174 SupportsBackingPropertiesChangedNotification();
3177 if (supportsBackingPropertiesNotification) {
3180 name:NSWindowDidChangeBackingPropertiesNotification
3185 name:NSWindowDidMoveNotification
3189 name:NSWindowDidEndLiveResizeNotification
3193 if (supportsBackingPropertiesNotification) {
3196 selector:@selector(windowDidChangeBackingProperties:)
3197 name:NSWindowDidChangeBackingPropertiesNotification
3202 selector:@selector(windowChangedGlobalFrame:)
3203 name:NSWindowDidMoveNotification
3207 selector:@selector(windowChangedGlobalFrame:)
3208 name:NSWindowDidEndLiveResizeNotification
3213 - (void)updateScreenProperties{
3214 renderWidgetHostView_->UpdateBackingStoreScaleFactor();
3215 renderWidgetHostView_->UpdateDisplayLink();
3218 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
3219 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
3220 // Background tabs check if their scale factor or vsync properties changed
3221 // when they are added to a window.
3223 // Allocating a CGLayerRef with the current scale factor immediately from
3224 // this handler doesn't work. Schedule the backing store update on the
3225 // next runloop cycle, then things are read for CGLayerRef allocations to
3227 [self performSelector:@selector(updateScreenProperties)
3232 - (void)globalFrameDidChange:(NSNotification*)notification {
3233 if (handlingGlobalFrameDidChange_)
3236 handlingGlobalFrameDidChange_ = YES;
3237 if (!renderWidgetHostView_->use_core_animation_ &&
3238 renderWidgetHostView_->compositing_iosurface_context_) {
3239 [renderWidgetHostView_->compositing_iosurface_context_->nsgl_context()
3242 handlingGlobalFrameDidChange_ = NO;
3245 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
3246 renderWidgetHostView_->UpdateScreenInfo(
3247 renderWidgetHostView_->GetNativeView());
3250 - (void)setFrameSize:(NSSize)newSize {
3251 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
3253 // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
3254 // -setFrame: isn't neccessary.
3255 [super setFrameSize:newSize];
3257 if (!renderWidgetHostView_->render_widget_host_)
3260 // Move the CALayers to their positions in the new view size. Note that
3261 // this will not draw anything because the non-background layers' sizes
3262 // didn't actually change.
3263 renderWidgetHostView_->LayoutLayers();
3265 renderWidgetHostView_->render_widget_host_->SendScreenRects();
3266 renderWidgetHostView_->render_widget_host_->WasResized();
3267 if (renderWidgetHostView_->delegated_frame_host_)
3268 renderWidgetHostView_->delegated_frame_host_->WasResized();
3270 // Wait for the frame that WasResize might have requested. If the view is
3271 // being made visible at a new size, then this call will have no effect
3272 // because the view widget is still hidden, and the pause call in WasShown
3273 // will have this effect for us.
3274 renderWidgetHostView_->PauseForPendingResizeOrRepaintsAndDraw();
3277 // Fills with white the parts of the area to the right and bottom for |rect|
3278 // that intersect |damagedRect|.
3279 - (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
3280 dirtyRect:(gfx::Rect)damagedRect
3281 inContext:(CGContextRef)context {
3282 if (damagedRect.right() > rect.right()) {
3283 int x = std::max(rect.right(), damagedRect.x());
3284 int y = std::min(rect.bottom(), damagedRect.bottom());
3285 int width = damagedRect.right() - x;
3286 int height = damagedRect.y() - y;
3288 // Extra fun to get around the fact that gfx::Rects can't have
3299 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
3300 CGContextSetFillColorWithColor(context,
3301 CGColorGetConstantColor(kCGColorWhite));
3302 CGContextFillRect(context, NSRectToCGRect(r));
3304 if (damagedRect.bottom() > rect.bottom()) {
3305 int x = damagedRect.x();
3306 int y = damagedRect.bottom();
3307 int width = damagedRect.right() - x;
3308 int height = std::max(rect.bottom(), damagedRect.y()) - y;
3310 // Extra fun to get around the fact that gfx::Rects can't have
3321 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
3322 CGContextSetFillColorWithColor(context,
3323 CGColorGetConstantColor(kCGColorWhite));
3324 CGContextFillRect(context, NSRectToCGRect(r));
3328 - (void)drawRect:(NSRect)dirtyRect {
3329 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::drawRect");
3330 DCHECK(!renderWidgetHostView_->use_core_animation_);
3332 if (!renderWidgetHostView_->render_widget_host_) {
3333 // When using CoreAnimation, this path is used to paint the contents area
3334 // white before any frames come in. When layers to draw frames exist, this
3336 [[NSColor whiteColor] set];
3337 NSRectFill(dirtyRect);
3341 const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
3343 if (renderWidgetHostView_->last_frame_was_accelerated_ &&
3344 renderWidgetHostView_->compositing_iosurface_) {
3345 if (renderWidgetHostView_->allow_overlapping_views_) {
3346 // If overlapping views need to be allowed, punch a hole in the window
3347 // to expose the GL underlay.
3348 TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
3349 "h", damagedRect.height());
3350 // NSRectFill is extremely slow (15ms for a window on a fast MacPro), so
3351 // this is only done when it's a real invalidation from window damage (not
3352 // when a BuffersSwapped was received). Note that even a 1x1 NSRectFill
3353 // can take many milliseconds sometimes (!) so this is skipped completely
3354 // for drawRects that are triggered by BuffersSwapped messages.
3355 [[NSColor clearColor] set];
3356 NSRectFill(dirtyRect);
3359 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
3360 renderWidgetHostView_->compositing_iosurface_context_->cgl_context());
3361 renderWidgetHostView_->DrawIOSurfaceWithoutCoreAnimation();
3365 CGContextRef context = static_cast<CGContextRef>(
3366 [[NSGraphicsContext currentContext] graphicsPort]);
3367 [self drawWithDirtyRect:NSRectToCGRect(dirtyRect)
3371 - (void)drawWithDirtyRect:(CGRect)dirtyRect
3372 inContext:(CGContextRef)context {
3373 content::SoftwareFrameManager* software_frame_manager =
3374 renderWidgetHostView_->software_frame_manager_.get();
3375 if (software_frame_manager->HasCurrentFrame()) {
3376 // Note: All coordinates are in view units, not pixels.
3377 gfx::Rect bitmapRect(
3378 software_frame_manager->GetCurrentFrameSizeInDIP());
3380 // Specify the proper y offset to ensure that the view is rooted to the
3381 // upper left corner. This can be negative, if the window was resized
3382 // smaller and the renderer hasn't yet repainted.
3383 int yOffset = NSHeight([self bounds]) - bitmapRect.height();
3385 NSRect nsDirtyRect = NSRectFromCGRect(dirtyRect);
3386 const gfx::Rect damagedRect([self flipNSRectToRect:nsDirtyRect]);
3388 gfx::Rect paintRect = gfx::IntersectRects(bitmapRect, damagedRect);
3389 if (!paintRect.IsEmpty()) {
3390 gfx::Size sizeInPixels =
3391 software_frame_manager->GetCurrentFrameSizeInPixels();
3392 base::ScopedCFTypeRef<CGDataProviderRef> dataProvider(
3393 CGDataProviderCreateWithData(
3395 software_frame_manager->GetCurrentFramePixels(),
3396 4 * sizeInPixels.width() * sizeInPixels.height(),
3398 base::ScopedCFTypeRef<CGImageRef> image(
3400 sizeInPixels.width(),
3401 sizeInPixels.height(),
3404 4 * sizeInPixels.width(),
3405 base::mac::GetSystemColorSpace(),
3406 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
3410 kCGRenderingIntentDefault));
3411 CGRect imageRect = bitmapRect.ToCGRect();
3412 imageRect.origin.y = yOffset;
3413 CGContextDrawImage(context, imageRect, image);
3416 renderWidgetHostView_->SendPendingLatencyInfoToHost();
3418 // Fill the remaining portion of the damagedRect with white
3419 [self fillBottomRightRemainderOfRect:bitmapRect
3420 dirtyRect:damagedRect
3423 if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
3424 base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
3425 renderWidgetHostView_->whiteout_start_time_;
3426 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
3428 // Reset the start time to 0 so that we start recording again the next
3429 // time the backing store is NULL...
3430 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
3432 if (!renderWidgetHostView_->web_contents_switch_paint_time_.is_null()) {
3433 base::TimeDelta web_contents_switch_paint_duration =
3434 base::TimeTicks::Now() -
3435 renderWidgetHostView_->web_contents_switch_paint_time_;
3436 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
3437 web_contents_switch_paint_duration);
3438 // Reset contents_switch_paint_time_ to 0 so future tab selections are
3440 renderWidgetHostView_->web_contents_switch_paint_time_ =
3444 CGContextSetFillColorWithColor(context,
3445 CGColorGetConstantColor(kCGColorWhite));
3446 CGContextFillRect(context, dirtyRect);
3447 if (renderWidgetHostView_->whiteout_start_time_.is_null())
3448 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
3452 - (BOOL)canBecomeKeyView {
3453 if (!renderWidgetHostView_->render_widget_host_)
3456 return canBeKeyView_;
3459 - (BOOL)acceptsFirstResponder {
3460 if (!renderWidgetHostView_->render_widget_host_)
3463 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
3466 - (BOOL)becomeFirstResponder {
3467 if (!renderWidgetHostView_->render_widget_host_)
3470 renderWidgetHostView_->render_widget_host_->Focus();
3471 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
3472 renderWidgetHostView_->SetTextInputActive(true);
3474 // Cancel any onging composition text which was left before we lost focus.
3475 // TODO(suzhe): We should do it in -resignFirstResponder: method, but
3476 // somehow that method won't be called when switching among different tabs.
3477 // See http://crbug.com/47209
3478 [self cancelComposition];
3480 NSNumber* direction = [NSNumber numberWithUnsignedInteger:
3481 [[self window] keyViewSelectionDirection]];
3482 NSDictionary* userInfo =
3483 [NSDictionary dictionaryWithObject:direction
3484 forKey:kSelectionDirection];
3485 [[NSNotificationCenter defaultCenter]
3486 postNotificationName:kViewDidBecomeFirstResponder
3493 - (BOOL)resignFirstResponder {
3494 renderWidgetHostView_->SetTextInputActive(false);
3495 if (!renderWidgetHostView_->render_widget_host_)
3498 if (closeOnDeactivate_)
3499 renderWidgetHostView_->KillSelf();
3501 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
3502 renderWidgetHostView_->render_widget_host_->Blur();
3504 // We should cancel any onging composition whenever RWH's Blur() method gets
3505 // called, because in this case, webkit will confirm the ongoing composition
3507 [self cancelComposition];
3512 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
3513 if (responderDelegate_ &&
3515 respondsToSelector:@selector(validateUserInterfaceItem:
3519 [responderDelegate_ validateUserInterfaceItem:item isValidItem:&valid];
3524 SEL action = [item action];
3526 if (action == @selector(stopSpeaking:)) {
3527 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3528 renderWidgetHostView_->IsSpeaking();
3530 if (action == @selector(startSpeaking:)) {
3531 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3532 renderWidgetHostView_->SupportsSpeech();
3535 // For now, these actions are always enabled for render view,
3536 // this is sub-optimal.
3537 // TODO(suzhe): Plumb the "can*" methods up from WebCore.
3538 if (action == @selector(undo:) ||
3539 action == @selector(redo:) ||
3540 action == @selector(cut:) ||
3541 action == @selector(copy:) ||
3542 action == @selector(copyToFindPboard:) ||
3543 action == @selector(paste:) ||
3544 action == @selector(pasteAndMatchStyle:)) {
3545 return renderWidgetHostView_->render_widget_host_->IsRenderView();
3548 return editCommand_helper_->IsMenuItemEnabled(action, self);
3551 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
3552 return renderWidgetHostView_.get();
3555 // Determine whether we should autohide the cursor (i.e., hide it until mouse
3556 // move) for the given event. Customize here to be more selective about which
3557 // key presses to autohide on.
3558 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
3559 return ([event type] == NSKeyDown &&
3560 !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
3563 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
3564 index:(NSUInteger)index
3565 maxCount:(NSUInteger)maxCount {
3566 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3567 NSUInteger totalLength = [fullArray count];
3568 if (index >= totalLength)
3570 NSUInteger length = MIN(totalLength - index, maxCount);
3571 return [fullArray subarrayWithRange:NSMakeRange(index, length)];
3574 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
3575 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3576 return [fullArray count];
3579 - (id)accessibilityAttributeValue:(NSString *)attribute {
3580 BrowserAccessibilityManager* manager =
3581 renderWidgetHostView_->GetBrowserAccessibilityManager();
3583 // Contents specifies document view of RenderWidgetHostViewCocoa provided by
3584 // BrowserAccessibilityManager. Children includes all subviews in addition to
3585 // contents. Currently we do not have subviews besides the document view.
3586 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
3587 [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
3589 return [NSArray arrayWithObjects:manager->
3590 GetRoot()->ToBrowserAccessibilityCocoa(), nil];
3591 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
3592 return NSAccessibilityScrollAreaRole;
3594 id ret = [super accessibilityAttributeValue:attribute];
3598 - (NSArray*)accessibilityAttributeNames {
3599 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
3600 [ret addObject:NSAccessibilityContentsAttribute];
3601 [ret addObjectsFromArray:[super accessibilityAttributeNames]];
3605 - (id)accessibilityHitTest:(NSPoint)point {
3606 if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
3608 NSPoint pointInWindow = [[self window] convertScreenToBase:point];
3609 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
3610 localPoint.y = NSHeight([self bounds]) - localPoint.y;
3611 BrowserAccessibilityCocoa* root = renderWidgetHostView_->
3612 GetBrowserAccessibilityManager()->
3613 GetRoot()->ToBrowserAccessibilityCocoa();
3614 id obj = [root accessibilityHitTest:localPoint];
3618 - (BOOL)accessibilityIsIgnored {
3619 return !renderWidgetHostView_->GetBrowserAccessibilityManager();
3622 - (NSUInteger)accessibilityGetIndexOf:(id)child {
3623 BrowserAccessibilityManager* manager =
3624 renderWidgetHostView_->GetBrowserAccessibilityManager();
3625 // Only child is root.
3627 manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
3634 - (id)accessibilityFocusedUIElement {
3635 BrowserAccessibilityManager* manager =
3636 renderWidgetHostView_->GetBrowserAccessibilityManager();
3638 BrowserAccessibility* focused_item = manager->GetFocus(NULL);
3639 DCHECK(focused_item);
3641 BrowserAccessibilityCocoa* focused_item_cocoa =
3642 focused_item->ToBrowserAccessibilityCocoa();
3643 DCHECK(focused_item_cocoa);
3644 if (focused_item_cocoa)
3645 return focused_item_cocoa;
3648 return [super accessibilityFocusedUIElement];
3651 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
3652 // with minor modifications for code style and commenting.
3654 // The 'public' interface is -setToolTipAtMousePoint:. This differs from
3655 // -setToolTip: in that the updated tooltip takes effect immediately,
3656 // without the user's having to move the mouse out of and back into the view.
3658 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
3659 // the view, which in turn requires overriding some internal tracking-rect
3660 // methods (to keep track of its owner & userdata, which need to be filled out
3661 // in the fake events.) --snej 7/6/09
3665 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3666 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
3668 * Redistribution and use in source and binary forms, with or without
3669 * modification, are permitted provided that the following conditions
3672 * 1. Redistributions of source code must retain the above copyright
3673 * notice, this list of conditions and the following disclaimer.
3674 * 2. Redistributions in binary form must reproduce the above copyright
3675 * notice, this list of conditions and the following disclaimer in the
3676 * documentation and/or other materials provided with the distribution.
3677 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
3678 * its contributors may be used to endorse or promote products derived
3679 * from this software without specific prior written permission.
3681 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
3682 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3683 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3684 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
3685 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3686 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
3687 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3688 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3689 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3690 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3693 // Any non-zero value will do, but using something recognizable might help us
3695 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
3697 // Override of a public NSView method, replacing the inherited functionality.
3698 // See above for rationale.
3699 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
3701 userData:(void *)data
3702 assumeInside:(BOOL)assumeInside {
3703 DCHECK(trackingRectOwner_ == nil);
3704 trackingRectOwner_ = owner;
3705 trackingRectUserData_ = data;
3706 return kTrackingRectTag;
3709 // Override of (apparently) a private NSView method(!) See above for rationale.
3710 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
3712 userData:(void *)data
3713 assumeInside:(BOOL)assumeInside
3714 useTrackingNum:(int)tag {
3715 DCHECK(tag == 0 || tag == kTrackingRectTag);
3716 DCHECK(trackingRectOwner_ == nil);
3717 trackingRectOwner_ = owner;
3718 trackingRectUserData_ = data;
3719 return kTrackingRectTag;
3722 // Override of (apparently) a private NSView method(!) See above for rationale.
3723 - (void)_addTrackingRects:(NSRect *)rects
3725 userDataList:(void **)userDataList
3726 assumeInsideList:(BOOL *)assumeInsideList
3727 trackingNums:(NSTrackingRectTag *)trackingNums
3730 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3731 DCHECK(trackingRectOwner_ == nil);
3732 trackingRectOwner_ = owner;
3733 trackingRectUserData_ = userDataList[0];
3734 trackingNums[0] = kTrackingRectTag;
3737 // Override of a public NSView method, replacing the inherited functionality.
3738 // See above for rationale.
3739 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3743 if (tag == kTrackingRectTag) {
3744 trackingRectOwner_ = nil;
3748 if (tag == lastToolTipTag_) {
3749 [super removeTrackingRect:tag];
3750 lastToolTipTag_ = 0;
3754 // If any other tracking rect is being removed, we don't know how it was
3755 // created and it's possible there's a leak involved (see Radar 3500217).
3759 // Override of (apparently) a private NSView method(!)
3760 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3761 for (int i = 0; i < count; ++i) {
3765 DCHECK(tag == kTrackingRectTag);
3766 trackingRectOwner_ = nil;
3770 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3771 - (void)_sendToolTipMouseExited {
3772 // Nothing matters except window, trackingNumber, and userData.
3773 int windowNumber = [[self window] windowNumber];
3774 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3775 location:NSZeroPoint
3778 windowNumber:windowNumber
3781 trackingNumber:kTrackingRectTag
3782 userData:trackingRectUserData_];
3783 [trackingRectOwner_ mouseExited:fakeEvent];
3786 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3787 - (void)_sendToolTipMouseEntered {
3788 // Nothing matters except window, trackingNumber, and userData.
3789 int windowNumber = [[self window] windowNumber];
3790 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3791 location:NSZeroPoint
3794 windowNumber:windowNumber
3797 trackingNumber:kTrackingRectTag
3798 userData:trackingRectUserData_];
3799 [trackingRectOwner_ mouseEntered:fakeEvent];
3802 // Sets the view's current tooltip, to be displayed at the current mouse
3803 // location. (This does not make the tooltip appear -- as usual, it only
3804 // appears after a delay.) Pass null to remove the tooltip.
3805 - (void)setToolTipAtMousePoint:(NSString *)string {
3806 NSString *toolTip = [string length] == 0 ? nil : string;
3807 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3808 (!toolTip && !toolTip_)) {
3813 [self _sendToolTipMouseExited];
3816 toolTip_.reset([toolTip copy]);
3819 // See radar 3500217 for why we remove all tooltips
3820 // rather than just the single one we created.
3821 [self removeAllToolTips];
3822 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3823 lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3826 [self _sendToolTipMouseEntered];
3830 // NSView calls this to get the text when displaying the tooltip.
3831 - (NSString *)view:(NSView *)view
3832 stringForToolTip:(NSToolTipTag)tag
3833 point:(NSPoint)point
3834 userData:(void *)data {
3835 return [[toolTip_ copy] autorelease];
3838 // Below is our NSTextInputClient implementation.
3840 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3841 // functions to process this event.
3843 // [WebHTMLView keyDown] ->
3844 // EventHandler::keyEvent() ->
3846 // [WebEditorClient handleKeyboardEvent] ->
3847 // [WebHTMLView _interceptEditingKeyEvent] ->
3848 // [NSResponder interpretKeyEvents] ->
3849 // [WebHTMLView insertText] ->
3850 // Editor::insertText()
3852 // Unfortunately, it is hard for Chromium to use this implementation because
3853 // it causes key-typing jank.
3854 // RenderWidgetHostViewMac is running in a browser process. On the other
3855 // hand, Editor and EventHandler are running in a renderer process.
3856 // So, if we used this implementation, a NSKeyDown event is dispatched to
3857 // the following functions of Chromium.
3859 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3860 // |Sync IPC (KeyDown)| (*1) ->
3861 // EventHandler::keyEvent() (renderer) ->
3863 // EditorClientImpl::handleKeyboardEvent() (renderer) ->
3864 // |Sync IPC| (*2) ->
3865 // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3866 // [self interpretKeyEvents] ->
3867 // [RenderWidgetHostViewMac insertText] (browser) ->
3869 // Editor::insertText() (renderer)
3871 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3872 // result of EventHandler::keyEvent().
3873 // (*2) we need to wait until this call finishes since WebEditorClient uses
3874 // the result of [WebHTMLView _interceptEditingKeyEvent].
3876 // This needs many sync IPC messages sent between a browser and a renderer for
3877 // each key event, which would probably result in key-typing jank.
3878 // To avoid this problem, this implementation processes key events (and input
3879 // method events) totally in a browser process and sends asynchronous input
3880 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3881 // renderer process.
3883 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3884 // |Async IPC (RawKeyDown)| ->
3885 // [self interpretKeyEvents] ->
3886 // [RenderWidgetHostViewMac insertText] (browser) ->
3887 // |Async IPC (Char)| ->
3888 // Editor::insertText() (renderer)
3890 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3891 // make any key-typing jank. --hbono 7/23/09
3894 extern NSString *NSTextInputReplacementRangeAttributeName;
3897 - (NSArray *)validAttributesForMarkedText {
3898 // This code is just copied from WebKit except renaming variables.
3899 if (!validAttributesForMarkedText_) {
3900 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3901 NSUnderlineStyleAttributeName,
3902 NSUnderlineColorAttributeName,
3903 NSMarkedClauseSegmentAttributeName,
3904 NSTextInputReplacementRangeAttributeName,
3907 return validAttributesForMarkedText_.get();
3910 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3911 DCHECK([self window]);
3912 // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3913 // coordinates (upper left origin). Scroll offsets will be taken care of in
3915 thePoint = [[self window] convertScreenToBase:thePoint];
3916 thePoint = [self convertPoint:thePoint fromView:nil];
3917 thePoint.y = NSHeight([self frame]) - thePoint.y;
3920 TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3921 renderWidgetHostView_->render_widget_host_,
3922 gfx::Point(thePoint.x, thePoint.y));
3926 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3927 actualRange:(NSRangePointer)actualRange {
3929 if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3933 rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3934 renderWidgetHostView_->render_widget_host_, theRange);
3936 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3938 *actualRange = theRange;
3941 // The returned rectangle is in WebKit coordinates (upper left origin), so
3942 // flip the coordinate system.
3943 NSRect viewFrame = [self frame];
3944 rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3948 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3949 actualRange:(NSRangePointer)actualRange {
3950 NSRect rect = [self firstViewRectForCharacterRange:theRange
3951 actualRange:actualRange];
3953 // Convert into screen coordinates for return.
3954 rect = [self convertRect:rect toView:nil];
3955 rect.origin = [[self window] convertBaseToScreen:rect.origin];
3959 - (NSRange)markedRange {
3960 // An input method calls this method to check if an application really has
3961 // a text being composed when hasMarkedText call returns true.
3962 // Returns the range saved in the setMarkedText method so the input method
3963 // calls the setMarkedText method and we can update the composition node
3964 // there. (When this method returns an empty range, the input method doesn't
3965 // call the setMarkedText method.)
3966 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3969 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3970 actualRange:(NSRangePointer)actualRange {
3971 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3973 *actualRange = range;
3974 NSAttributedString* str =
3975 TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3976 renderWidgetHostView_->render_widget_host_, range);
3980 - (NSInteger)conversationIdentifier {
3981 return reinterpret_cast<NSInteger>(self);
3984 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3985 // nil when the caret is in non-editable content or password box to avoid
3986 // making input methods do their work.
3987 - (NSTextInputContext *)inputContext {
3988 if (focusedPluginIdentifier_ != -1)
3989 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3991 switch(renderWidgetHostView_->text_input_type_) {
3992 case ui::TEXT_INPUT_TYPE_NONE:
3993 case ui::TEXT_INPUT_TYPE_PASSWORD:
3996 return [super inputContext];
4000 - (BOOL)hasMarkedText {
4001 // An input method calls this function to figure out whether or not an
4002 // application is really composing a text. If it is composing, it calls
4003 // the markedRange method, and maybe calls the setMarkedText method.
4004 // It seems an input method usually calls this function when it is about to
4005 // cancel an ongoing composition. If an application has a non-empty marked
4006 // range, it calls the setMarkedText method to delete the range.
4007 return hasMarkedText_;
4010 - (void)unmarkText {
4011 // Delete the composition node of the renderer and finish an ongoing
4013 // It seems an input method calls the setMarkedText method and set an empty
4014 // text when it cancels an ongoing composition, i.e. I have never seen an
4015 // input method calls this method.
4016 hasMarkedText_ = NO;
4017 markedText_.clear();
4018 underlines_.clear();
4020 // If we are handling a key down event, then ConfirmComposition() will be
4021 // called in keyEvent: method.
4022 if (!handlingKeyDown_) {
4023 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
4024 base::string16(), gfx::Range::InvalidRange(), false);
4026 unmarkTextCalled_ = YES;
4030 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
4031 replacementRange:(NSRange)replacementRange {
4032 // An input method updates the composition string.
4033 // We send the given text and range to the renderer so it can update the
4034 // composition node of WebKit.
4035 // TODO(suzhe): It's hard for us to support replacementRange without accessing
4036 // the full web content.
4037 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
4038 NSString* im_text = isAttributedString ? [string string] : string;
4039 int length = [im_text length];
4041 // |markedRange_| will get set on a callback from ImeSetComposition().
4042 selectedRange_ = newSelRange;
4043 markedText_ = base::SysNSStringToUTF16(im_text);
4044 hasMarkedText_ = (length > 0);
4046 underlines_.clear();
4047 if (isAttributedString) {
4048 ExtractUnderlines(string, &underlines_);
4050 // Use a thin black underline by default.
4051 underlines_.push_back(
4052 blink::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
4055 // If we are handling a key down event, then SetComposition() will be
4056 // called in keyEvent: method.
4057 // Input methods of Mac use setMarkedText calls with an empty text to cancel
4058 // an ongoing composition. So, we should check whether or not the given text
4059 // is empty to update the input method state. (Our input method backend can
4060 // automatically cancels an ongoing composition when we send an empty text.
4061 // So, it is OK to send an empty text to the renderer.)
4062 if (!handlingKeyDown_) {
4063 renderWidgetHostView_->render_widget_host_->ImeSetComposition(
4064 markedText_, underlines_,
4065 newSelRange.location, NSMaxRange(newSelRange));
4069 - (void)doCommandBySelector:(SEL)selector {
4070 // An input method calls this function to dispatch an editing command to be
4071 // handled by this view.
4072 if (selector == @selector(noop:))
4075 std::string command(
4076 [RenderWidgetHostViewMacEditCommandHelper::
4077 CommandNameForSelector(selector) UTF8String]);
4079 // If this method is called when handling a key down event, then we need to
4080 // handle the command in the key event handler. Otherwise we can just handle
4082 if (handlingKeyDown_) {
4083 hasEditCommands_ = YES;
4084 // We ignore commands that insert characters, because this was causing
4085 // strange behavior (e.g. tab always inserted a tab rather than moving to
4086 // the next field on the page).
4087 if (!StartsWithASCII(command, "insert", false))
4088 editCommands_.push_back(EditCommand(command, ""));
4090 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
4091 rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
4096 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
4097 // An input method has characters to be inserted.
4098 // Same as Linux, Mac calls this method not only:
4099 // * when an input method finishs composing text, but also;
4100 // * when we type an ASCII character (without using input methods).
4101 // When we aren't using input methods, we should send the given character as
4102 // a Char event so it is dispatched to an onkeypress() event handler of
4104 // On the other hand, when we are using input methods, we should send the
4105 // given characters as an input method event and prevent the characters from
4106 // being dispatched to onkeypress() event handlers.
4107 // Text inserting might be initiated by other source instead of keyboard
4108 // events, such as the Characters dialog. In this case the text should be
4109 // sent as an input method event as well.
4110 // TODO(suzhe): It's hard for us to support replacementRange without accessing
4111 // the full web content.
4112 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
4113 NSString* im_text = isAttributedString ? [string string] : string;
4114 if (handlingKeyDown_) {
4115 textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
4117 gfx::Range replacement_range(replacementRange);
4118 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
4119 base::SysNSStringToUTF16(im_text), replacement_range, false);
4122 // Inserting text will delete all marked text automatically.
4123 hasMarkedText_ = NO;
4126 - (void)insertText:(id)string {
4127 [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
4130 - (void)viewDidMoveToWindow {
4132 [self updateScreenProperties];
4134 if (canBeKeyView_) {
4135 NSWindow* newWindow = [self window];
4136 // Pointer comparison only, since we don't know if lastWindow_ is still
4139 // If we move into a new window, refresh the frame information. We
4140 // don't need to do it if it was the same window as it used to be in,
4141 // since that case is covered by WasShown(). We only want to do this for
4142 // real browser views, not popups.
4143 if (newWindow != lastWindow_) {
4144 lastWindow_ = newWindow;
4145 renderWidgetHostView_->WindowFrameChanged();
4150 // If we switch windows (or are removed from the view hierarchy), cancel any
4151 // open mouse-downs.
4152 if (hasOpenMouseDown_) {
4153 WebMouseEvent event;
4154 event.type = WebInputEvent::MouseUp;
4155 event.button = WebMouseEvent::ButtonLeft;
4156 renderWidgetHostView_->ForwardMouseEvent(event);
4158 hasOpenMouseDown_ = NO;
4162 - (void)undo:(id)sender {
4163 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
4165 web_contents->Undo();
4168 - (void)redo:(id)sender {
4169 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
4171 web_contents->Redo();
4174 - (void)cut:(id)sender {
4175 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
4177 web_contents->Cut();
4180 - (void)copy:(id)sender {
4181 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
4183 web_contents->Copy();
4186 - (void)copyToFindPboard:(id)sender {
4187 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
4189 web_contents->CopyToFindPboard();
4192 - (void)paste:(id)sender {
4193 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
4195 web_contents->Paste();
4198 - (void)pasteAndMatchStyle:(id)sender {
4199 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
4201 web_contents->PasteAndMatchStyle();
4204 - (void)selectAll:(id)sender {
4205 // editCommand_helper_ adds implementations for most NSResponder methods
4206 // dynamically. But the renderer side only sends selection results back to
4207 // the browser if they were triggered by a keyboard event or went through
4208 // one of the Select methods on RWH. Since selectAll: is called from the
4209 // menu handler, neither is true.
4210 // Explicitly call SelectAll() here to make sure the renderer returns
4211 // selection results.
4212 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
4214 web_contents->SelectAll();
4217 - (void)startSpeaking:(id)sender {
4218 renderWidgetHostView_->SpeakSelection();
4221 - (void)stopSpeaking:(id)sender {
4222 renderWidgetHostView_->StopSpeaking();
4225 - (void)cancelComposition {
4226 if (!hasMarkedText_)
4229 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
4230 // doesn't call any NSTextInput functions, such as setMarkedText or
4231 // insertText. So, we need to send an IPC message to a renderer so it can
4232 // delete the composition node.
4233 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
4234 [currentInputManager markedTextAbandoned:self];
4236 hasMarkedText_ = NO;
4237 // Should not call [self unmarkText] here, because it'll send unnecessary
4238 // cancel composition IPC message to the renderer.
4241 - (void)confirmComposition {
4242 if (!hasMarkedText_)
4245 if (renderWidgetHostView_->render_widget_host_)
4246 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
4247 base::string16(), gfx::Range::InvalidRange(), false);
4249 [self cancelComposition];
4252 - (void)setPluginImeActive:(BOOL)active {
4253 if (active == pluginImeActive_)
4256 pluginImeActive_ = active;
4258 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
4259 renderWidgetHostView_->PluginImeCompositionCompleted(
4260 base::string16(), focusedPluginIdentifier_);
4264 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
4266 focusedPluginIdentifier_ = pluginId;
4267 else if (focusedPluginIdentifier_ == pluginId)
4268 focusedPluginIdentifier_ = -1;
4270 // Whenever plugin focus changes, plugin IME resets.
4271 [self setPluginImeActive:NO];
4274 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
4275 if (!pluginImeActive_)
4278 ComplexTextInputPanel* inputPanel =
4279 [ComplexTextInputPanel sharedComplexTextInputPanel];
4280 NSString* composited_string = nil;
4281 BOOL handled = [inputPanel interpretKeyEvent:event
4282 string:&composited_string];
4283 if (composited_string) {
4284 renderWidgetHostView_->PluginImeCompositionCompleted(
4285 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
4286 pluginImeActive_ = NO;
4291 - (void)checkForPluginImeCancellation {
4292 if (pluginImeActive_ &&
4293 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
4294 renderWidgetHostView_->PluginImeCompositionCompleted(
4295 base::string16(), focusedPluginIdentifier_);
4296 pluginImeActive_ = NO;
4300 // Overriding a NSResponder method to support application services.
4302 - (id)validRequestorForSendType:(NSString*)sendType
4303 returnType:(NSString*)returnType {
4305 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
4306 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
4307 BOOL hasText = !renderWidgetHostView_->selected_text().empty();
4309 renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
4311 if (sendTypeIsString && hasText && !returnType) {
4313 } else if (!sendType && returnTypeIsString && takesText) {
4315 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
4318 requestor = [super validRequestorForSendType:sendType
4319 returnType:returnType];
4324 - (void)viewWillStartLiveResize {
4325 [super viewWillStartLiveResize];
4326 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
4328 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
4331 - (void)viewDidEndLiveResize {
4332 [super viewDidEndLiveResize];
4333 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
4335 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
4338 - (void)updateCursor:(NSCursor*)cursor {
4339 if (currentCursor_ == cursor)
4342 currentCursor_.reset([cursor retain]);
4343 [[self window] invalidateCursorRectsForView:self];
4346 - (void)popupWindowWillClose:(NSNotification *)notification {
4347 renderWidgetHostView_->KillSelf();
4353 // Supporting application services
4355 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
4357 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
4358 types:(NSArray*)types {
4359 const std::string& str = renderWidgetHostView_->selected_text();
4360 if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
4362 base::scoped_nsobject<NSString> text(
4363 [[NSString alloc] initWithUTF8String:str.c_str()]);
4364 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
4365 [pboard declareTypes:toDeclare owner:nil];
4366 return [pboard setString:text forType:NSStringPboardType];
4369 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
4370 NSString *string = [pboard stringForType:NSStringPboardType];
4371 if (!string) return NO;
4373 // If the user is currently using an IME, confirm the IME input,
4374 // and then insert the text from the service, the same as TextEdit and Safari.
4375 [self confirmComposition];
4376 [self insertText:string];
4381 if (renderWidgetHostView_->use_core_animation_)
4383 return [super isOpaque];
4386 // "-webkit-app-region: drag | no-drag" is implemented on Mac by excluding
4387 // regions that are not draggable. (See ControlRegionView in
4388 // native_app_window_cocoa.mm). This requires the render host view to be
4389 // draggable by default.
4390 - (BOOL)mouseDownCanMoveWindow {