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/frame_host/frame_tree.h"
31 #include "content/browser/frame_host/frame_tree_node.h"
32 #include "content/browser/frame_host/render_frame_host_impl.h"
33 #include "content/browser/renderer_host/backing_store_mac.h"
34 #include "content/browser/renderer_host/backing_store_manager.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_view_host_impl.h"
39 #import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
40 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
41 #import "content/browser/renderer_host/text_input_client_mac.h"
42 #include "content/common/accessibility_messages.h"
43 #include "content/common/edit_command.h"
44 #include "content/common/gpu/gpu_messages.h"
45 #include "content/common/input_messages.h"
46 #include "content/common/view_messages.h"
47 #include "content/common/webplugin_geometry.h"
48 #include "content/port/browser/render_widget_host_view_frame_subscriber.h"
49 #include "content/public/browser/browser_thread.h"
50 #include "content/public/browser/native_web_keyboard_event.h"
51 #include "content/public/browser/notification_service.h"
52 #include "content/public/browser/notification_types.h"
53 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
54 #include "content/public/browser/user_metrics.h"
55 #include "skia/ext/platform_canvas.h"
56 #include "third_party/WebKit/public/platform/WebScreenInfo.h"
57 #include "third_party/WebKit/public/web/WebInputEvent.h"
58 #include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
59 #import "third_party/mozilla/ComplexTextInputPanel.h"
60 #include "ui/base/cocoa/animation_utils.h"
61 #import "ui/base/cocoa/fullscreen_window_manager.h"
62 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
63 #include "ui/events/keycodes/keyboard_codes.h"
64 #include "ui/base/layout.h"
65 #include "ui/gfx/display.h"
66 #include "ui/gfx/point.h"
67 #include "ui/gfx/rect_conversions.h"
68 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
69 #include "ui/gfx/screen.h"
70 #include "ui/gfx/size_conversions.h"
71 #include "ui/gl/gl_switches.h"
72 #include "ui/gl/io_surface_support_mac.h"
74 using content::BackingStoreMac;
75 using content::BrowserAccessibility;
76 using content::BrowserAccessibilityManager;
77 using content::EditCommand;
78 using content::FrameTreeNode;
79 using content::NativeWebKeyboardEvent;
80 using content::RenderFrameHost;
81 using content::RenderViewHost;
82 using content::RenderViewHostImpl;
83 using content::RenderWidgetHostImpl;
84 using content::RenderWidgetHostViewMac;
85 using content::RenderWidgetHostViewMacEditCommandHelper;
86 using content::TextInputClientMac;
87 using blink::WebInputEvent;
88 using blink::WebInputEventFactory;
89 using blink::WebMouseEvent;
90 using blink::WebMouseWheelEvent;
92 // These are not documented, so use only after checking -respondsToSelector:.
93 @interface NSApplication (UndocumentedSpeechMethods)
94 - (void)speakString:(NSString*)string;
95 - (void)stopSpeaking:(id)sender;
99 // Declare things that are part of the 10.7 SDK.
100 #if !defined(MAC_OS_X_VERSION_10_7) || \
101 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
102 @interface NSView (NSOpenGLSurfaceResolutionLionAPI)
103 - (void)setWantsBestResolutionOpenGLSurface:(BOOL)flag;
106 static NSString* const NSWindowDidChangeBackingPropertiesNotification =
107 @"NSWindowDidChangeBackingPropertiesNotification";
111 // This method will return YES for OS X versions 10.7.3 and later, and NO
113 // Used to prevent a crash when building with the 10.7 SDK and accessing the
114 // notification below. See: http://crbug.com/260595.
115 static BOOL SupportsBackingPropertiesChangedNotification() {
116 // windowDidChangeBackingProperties: method has been added to the
117 // NSWindowDelegate protocol in 10.7.3, at the same time as the
118 // NSWindowDidChangeBackingPropertiesNotification notification was added.
119 // If the protocol contains this method description, the notification should
120 // be supported as well.
121 Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
122 struct objc_method_description methodDescription =
123 protocol_getMethodDescription(
124 windowDelegateProtocol,
125 @selector(windowDidChangeBackingProperties:),
129 // If the protocol does not contain the method, the returned method
130 // description is {NULL, NULL}
131 return methodDescription.name != NULL || methodDescription.types != NULL;
134 static float ScaleFactorForView(NSView* view) {
135 return ui::GetImageScale(ui::GetScaleFactorForNativeView(view));
139 @interface RenderWidgetHostViewCocoa ()
140 @property(nonatomic, assign) NSRange selectedRange;
141 @property(nonatomic, assign) NSRange markedRange;
143 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
144 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
145 - (void)gotUnhandledWheelEvent;
146 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right;
147 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar;
148 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
149 - (void)windowDidChangeBackingProperties:(NSNotification*)notification;
150 - (void)windowChangedGlobalFrame:(NSNotification*)notification;
151 - (void)drawBackingStore:(BackingStoreMac*)backingStore
152 dirtyRect:(CGRect)dirtyRect
153 inContext:(CGContextRef)context;
154 - (void)checkForPluginImeCancellation;
155 - (void)updateScreenProperties;
156 - (void)setResponderDelegate:
157 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate;
160 // A window subclass that allows the fullscreen window to become main and gain
161 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
162 // handled by the browser.
163 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
166 @implementation PepperFlashFullscreenWindow
168 - (BOOL)canBecomeKeyWindow {
172 - (BOOL)canBecomeMainWindow {
178 @interface RenderWidgetPopupWindow : NSWindow {
179 // The event tap that allows monitoring of all events, to properly close with
180 // a click outside the bounds of the window.
185 @implementation RenderWidgetPopupWindow
187 - (id)initWithContentRect:(NSRect)contentRect
188 styleMask:(NSUInteger)windowStyle
189 backing:(NSBackingStoreType)bufferingType
190 defer:(BOOL)deferCreation {
191 if (self = [super initWithContentRect:contentRect
192 styleMask:windowStyle
193 backing:bufferingType
194 defer:deferCreation]) {
195 DCHECK_EQ(content::CORE_ANIMATION_DISABLED,
196 content::GetCoreAnimationStatus());
198 [self setBackgroundColor:[NSColor clearColor]];
199 [self startObservingClicks];
205 [self stopObservingClicks];
209 // Gets called when the menubar is clicked.
210 // Needed because the local event monitor doesn't see the click on the menubar.
211 - (void)beganTracking:(NSNotification*)notification {
215 // Install the callback.
216 - (void)startObservingClicks {
217 clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
218 handler:^NSEvent* (NSEvent* event) {
219 if ([event window] == self)
221 NSEventType eventType = [event type];
222 if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
227 NSNotificationCenter* notificationCenter =
228 [NSNotificationCenter defaultCenter];
229 [notificationCenter addObserver:self
230 selector:@selector(beganTracking:)
231 name:NSMenuDidBeginTrackingNotification
232 object:[NSApp mainMenu]];
235 // Remove the callback.
236 - (void)stopObservingClicks {
240 [NSEvent removeMonitor:clickEventTap_];
241 clickEventTap_ = nil;
243 NSNotificationCenter* notificationCenter =
244 [NSNotificationCenter defaultCenter];
245 [notificationCenter removeObserver:self
246 name:NSMenuDidBeginTrackingNotification
247 object:[NSApp mainMenu]];
254 // Maximum number of characters we allow in a tooltip.
255 const size_t kMaxTooltipLength = 1024;
257 // TODO(suzhe): Upstream this function.
258 blink::WebColor WebColorFromNSColor(NSColor *color) {
260 [color getRed:&r green:&g blue:&b alpha:&a];
263 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
264 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
265 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
266 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
269 // Extract underline information from an attributed string. Mostly copied from
270 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
271 void ExtractUnderlines(
272 NSAttributedString* string,
273 std::vector<blink::WebCompositionUnderline>* underlines) {
274 int length = [[string string] length];
278 NSDictionary* attrs = [string attributesAtIndex:i
279 longestEffectiveRange:&range
280 inRange:NSMakeRange(i, length - i)];
281 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
282 blink::WebColor color = SK_ColorBLACK;
283 if (NSColor *colorAttr =
284 [attrs objectForKey:NSUnderlineColorAttributeName]) {
285 color = WebColorFromNSColor(
286 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
288 underlines->push_back(blink::WebCompositionUnderline(
289 range.location, NSMaxRange(range), color, [style intValue] > 1));
291 i = range.location + range.length;
295 // EnablePasswordInput() and DisablePasswordInput() are copied from
296 // enableSecureTextInput() and disableSecureTextInput() functions in
297 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
298 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
299 // here, because they are already called in webkit and they are system wide
301 void EnablePasswordInput() {
302 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
303 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
304 sizeof(CFArrayRef), &inputSources);
305 CFRelease(inputSources);
308 void DisablePasswordInput() {
309 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
312 // Calls to [NSScreen screens], required by FlipYFromRectToScreen and
313 // FlipNSRectToRectScreen, can take several milliseconds. Only re-compute this
314 // value when screen info changes.
315 // TODO(ccameron): An observer on every RWHVCocoa will set this to false
316 // on NSApplicationDidChangeScreenParametersNotification. Only one observer
318 bool g_screen_info_up_to_date = false;
320 float FlipYFromRectToScreen(float y, float rect_height) {
321 TRACE_EVENT0("browser", "FlipYFromRectToScreen");
322 static CGFloat screen_zero_height = 0;
323 if (!g_screen_info_up_to_date) {
324 if ([[NSScreen screens] count] > 0) {
326 [[[NSScreen screens] objectAtIndex:0] frame].size.height;
327 g_screen_info_up_to_date = true;
332 return screen_zero_height - y - rect_height;
335 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
336 // left of the primary screen (Carbon coordinates), and stuffs it into a
338 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
339 gfx::Rect new_rect(NSRectToCGRect(rect));
340 new_rect.set_y(FlipYFromRectToScreen(new_rect.y(), new_rect.height()));
344 // Returns the window that visually contains the given view. This is different
345 // from [view window] in the case of tab dragging, where the view's owning
346 // window is a floating panel attached to the actual browser window that the tab
347 // is visually part of.
348 NSWindow* ApparentWindowForView(NSView* view) {
349 // TODO(shess): In case of !window, the view has been removed from
350 // the view hierarchy because the tab isn't main. Could retrieve
351 // the information from the main tab for our window.
352 NSWindow* enclosing_window = [view window];
354 // See if this is a tab drag window. The width check is to distinguish that
355 // case from extension popup windows.
356 NSWindow* ancestor_window = [enclosing_window parentWindow];
357 if (ancestor_window && (NSWidth([enclosing_window frame]) ==
358 NSWidth([ancestor_window frame]))) {
359 enclosing_window = ancestor_window;
362 return enclosing_window;
365 blink::WebScreenInfo GetWebScreenInfo(NSView* view) {
366 gfx::Display display =
367 gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
369 NSScreen* screen = [NSScreen deepestScreen];
371 blink::WebScreenInfo results;
373 results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
374 results.depth = NSBitsPerPixelFromDepth([screen depth]);
375 results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
376 results.isMonochrome =
377 [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
378 results.rect = display.bounds();
379 results.availableRect = display.work_area();
383 void RemoveLayerFromSuperlayer(
384 base::scoped_nsobject<CompositingIOSurfaceLayer> layer) {
385 // Disable the fade-out animation as the layer is removed.
386 ScopedCAActionDisabler disabler;
387 [layer removeFromSuperlayer];
394 ///////////////////////////////////////////////////////////////////////////////
395 // RenderWidgetHostView, public:
398 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
399 RenderWidgetHost* widget) {
400 return new RenderWidgetHostViewMac(widget);
404 void RenderWidgetHostViewPort::GetDefaultScreenInfo(
405 blink::WebScreenInfo* results) {
406 *results = GetWebScreenInfo(NULL);
409 ///////////////////////////////////////////////////////////////////////////////
410 // RenderWidgetHostViewMac, public:
412 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
413 : render_widget_host_(RenderWidgetHostImpl::From(widget)),
414 last_frame_was_accelerated_(false),
415 text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
416 can_compose_inline_(true),
417 compositing_iosurface_layer_async_timer_(
418 FROM_HERE, base::TimeDelta::FromMilliseconds(250),
419 this, &RenderWidgetHostViewMac::TimerSinceGotAcceleratedFrameFired),
420 allow_overlapping_views_(false),
421 use_core_animation_(false),
422 pending_latency_info_delay_(0),
423 pending_latency_info_delay_weak_ptr_factory_(this),
424 backing_store_scale_factor_(1),
427 fullscreen_parent_host_view_(NULL),
428 underlay_view_has_drawn_(false),
429 overlay_view_weak_factory_(this),
430 software_frame_weak_ptr_factory_(this) {
431 software_frame_manager_.reset(new SoftwareFrameManager(
432 software_frame_weak_ptr_factory_.GetWeakPtr()));
433 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
434 // goes away. Since we autorelease it, our caller must put
435 // |GetNativeView()| into the view hierarchy right after calling us.
436 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
437 initWithRenderWidgetHostViewMac:this] autorelease];
439 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED) {
440 use_core_animation_ = true;
441 background_layer_.reset([[CALayer alloc] init]);
443 setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
444 [cocoa_view_ setLayer:background_layer_];
445 [cocoa_view_ setWantsLayer:YES];
448 render_widget_host_->SetView(this);
451 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
452 // This is being called from |cocoa_view_|'s destructor, so invalidate the
458 // Make sure that the layer doesn't reach into the now-invalid object.
459 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
460 DestroySoftwareLayer();
462 // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
463 // RenderWidgetHost does we need to tell it not to hold a stale pointer to
465 if (render_widget_host_)
466 render_widget_host_->SetView(NULL);
469 void RenderWidgetHostViewMac::SetDelegate(
470 NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
471 [cocoa_view_ setResponderDelegate:delegate];
474 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
475 if (allow_overlapping_views_ == overlapping)
477 allow_overlapping_views_ = overlapping;
478 [cocoa_view_ setNeedsDisplay:YES];
479 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
482 ///////////////////////////////////////////////////////////////////////////////
483 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
485 bool RenderWidgetHostViewMac::EnsureCompositedIOSurface() {
486 // If the context or the IOSurface's context has had an error, re-build
487 // everything from scratch.
488 if (compositing_iosurface_context_ &&
489 compositing_iosurface_context_->HasBeenPoisoned()) {
490 LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
491 << "context was poisoned";
494 if (compositing_iosurface_ &&
495 compositing_iosurface_->HasBeenPoisoned()) {
496 LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
497 << "surface was poisoned";
501 int current_window_number = use_core_animation_ ?
502 CompositingIOSurfaceContext::kOffscreenContextWindowNumber :
504 bool new_surface_needed = !compositing_iosurface_;
505 bool new_context_needed =
506 !compositing_iosurface_context_ ||
507 (compositing_iosurface_context_ &&
508 compositing_iosurface_context_->window_number() !=
509 current_window_number);
511 if (!new_surface_needed && !new_context_needed)
514 // Create the GL context and shaders.
515 if (new_context_needed) {
516 scoped_refptr<CompositingIOSurfaceContext> new_context =
517 CompositingIOSurfaceContext::Get(current_window_number);
518 // Un-bind the GL context from this view before binding the new GL
519 // context. Having two GL contexts bound to a view will result in
520 // crashes and corruption.
521 // http://crbug.com/230883
522 ClearBoundContextDrawable();
524 LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
527 compositing_iosurface_context_ = new_context;
530 // Create the IOSurface texture.
531 if (new_surface_needed) {
532 compositing_iosurface_.reset(CompositingIOSurfaceMac::Create());
533 if (!compositing_iosurface_) {
534 LOG(ERROR) << "Failed to create CompositingIOSurface";
542 void RenderWidgetHostViewMac::EnsureSoftwareLayer() {
543 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::EnsureSoftwareLayer");
544 if (software_layer_ || !use_core_animation_)
547 software_layer_.reset([[SoftwareLayer alloc]
548 initWithRenderWidgetHostViewMac:this]);
549 DCHECK(software_layer_);
551 // Disable the fade-in animation as the layer is added.
552 ScopedCAActionDisabler disabler;
553 [background_layer_ addSublayer:software_layer_];
556 void RenderWidgetHostViewMac::DestroySoftwareLayer() {
557 if (!software_layer_)
560 // Disable the fade-out animation as the layer is removed.
561 ScopedCAActionDisabler disabler;
562 [software_layer_ removeFromSuperlayer];
563 [software_layer_ disableRendering];
564 software_layer_.reset();
567 void RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer() {
568 TRACE_EVENT0("browser",
569 "RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer");
570 DCHECK(compositing_iosurface_context_);
571 if (compositing_iosurface_layer_ || !use_core_animation_)
574 compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
575 initWithRenderWidgetHostViewMac:this]);
576 DCHECK(compositing_iosurface_layer_);
578 // Disable the fade-in animation as the layer is added.
579 ScopedCAActionDisabler disabler;
580 [background_layer_ addSublayer:compositing_iosurface_layer_];
583 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceLayer(
584 DestroyCompositedIOSurfaceLayerBehavior destroy_layer_behavior) {
585 if (!compositing_iosurface_layer_)
588 if (destroy_layer_behavior == kRemoveLayerFromHierarchy) {
589 // Disable the fade-out animation as the layer is removed.
590 ScopedCAActionDisabler disabler;
591 [compositing_iosurface_layer_ removeFromSuperlayer];
593 [compositing_iosurface_layer_ disableCompositing];
594 compositing_iosurface_layer_.reset();
597 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer(
598 DestroyContextBehavior destroy_context_behavior) {
599 // Any pending frames will not be displayed, so ack them now.
600 SendPendingSwapAck();
602 DestroyCompositedIOSurfaceLayer(kRemoveLayerFromHierarchy);
603 compositing_iosurface_.reset();
605 switch (destroy_context_behavior) {
606 case kLeaveContextBoundToView:
608 case kDestroyContext:
609 ClearBoundContextDrawable();
610 compositing_iosurface_context_ = NULL;
618 void RenderWidgetHostViewMac::ClearBoundContextDrawable() {
619 if (use_core_animation_)
622 if (compositing_iosurface_context_ &&
624 [[compositing_iosurface_context_->nsgl_context() view]
625 isEqual:cocoa_view_]) {
626 // Disable screen updates because removing the GL context from below can
628 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
629 [compositing_iosurface_context_->nsgl_context() clearDrawable];
633 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
635 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
636 IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
637 IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
638 IPC_MESSAGE_UNHANDLED(handled = false)
639 IPC_END_MESSAGE_MAP()
643 void RenderWidgetHostViewMac::InitAsChild(
644 gfx::NativeView parent_view) {
647 void RenderWidgetHostViewMac::InitAsPopup(
648 RenderWidgetHostView* parent_host_view,
649 const gfx::Rect& pos) {
650 bool activatable = popup_type_ == blink::WebPopupTypeNone;
651 [cocoa_view_ setCloseOnDeactivate:YES];
652 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
654 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
655 origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height());
657 popup_window_.reset([[RenderWidgetPopupWindow alloc]
658 initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
659 pos.width(), pos.height())
660 styleMask:NSBorderlessWindowMask
661 backing:NSBackingStoreBuffered
663 [popup_window_ setLevel:NSPopUpMenuWindowLevel];
664 [popup_window_ setReleasedWhenClosed:NO];
665 [popup_window_ makeKeyAndOrderFront:nil];
666 [[popup_window_ contentView] addSubview:cocoa_view_];
667 [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
668 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
669 [[NSNotificationCenter defaultCenter]
670 addObserver:cocoa_view_
671 selector:@selector(popupWindowWillClose:)
672 name:NSWindowWillCloseNotification
673 object:popup_window_];
676 // This function creates the fullscreen window and hides the dock and menubar if
677 // necessary. Note, this codepath is only used for pepper flash when
678 // pp::FlashFullScreen::SetFullscreen() is called. If
679 // pp::FullScreen::SetFullscreen() is called then the entire browser window
680 // will enter fullscreen instead.
681 void RenderWidgetHostViewMac::InitAsFullscreen(
682 RenderWidgetHostView* reference_host_view) {
683 fullscreen_parent_host_view_ =
684 static_cast<RenderWidgetHostViewMac*>(reference_host_view);
685 NSWindow* parent_window = nil;
686 if (reference_host_view)
687 parent_window = [reference_host_view->GetNativeView() window];
688 NSScreen* screen = [parent_window screen];
690 screen = [NSScreen mainScreen];
692 pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
693 initWithContentRect:[screen frame]
694 styleMask:NSBorderlessWindowMask
695 backing:NSBackingStoreBuffered
697 [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
698 [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
699 [cocoa_view_ setCanBeKeyView:YES];
700 [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
701 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
702 // If the pepper fullscreen window isn't opaque then there are performance
703 // issues when it's on the discrete GPU and the Chrome window is being drawn
704 // to. http://crbug.com/171911
705 [pepper_fullscreen_window_ setOpaque:YES];
707 // Note that this forms a reference cycle between the fullscreen window and
708 // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
709 // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
710 // This cycle is normally broken when -keyEvent: receives an <esc> key, which
711 // explicitly calls Shutdown on the render_widget_host_, which calls
712 // Destroy() on RWHVMac, which drops the reference to
713 // pepper_fullscreen_window_.
714 [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
716 // Note that this keeps another reference to pepper_fullscreen_window_.
717 fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
718 initWithWindow:pepper_fullscreen_window_.get()
719 desiredScreen:screen]);
720 [fullscreen_window_manager_ enterFullscreenMode];
721 [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
724 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
725 // See comment in InitAsFullscreen(): There is a reference cycle between
726 // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
727 // Tests that test pepper fullscreen mode without sending an <esc> event
728 // need to call this method to break the reference cycle.
729 [fullscreen_window_manager_ exitFullscreenMode];
730 fullscreen_window_manager_.reset();
731 [pepper_fullscreen_window_ close];
732 pepper_fullscreen_window_.reset();
735 int RenderWidgetHostViewMac::window_number() const {
736 NSWindow* window = [cocoa_view_ window];
739 return [window windowNumber];
742 float RenderWidgetHostViewMac::ViewScaleFactor() const {
743 return ScaleFactorForView(cocoa_view_);
746 void RenderWidgetHostViewMac::UpdateDisplayLink() {
747 static bool is_vsync_disabled =
748 CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync);
749 if (is_vsync_disabled)
752 NSScreen* screen = [[cocoa_view_ window] screen];
753 NSDictionary* screen_description = [screen deviceDescription];
754 NSNumber* screen_number = [screen_description objectForKey:@"NSScreenNumber"];
755 CGDirectDisplayID display_id = [screen_number unsignedIntValue];
757 display_link_ = DisplayLinkMac::GetForDisplay(display_id);
758 if (!display_link_) {
759 // Note that on some headless systems, the display link will fail to be
760 // created, so this should not be a fatal error.
761 LOG(ERROR) << "Failed to create display link.";
765 void RenderWidgetHostViewMac::SendVSyncParametersToRenderer() {
766 if (!render_widget_host_ || !display_link_)
769 base::TimeTicks timebase;
770 base::TimeDelta interval;
771 if (!display_link_->GetVSyncParameters(&timebase, &interval))
774 render_widget_host_->UpdateVSyncParameters(timebase, interval);
777 void RenderWidgetHostViewMac::UpdateBackingStoreScaleFactor() {
778 if (!render_widget_host_)
781 float new_scale_factor = ScaleFactorForView(cocoa_view_);
782 if (new_scale_factor == backing_store_scale_factor_)
784 backing_store_scale_factor_ = new_scale_factor;
786 BackingStoreMac* backing_store = static_cast<BackingStoreMac*>(
787 render_widget_host_->GetBackingStore(false));
789 backing_store->ScaleFactorChanged(backing_store_scale_factor_);
791 render_widget_host_->NotifyScreenInfoChanged();
794 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
795 return render_widget_host_;
798 void RenderWidgetHostViewMac::WasShown() {
799 if (!render_widget_host_->is_hidden())
802 if (web_contents_switch_paint_time_.is_null())
803 web_contents_switch_paint_time_ = base::TimeTicks::Now();
804 render_widget_host_->WasShown();
805 software_frame_manager_->SetVisibility(true);
807 // Call setNeedsDisplay before pausing for new frames to come in -- if any
808 // do, and are drawn, then the needsDisplay bit will be cleared.
809 [software_layer_ setNeedsDisplay];
810 [compositing_iosurface_layer_ setNeedsDisplay];
811 PauseForPendingResizeOrRepaintsAndDraw();
813 // We're messing with the window, so do this to ensure no flashes.
814 if (!use_core_animation_)
815 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
818 void RenderWidgetHostViewMac::WasHidden() {
819 if (render_widget_host_->is_hidden())
822 // Any pending frames will not be displayed until this is shown again. Ack
824 SendPendingSwapAck();
826 // If we have a renderer, then inform it that we are being hidden so it can
827 // reduce its resource utilization.
828 render_widget_host_->WasHidden();
829 software_frame_manager_->SetVisibility(false);
831 // There can be a transparent flash as this view is removed and the next is
832 // added, because of OSX windowing races between displaying the contents of
833 // the NSView and its corresponding OpenGL context.
834 // disableScreenUpdatesUntilFlush prevents the transparent flash by avoiding
835 // screen updates until the next tab draws.
836 if (!use_core_animation_)
837 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
839 web_contents_switch_paint_time_ = base::TimeTicks();
842 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
843 gfx::Rect rect = GetViewBounds();
848 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
849 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
850 // TODO(thakis): fix, http://crbug.com/73362
851 if (render_widget_host_->is_hidden())
854 // During the initial creation of the RenderWidgetHostView in
855 // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
856 // an empty size. In the Windows code flow, it is not ignored because
857 // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
858 // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
859 // flags to keep things sized properly. On the other hand, if the size is not
860 // empty then this is a valid request for a pop-up.
861 if (rect.size().IsEmpty())
864 // Ignore the position of |rect| for non-popup rwhvs. This is because
865 // background tabs do not have a window, but the window is required for the
866 // coordinate conversions. Popups are always for a visible tab.
868 // Note: If |cocoa_view_| has been removed from the view hierarchy, it's still
869 // valid for resizing to be requested (e.g., during tab capture, to size the
870 // view to screen-capture resolution). In this case, simply treat the view as
871 // relative to the screen.
872 BOOL isRelativeToScreen = IsPopup() ||
873 ![[cocoa_view_ superview] isKindOfClass:[BaseView class]];
874 if (isRelativeToScreen) {
875 // The position of |rect| is screen coordinate system and we have to
876 // consider Cocoa coordinate system is upside-down and also multi-screen.
877 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
878 NSSize size = NSMakeSize(rect.width(), rect.height());
879 size = [cocoa_view_ convertSize:size toView:nil];
880 origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height);
881 NSRect frame = NSMakeRect(origin_global.x, origin_global.y,
882 size.width, size.height);
884 [popup_window_ setFrame:frame display:YES];
886 [cocoa_view_ setFrame:frame];
888 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
889 gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
890 rect2.set_width(rect.width());
891 rect2.set_height(rect.height());
892 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
896 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
900 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
901 return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
904 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
906 return static_cast<gfx::NativeViewAccessible>(NULL);
909 void RenderWidgetHostViewMac::MovePluginWindows(
910 const gfx::Vector2d& scroll_offset,
911 const std::vector<WebPluginGeometry>& moves) {
912 // Must be overridden, but unused on this platform. Core Animation
913 // plugins are drawn by the GPU process (through the compositor),
914 // and Core Graphics plugins are drawn by the renderer process.
915 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
918 void RenderWidgetHostViewMac::Focus() {
919 [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
922 void RenderWidgetHostViewMac::Blur() {
924 [[cocoa_view_ window] makeFirstResponder:nil];
927 bool RenderWidgetHostViewMac::HasFocus() const {
928 return [[cocoa_view_ window] firstResponder] == cocoa_view_;
931 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
932 return software_frame_manager_->HasCurrentFrame() ||
933 (compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) ||
934 !!render_widget_host_->GetBackingStore(false);
937 void RenderWidgetHostViewMac::Show() {
938 [cocoa_view_ setHidden:NO];
943 void RenderWidgetHostViewMac::Hide() {
944 // We're messing with the window, so do this to ensure no flashes.
945 if (!use_core_animation_)
946 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
948 [cocoa_view_ setHidden:YES];
953 bool RenderWidgetHostViewMac::IsShowing() {
954 return ![cocoa_view_ isHidden];
957 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
958 NSRect bounds = [cocoa_view_ bounds];
959 // TODO(shess): In case of !window, the view has been removed from
960 // the view hierarchy because the tab isn't main. Could retrieve
961 // the information from the main tab for our window.
962 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
963 if (!enclosing_window)
964 return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
966 bounds = [cocoa_view_ convertRect:bounds toView:nil];
967 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
968 return FlipNSRectToRectScreen(bounds);
971 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
972 WebCursor web_cursor = cursor;
973 [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
976 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
977 is_loading_ = is_loading;
978 // If we ever decide to show the waiting cursor while the page is loading
979 // like Chrome does on Windows, call |UpdateCursor()| here.
982 void RenderWidgetHostViewMac::TextInputTypeChanged(
983 ui::TextInputType type,
984 ui::TextInputMode input_mode,
985 bool can_compose_inline) {
986 if (text_input_type_ != type
987 || can_compose_inline_ != can_compose_inline) {
988 text_input_type_ = type;
989 can_compose_inline_ = can_compose_inline;
991 SetTextInputActive(true);
993 // Let AppKit cache the new input context to make IMEs happy.
994 // See http://crbug.com/73039.
995 [NSApp updateWindows];
998 UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
1004 void RenderWidgetHostViewMac::ImeCancelComposition() {
1005 [cocoa_view_ cancelComposition];
1008 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
1009 const gfx::Range& range,
1010 const std::vector<gfx::Rect>& character_bounds) {
1011 // The RangeChanged message is only sent with valid values. The current
1012 // caret position (start == end) will be sent if there is no IME range.
1013 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1014 composition_range_ = range;
1015 composition_bounds_ = character_bounds;
1018 void RenderWidgetHostViewMac::DidUpdateBackingStore(
1019 const gfx::Rect& scroll_rect,
1020 const gfx::Vector2d& scroll_delta,
1021 const std::vector<gfx::Rect>& copy_rects,
1022 const std::vector<ui::LatencyInfo>& latency_info) {
1023 // This can be called while already inside of a display function. Process the
1024 // new frame in a callback to ensure a clean stack.
1025 // TODO(ccameron): This should never be called. Remove the remaining callers
1026 // and remove all places where the backing store is drawn.
1027 AddPendingLatencyInfo(latency_info);
1028 base::MessageLoop::current()->PostTask(
1030 base::Bind(&RenderWidgetHostViewMac::GotSoftwareFrame,
1031 weak_factory_.GetWeakPtr()));
1034 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
1039 void RenderWidgetHostViewMac::Destroy() {
1040 [[NSNotificationCenter defaultCenter]
1041 removeObserver:cocoa_view_
1042 name:NSWindowWillCloseNotification
1043 object:popup_window_];
1045 // We've been told to destroy.
1046 [cocoa_view_ retain];
1047 [cocoa_view_ removeFromSuperview];
1048 [cocoa_view_ autorelease];
1050 [popup_window_ close];
1051 popup_window_.autorelease();
1053 [fullscreen_window_manager_ exitFullscreenMode];
1054 fullscreen_window_manager_.reset();
1055 [pepper_fullscreen_window_ close];
1057 // This can be called as part of processing the window's responder
1058 // chain, for instance |-performKeyEquivalent:|. In that case the
1059 // object needs to survive until the stack unwinds.
1060 pepper_fullscreen_window_.autorelease();
1062 // We get this call just before |render_widget_host_| deletes
1063 // itself. But we are owned by |cocoa_view_|, which may be retained
1064 // by some other code. Examples are WebContentsViewMac's
1065 // |latent_focus_view_| and TabWindowController's
1066 // |cachedContentView_|.
1067 render_widget_host_ = NULL;
1070 // Called from the renderer to tell us what the tooltip text should be. It
1071 // calls us frequently so we need to cache the value to prevent doing a lot
1073 void RenderWidgetHostViewMac::SetTooltipText(
1074 const base::string16& tooltip_text) {
1075 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1076 tooltip_text_ = tooltip_text;
1078 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1079 // Windows; we're just trying to be polite. Don't persist the trimmed
1080 // string, as then the comparison above will always fail and we'll try to
1081 // set it again every single time the mouse moves.
1082 base::string16 display_text = tooltip_text_;
1083 if (tooltip_text_.length() > kMaxTooltipLength)
1084 display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1086 NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1087 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1091 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1092 return [NSApp respondsToSelector:@selector(speakString:)] &&
1093 [NSApp respondsToSelector:@selector(stopSpeaking:)];
1096 void RenderWidgetHostViewMac::SpeakSelection() {
1097 if ([NSApp respondsToSelector:@selector(speakString:)])
1098 [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
1101 bool RenderWidgetHostViewMac::IsSpeaking() const {
1102 return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1106 void RenderWidgetHostViewMac::StopSpeaking() {
1107 if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1108 [NSApp stopSpeaking:cocoa_view_];
1112 // RenderWidgetHostViewCocoa uses the stored selection text,
1113 // which implements NSServicesRequests protocol.
1115 void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text,
1117 const gfx::Range& range) {
1118 if (range.is_empty() || text.empty()) {
1119 selected_text_.clear();
1121 size_t pos = range.GetMin() - offset;
1122 size_t n = range.length();
1124 DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1125 if (pos >= text.length()) {
1126 DCHECK(false) << "The text can not cover range.";
1129 selected_text_ = base::UTF16ToUTF8(text.substr(pos, n));
1132 [cocoa_view_ setSelectedRange:range.ToNSRange()];
1133 // Updates markedRange when there is no marked text so that retrieving
1134 // markedRange immediately after calling setMarkdText: returns the current
1136 if (![cocoa_view_ hasMarkedText]) {
1137 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1140 RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1143 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1144 const ViewHostMsg_SelectionBounds_Params& params) {
1145 if (params.anchor_rect == params.focus_rect)
1146 caret_rect_ = params.anchor_rect;
1149 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1152 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1153 RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1155 // Create a fake mouse event to inform the render widget that the mouse
1157 NSWindow* window = [cocoa_view_ window];
1158 // TODO(asvitkine): If the location outside of the event stream doesn't
1159 // correspond to the current event (due to delayed event processing), then
1160 // this may result in a cursor flicker if there are later mouse move events
1161 // in the pipeline. Find a way to use the mouse location from the event that
1162 // dismissed the context menu.
1163 NSPoint location = [window mouseLocationOutsideOfEventStream];
1164 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1168 windowNumber:window_number()
1173 WebMouseEvent web_event =
1174 WebInputEventFactory::mouseEvent(event, cocoa_view_);
1176 web_event.type = WebInputEvent::MouseLeave;
1177 ForwardMouseEvent(web_event);
1180 bool RenderWidgetHostViewMac::IsPopup() const {
1181 return popup_type_ != blink::WebPopupTypeNone;
1184 BackingStore* RenderWidgetHostViewMac::AllocBackingStore(
1185 const gfx::Size& size) {
1186 float scale = ScaleFactorForView(cocoa_view_);
1187 return new BackingStoreMac(render_widget_host_, size, scale);
1190 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1191 const gfx::Rect& src_subrect,
1192 const gfx::Size& dst_size,
1193 const base::Callback<void(bool, const SkBitmap&)>& callback,
1194 const SkBitmap::Config config) {
1195 if (config != SkBitmap::kARGB_8888_Config) {
1197 callback.Run(false, SkBitmap());
1199 base::ScopedClosureRunner scoped_callback_runner(
1200 base::Bind(callback, false, SkBitmap()));
1201 float scale = ScaleFactorForView(cocoa_view_);
1202 gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1203 gfx::ScaleSize(dst_size, scale));
1204 if (compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) {
1205 ignore_result(scoped_callback_runner.Release());
1206 compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1209 } else if (software_frame_manager_->HasCurrentFrame()) {
1210 gfx::Rect src_pixel_rect = gfx::ToEnclosingRect(gfx::ScaleRect(
1212 software_frame_manager_->GetCurrentFrameDeviceScaleFactor()));
1213 SkBitmap source_bitmap;
1214 source_bitmap.setConfig(
1215 SkBitmap::kARGB_8888_Config,
1216 software_frame_manager_->GetCurrentFrameSizeInPixels().width(),
1217 software_frame_manager_->GetCurrentFrameSizeInPixels().height(),
1219 kOpaque_SkAlphaType);
1220 source_bitmap.setPixels(software_frame_manager_->GetCurrentFramePixels());
1222 SkBitmap target_bitmap;
1223 target_bitmap.setConfig(
1224 SkBitmap::kARGB_8888_Config,
1225 dst_pixel_size.width(),
1226 dst_pixel_size.height(),
1228 kOpaque_SkAlphaType);
1229 if (!target_bitmap.allocPixels())
1232 SkCanvas target_canvas(target_bitmap);
1233 SkRect src_pixel_skrect = SkRect::MakeXYWH(
1234 src_pixel_rect.x(), src_pixel_rect.y(),
1235 src_pixel_rect.width(), src_pixel_rect.height());
1236 target_canvas.drawBitmapRectToRect(
1239 SkRect::MakeXYWH(0, 0, dst_pixel_size.width(), dst_pixel_size.height()),
1241 SkCanvas::kNone_DrawBitmapRectFlag);
1243 ignore_result(scoped_callback_runner.Release());
1244 callback.Run(true, target_bitmap);
1248 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1249 const gfx::Rect& src_subrect,
1250 const scoped_refptr<media::VideoFrame>& target,
1251 const base::Callback<void(bool)>& callback) {
1252 base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1253 if (!render_widget_host_->is_accelerated_compositing_active() ||
1254 !compositing_iosurface_ ||
1255 !compositing_iosurface_->HasIOSurface())
1258 if (!target.get()) {
1263 if (target->format() != media::VideoFrame::YV12 &&
1264 target->format() != media::VideoFrame::I420) {
1269 if (src_subrect.IsEmpty())
1272 ignore_result(scoped_callback_runner.Release());
1273 compositing_iosurface_->CopyToVideoFrame(
1274 GetScaledOpenGLPixelRect(src_subrect),
1279 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1280 return (!render_widget_host_->GetBackingStore(false) &&
1281 !software_frame_manager_->HasCurrentFrame() &&
1282 render_widget_host_->is_accelerated_compositing_active() &&
1283 compositing_iosurface_ &&
1284 compositing_iosurface_->HasIOSurface());
1287 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1288 return !software_frame_manager_->HasCurrentFrame();
1291 void RenderWidgetHostViewMac::BeginFrameSubscription(
1292 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1293 frame_subscriber_ = subscriber.Pass();
1296 void RenderWidgetHostViewMac::EndFrameSubscription() {
1297 frame_subscriber_.reset();
1300 // Sets whether or not to accept first responder status.
1301 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1302 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1305 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1306 if (render_widget_host_)
1307 render_widget_host_->ForwardMouseEvent(event);
1309 if (event.type == WebInputEvent::MouseLeave) {
1310 [cocoa_view_ setToolTipAtMousePoint:nil];
1311 tooltip_text_.clear();
1315 void RenderWidgetHostViewMac::KillSelf() {
1316 if (!weak_factory_.HasWeakPtrs()) {
1317 [cocoa_view_ setHidden:YES];
1318 base::MessageLoop::current()->PostTask(FROM_HERE,
1319 base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1320 weak_factory_.GetWeakPtr()));
1324 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1325 const NativeWebKeyboardEvent& event) {
1326 // Check WebInputEvent type since multiple types of events can be sent into
1327 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1328 // necessary to avoid double processing.
1329 // Also check the native type, since NSFlagsChanged is considered a key event
1330 // for WebKit purposes, but isn't considered a key event by the OS.
1331 if (event.type == WebInputEvent::RawKeyDown &&
1332 [event.os_event type] == NSKeyDown)
1333 return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1337 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1338 const base::string16& text, int plugin_id) {
1339 if (render_widget_host_) {
1340 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1341 render_widget_host_->GetRoutingID(), text, plugin_id));
1345 void RenderWidgetHostViewMac::CompositorSwapBuffers(
1346 uint64 surface_handle,
1347 const gfx::Size& size,
1348 float surface_scale_factor,
1349 const std::vector<ui::LatencyInfo>& latency_info) {
1350 // Ensure that the frame be acked unless it is explicitly passed to a
1351 // display function.
1352 base::ScopedClosureRunner scoped_ack(
1353 base::Bind(&RenderWidgetHostViewMac::SendPendingSwapAck,
1354 weak_factory_.GetWeakPtr()));
1356 if (render_widget_host_->is_hidden())
1359 // Ensure that if this function exits before the frame is set up (but not
1360 // necessarily drawn) then it is treated as an error.
1361 base::ScopedClosureRunner scoped_error(
1362 base::Bind(&RenderWidgetHostViewMac::GotAcceleratedCompositingError,
1363 weak_factory_.GetWeakPtr()));
1365 AddPendingLatencyInfo(latency_info);
1367 // If compositing_iosurface_ exists and has been poisoned, destroy it
1368 // and allow EnsureCompositedIOSurface to recreate it below. Keep a
1369 // reference to the destroyed layer around until after the below call
1370 // to LayoutLayers, to avoid flickers.
1371 base::ScopedClosureRunner scoped_layer_remover;
1372 if (compositing_iosurface_context_ &&
1373 compositing_iosurface_context_->HasBeenPoisoned()) {
1374 scoped_layer_remover.Reset(
1375 base::Bind(RemoveLayerFromSuperlayer, compositing_iosurface_layer_));
1376 DestroyCompositedIOSurfaceLayer(kLeaveLayerInHierarchy);
1377 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1380 // Ensure compositing_iosurface_ and compositing_iosurface_context_ be
1382 if (!EnsureCompositedIOSurface()) {
1383 LOG(ERROR) << "Failed EnsureCompositingIOSurface";
1387 // Make the context current and update the IOSurface with the handle
1388 // passed in by the swap command.
1390 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1391 compositing_iosurface_context_->cgl_context());
1392 if (!compositing_iosurface_->SetIOSurfaceWithContextCurrent(
1393 compositing_iosurface_context_, surface_handle, size,
1394 surface_scale_factor)) {
1395 LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1400 // Grab video frames now that the IOSurface has been set up. Note that this
1401 // will be done in an offscreen context, so it is necessary to re-set the
1402 // current context afterward.
1403 bool frame_was_captured = false;
1404 if (frame_subscriber_) {
1405 const base::TimeTicks present_time = base::TimeTicks::Now();
1406 scoped_refptr<media::VideoFrame> frame;
1407 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1408 if (frame_subscriber_->ShouldCaptureFrame(present_time,
1409 &frame, &callback)) {
1410 // Flush the context that updated the IOSurface, to ensure that the
1411 // context that does the copy picks up the correct version.
1413 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1414 compositing_iosurface_context_->cgl_context());
1417 compositing_iosurface_->CopyToVideoFrame(
1418 gfx::Rect(size), frame,
1419 base::Bind(callback, present_time));
1420 frame_was_captured = true;
1424 // At this point the surface, its context, and its layer have been set up, so
1425 // don't generate an error (one may be generated when drawing).
1426 ignore_result(scoped_error.Release());
1428 GotAcceleratedFrame();
1430 gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1431 if (window_size.IsEmpty()) {
1432 // setNeedsDisplay will never display and we'll never ack if the window is
1433 // empty, so ack now and don't bother calling setNeedsDisplay below.
1436 if (window_number() <= 0) {
1437 // It's normal for a backgrounded tab that is being captured to have no
1438 // window but not be hidden. Immediately ack the frame, and don't try to
1440 if (frame_was_captured)
1443 // If this frame was not captured, there is likely some sort of bug. Ack
1444 // the frame and hope for the best. Because the IOSurface and layer are
1445 // populated, it will likely be displayed when the view is added to a
1446 // window's hierarchy.
1448 // TODO(shess) If the view does not have a window, or the window
1449 // does not have backing, the IOSurface will log "invalid drawable"
1450 // in -setView:. It is not clear how this code is reached with such
1451 // a case, so record some info into breakpad (some subset of
1452 // browsers are likely to crash later for unrelated reasons).
1453 // http://crbug.com/148882
1454 const char* const kCrashKey = "rwhvm_window";
1455 NSWindow* window = [cocoa_view_ window];
1457 base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1460 base::StringPrintf("window %s delegate %s controller %s",
1461 object_getClassName(window),
1462 object_getClassName([window delegate]),
1463 object_getClassName([window windowController]));
1464 base::debug::SetCrashKeyValue(kCrashKey, value);
1469 // If we reach here, then the frame will be displayed by a future draw
1470 // call, so don't make the callback.
1471 ignore_result(scoped_ack.Release());
1472 if (use_core_animation_) {
1473 DCHECK(compositing_iosurface_layer_);
1474 compositing_iosurface_layer_async_timer_.Reset();
1475 [compositing_iosurface_layer_ gotNewFrame];
1477 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1478 compositing_iosurface_context_->cgl_context());
1479 DrawIOSurfaceWithoutCoreAnimation();
1482 // Try to finish previous copy requests after draw to get better pipelining.
1483 if (compositing_iosurface_)
1484 compositing_iosurface_->CheckIfAllCopiesAreFinished(false);
1486 // The IOSurface's size may have changed, so re-layout the layers to take
1487 // this into account. This may force an immediate draw.
1491 void RenderWidgetHostViewMac::DrawIOSurfaceWithoutCoreAnimation() {
1492 CHECK(!use_core_animation_);
1493 CHECK(compositing_iosurface_);
1495 // If there is a pending frame, it should be acked by the end of this
1496 // function. Note that the ack should happen only after all drawing is
1497 // complete, so that the ack happens after any blocking due to vsync.
1498 base::ScopedClosureRunner scoped_ack(
1499 base::Bind(&RenderWidgetHostViewMac::SendPendingSwapAck,
1500 weak_factory_.GetWeakPtr()));
1502 GLint old_gl_surface_order = 0;
1503 GLint new_gl_surface_order = allow_overlapping_views_ ? -1 : 1;
1504 [compositing_iosurface_context_->nsgl_context()
1505 getValues:&old_gl_surface_order
1506 forParameter:NSOpenGLCPSurfaceOrder];
1507 if (old_gl_surface_order != new_gl_surface_order) {
1508 [compositing_iosurface_context_->nsgl_context()
1509 setValues:&new_gl_surface_order
1510 forParameter:NSOpenGLCPSurfaceOrder];
1513 // Instead of drawing, request that underlay view redraws.
1514 if (underlay_view_ &&
1515 underlay_view_->compositing_iosurface_ &&
1516 underlay_view_has_drawn_) {
1517 [underlay_view_->cocoa_view() setNeedsDisplayInRect:NSMakeRect(0, 0, 1, 1)];
1521 bool has_overlay = overlay_view_ && overlay_view_->compositing_iosurface_;
1523 // Un-bind the overlay view's OpenGL context, since its content will be
1524 // drawn by this context. Not doing this can result in corruption.
1525 // http://crbug.com/330701
1526 overlay_view_->ClearBoundContextDrawable();
1528 [compositing_iosurface_context_->nsgl_context() setView:cocoa_view_];
1530 gfx::Rect view_rect(NSRectToCGRect([cocoa_view_ frame]));
1531 if (!compositing_iosurface_->DrawIOSurface(
1532 compositing_iosurface_context_, view_rect,
1533 ViewScaleFactor(), !has_overlay)) {
1534 GotAcceleratedCompositingError();
1539 overlay_view_->underlay_view_has_drawn_ = true;
1540 gfx::Rect overlay_view_rect(
1541 NSRectToCGRect([overlay_view_->cocoa_view() frame]));
1542 overlay_view_rect.set_x(overlay_view_offset_.x());
1543 overlay_view_rect.set_y(view_rect.height() -
1544 overlay_view_rect.height() -
1545 overlay_view_offset_.y());
1546 if (!overlay_view_->compositing_iosurface_->DrawIOSurface(
1547 compositing_iosurface_context_, overlay_view_rect,
1548 overlay_view_->ViewScaleFactor(), true)) {
1549 GotAcceleratedCompositingError();
1554 SendPendingLatencyInfoToHost();
1557 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1558 LOG(ERROR) << "Encountered accelerated compositing error";
1559 base::MessageLoop::current()->PostTask(
1561 base::Bind(&RenderWidgetHostViewMac::DestroyCompositingStateOnError,
1562 weak_factory_.GetWeakPtr()));
1565 void RenderWidgetHostViewMac::DestroyCompositingStateOnError() {
1566 // This should be called with a clean stack. Make sure that no context is
1568 DCHECK(!CGLGetCurrentContext());
1570 // The existing GL contexts may be in a bad state, so don't re-use any of the
1571 // existing ones anymore, rather, allocate new ones.
1572 if (compositing_iosurface_context_)
1573 compositing_iosurface_context_->PoisonContextAndSharegroup();
1575 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1577 // Request that a new frame be generated and dirty the view.
1578 if (render_widget_host_)
1579 render_widget_host_->ScheduleComposite();
1580 [cocoa_view_ setNeedsDisplay:YES];
1582 // Mark the last frame as not accelerated (so that the window is prepared for
1583 // an underlay next time an accelerated frame comes in).
1584 last_frame_was_accelerated_ = false;
1586 // TODO(ccameron): It may be a good idea to request that the renderer recreate
1587 // its GL context as well, and fall back to software if this happens
1591 void RenderWidgetHostViewMac::SetOverlayView(
1592 RenderWidgetHostViewMac* overlay, const gfx::Point& offset) {
1594 overlay_view_->underlay_view_.reset();
1596 overlay_view_ = overlay->overlay_view_weak_factory_.GetWeakPtr();
1597 overlay_view_->underlay_view_ = overlay_view_weak_factory_.GetWeakPtr();
1598 if (use_core_animation_)
1601 overlay_view_offset_ = offset;
1602 overlay_view_->underlay_view_has_drawn_ = false;
1604 [cocoa_view_ setNeedsDisplay:YES];
1605 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1608 void RenderWidgetHostViewMac::RemoveOverlayView() {
1609 if (overlay_view_) {
1610 overlay_view_->underlay_view_.reset();
1611 overlay_view_.reset();
1613 if (use_core_animation_)
1616 [cocoa_view_ setNeedsDisplay:YES];
1617 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1620 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1621 const std::vector<gfx::Rect>& bounds,
1622 const gfx::Range& range,
1623 size_t* line_break_point) {
1624 DCHECK(line_break_point);
1625 if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1628 // We can't check line breaking completely from only rectangle array. Thus we
1629 // assume the line breaking as the next character's y offset is larger than
1630 // a threshold. Currently the threshold is determined as minimum y offset plus
1631 // 75% of maximum height.
1632 // TODO(nona): Check the threshold is reliable or not.
1633 // TODO(nona): Bidi support.
1634 const size_t loop_end_idx = std::min(bounds.size(), range.end());
1636 int min_y_offset = kint32max;
1637 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1638 max_height = std::max(max_height, bounds[idx].height());
1639 min_y_offset = std::min(min_y_offset, bounds[idx].y());
1641 int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1642 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1643 if (bounds[idx].y() > line_break_threshold) {
1644 *line_break_point = idx;
1651 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1652 const gfx::Range& range,
1653 gfx::Range* actual_range) {
1654 DCHECK(actual_range);
1655 DCHECK(!composition_bounds_.empty());
1656 DCHECK(range.start() <= composition_bounds_.size());
1657 DCHECK(range.end() <= composition_bounds_.size());
1659 if (range.is_empty()) {
1660 *actual_range = range;
1661 if (range.start() == composition_bounds_.size()) {
1662 return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1663 composition_bounds_[range.start() - 1].y(),
1665 composition_bounds_[range.start() - 1].height());
1667 return gfx::Rect(composition_bounds_[range.start()].x(),
1668 composition_bounds_[range.start()].y(),
1670 composition_bounds_[range.start()].height());
1675 if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1676 end_idx = range.end();
1678 *actual_range = gfx::Range(range.start(), end_idx);
1679 gfx::Rect rect = composition_bounds_[range.start()];
1680 for (size_t i = range.start() + 1; i < end_idx; ++i) {
1681 rect.Union(composition_bounds_[i]);
1686 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1687 const gfx::Range& request_range) {
1688 if (composition_range_.is_empty())
1689 return gfx::Range::InvalidRange();
1691 if (request_range.is_reversed())
1692 return gfx::Range::InvalidRange();
1694 if (request_range.start() < composition_range_.start() ||
1695 request_range.start() > composition_range_.end() ||
1696 request_range.end() > composition_range_.end()) {
1697 return gfx::Range::InvalidRange();
1701 request_range.start() - composition_range_.start(),
1702 request_range.end() - composition_range_.start());
1705 RenderFrameHost* RenderWidgetHostViewMac::GetFocusedFrame() {
1706 if (!render_widget_host_->IsRenderView())
1709 RenderViewHost* rvh = RenderViewHost::From(render_widget_host_);
1710 RenderFrameHostImpl* rfh =
1711 static_cast<RenderFrameHostImpl*>(rvh->GetMainFrame());
1712 FrameTreeNode* focused_frame =
1713 rfh->frame_tree_node()->frame_tree()->GetFocusedFrame();
1717 return focused_frame->current_frame_host();
1720 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1723 NSRange* actual_range) {
1725 // This exists to make IMEs more responsive, see http://crbug.com/115920
1726 TRACE_EVENT0("browser",
1727 "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1729 // If requested range is same as caret location, we can just return it.
1730 if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1732 *actual_range = range;
1733 *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1737 const gfx::Range request_range_in_composition =
1738 ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1739 if (request_range_in_composition == gfx::Range::InvalidRange())
1742 // If firstRectForCharacterRange in WebFrame is failed in renderer,
1743 // ImeCompositionRangeChanged will be sent with empty vector.
1744 if (composition_bounds_.empty())
1746 DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1748 gfx::Range ui_actual_range;
1749 *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1750 request_range_in_composition,
1751 &ui_actual_range).ToCGRect());
1753 *actual_range = gfx::Range(
1754 composition_range_.start() + ui_actual_range.start(),
1755 composition_range_.start() + ui_actual_range.end()).ToNSRange();
1760 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1761 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1763 TRACE_EVENT0("browser",
1764 "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1765 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1767 AddPendingSwapAck(params.route_id,
1769 compositing_iosurface_ ?
1770 compositing_iosurface_->GetRendererID() : 0);
1771 CompositorSwapBuffers(params.surface_handle,
1773 params.scale_factor,
1774 params.latency_info);
1777 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1778 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1780 TRACE_EVENT0("browser",
1781 "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1782 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1784 AddPendingSwapAck(params.route_id,
1786 compositing_iosurface_ ?
1787 compositing_iosurface_->GetRendererID() : 0);
1788 CompositorSwapBuffers(params.surface_handle,
1789 params.surface_size,
1790 params.surface_scale_factor,
1791 params.latency_info);
1794 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1795 if (compositing_iosurface_)
1796 compositing_iosurface_->UnrefIOSurface();
1799 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1800 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1803 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1804 const gfx::Size& desired_size) {
1805 if (last_frame_was_accelerated_) {
1806 return compositing_iosurface_ &&
1807 compositing_iosurface_->HasIOSurface() &&
1808 (desired_size.IsEmpty() ||
1809 compositing_iosurface_->dip_io_surface_size() == desired_size);
1811 return (software_frame_manager_->HasCurrentFrame() &&
1812 (desired_size.IsEmpty() ||
1813 software_frame_manager_->GetCurrentFrameSizeInDIP() ==
1819 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1820 uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1821 // Only software compositor frames are accepted.
1822 if (!frame->software_frame_data) {
1823 DLOG(ERROR) << "Received unexpected frame type.";
1825 base::UserMetricsAction("BadMessageTerminate_UnexpectedFrameType"));
1826 render_widget_host_->GetProcess()->ReceivedBadMessage();
1830 if (!software_frame_manager_->SwapToNewFrame(
1832 frame->software_frame_data.get(),
1833 frame->metadata.device_scale_factor,
1834 render_widget_host_->GetProcess()->GetHandle())) {
1835 render_widget_host_->GetProcess()->ReceivedBadMessage();
1839 // Add latency info to report when the frame finishes drawing.
1840 AddPendingLatencyInfo(frame->metadata.latency_info);
1843 cc::CompositorFrameAck ack;
1844 RenderWidgetHostImpl::SendSwapCompositorFrameAck(
1845 render_widget_host_->GetRoutingID(),
1846 software_frame_manager_->GetCurrentFrameOutputSurfaceId(),
1847 render_widget_host_->GetProcess()->GetID(),
1849 software_frame_manager_->SwapToNewFrameComplete(
1850 !render_widget_host_->is_hidden());
1852 // Notify observers, tab capture observers in particular, that a new software
1853 // frame has come in.
1854 NotificationService::current()->Notify(
1855 NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
1856 Source<RenderWidgetHost>(render_widget_host_),
1857 NotificationService::NoDetails());
1860 void RenderWidgetHostViewMac::OnAcceleratedCompositingStateChange() {
1863 void RenderWidgetHostViewMac::AcceleratedSurfaceInitialized(int host_id,
1867 void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) {
1868 *results = GetWebScreenInfo(GetNativeView());
1871 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1872 // TODO(shess): In case of !window, the view has been removed from
1873 // the view hierarchy because the tab isn't main. Could retrieve
1874 // the information from the main tab for our window.
1875 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1876 if (!enclosing_window)
1879 NSRect bounds = [enclosing_window frame];
1880 return FlipNSRectToRectScreen(bounds);
1883 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1884 // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1885 // completely on Mac OS.
1886 return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
1889 void RenderWidgetHostViewMac::SetHasHorizontalScrollbar(
1890 bool has_horizontal_scrollbar) {
1891 [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
1894 void RenderWidgetHostViewMac::SetScrollOffsetPinning(
1895 bool is_pinned_to_left, bool is_pinned_to_right) {
1896 [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
1897 toRight:is_pinned_to_right];
1900 bool RenderWidgetHostViewMac::LockMouse() {
1904 mouse_locked_ = true;
1906 // Lock position of mouse cursor and hide it.
1907 CGAssociateMouseAndMouseCursorPosition(NO);
1910 // Clear the tooltip window.
1911 SetTooltipText(base::string16());
1916 void RenderWidgetHostViewMac::UnlockMouse() {
1919 mouse_locked_ = false;
1921 // Unlock position of mouse cursor and unhide it.
1922 CGAssociateMouseAndMouseCursorPosition(YES);
1925 if (render_widget_host_)
1926 render_widget_host_->LostMouseLock();
1929 void RenderWidgetHostViewMac::UnhandledWheelEvent(
1930 const blink::WebMouseWheelEvent& event) {
1931 // Only record a wheel event as unhandled if JavaScript handlers got a chance
1932 // to see it (no-op wheel events are ignored by the event dispatcher)
1933 if (event.deltaX || event.deltaY)
1934 [cocoa_view_ gotUnhandledWheelEvent];
1937 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
1938 if (render_widget_host_)
1939 return render_widget_host_->Send(message);
1944 void RenderWidgetHostViewMac::SoftwareFrameWasFreed(
1945 uint32 output_surface_id, unsigned frame_id) {
1946 if (!render_widget_host_)
1948 cc::CompositorFrameAck ack;
1949 ack.last_software_frame_id = frame_id;
1950 RenderWidgetHostImpl::SendReclaimCompositorResources(
1951 render_widget_host_->GetRoutingID(),
1953 render_widget_host_->GetProcess()->GetID(),
1957 void RenderWidgetHostViewMac::ReleaseReferencesToSoftwareFrame() {
1958 DestroySoftwareLayer();
1961 void RenderWidgetHostViewMac::ShutdownHost() {
1962 weak_factory_.InvalidateWeakPtrs();
1963 render_widget_host_->Shutdown();
1964 // Do not touch any members at this point, |this| has been deleted.
1967 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
1968 EnsureCompositedIOSurfaceLayer();
1969 SendVSyncParametersToRenderer();
1970 if (!last_frame_was_accelerated_) {
1971 last_frame_was_accelerated_ = true;
1973 if (!use_core_animation_) {
1974 // Need to wipe the software view with transparency to expose the GL
1975 // underlay. Invalidate the whole window to do that.
1976 [cocoa_view_ setNeedsDisplay:YES];
1979 // Delete software backingstore and layer.
1980 BackingStoreManager::RemoveBackingStore(render_widget_host_);
1981 software_frame_manager_->DiscardCurrentFrame();
1982 DestroySoftwareLayer();
1986 void RenderWidgetHostViewMac::GotSoftwareFrame() {
1987 if (!render_widget_host_)
1990 EnsureSoftwareLayer();
1992 SendVSyncParametersToRenderer();
1994 // Draw the contents of the frame immediately. It is critical that this
1995 // happen before the frame be acked, otherwise the new frame will likely be
1996 // ready before the drawing is complete, thrashing the browser main thread.
1997 if (use_core_animation_) {
1998 [software_layer_ setNeedsDisplay];
1999 [software_layer_ displayIfNeeded];
2001 [cocoa_view_ setNeedsDisplay:YES];
2002 [cocoa_view_ displayIfNeeded];
2005 if (last_frame_was_accelerated_) {
2006 last_frame_was_accelerated_ = false;
2008 // If overlapping views are allowed, then don't unbind the context
2009 // from the view (that is, don't call clearDrawble -- just delete the
2010 // texture and IOSurface). Rather, let it sit behind the software frame
2011 // that will be put up in front. This will prevent transparent
2013 // http://crbug.com/154531
2014 // Also note that it is necessary that clearDrawable be called if
2015 // overlapping views are not allowed, e.g, for content shell.
2016 // http://crbug.com/178408
2017 // Disable screen updates so that the changes of flashes is minimized.
2018 // http://crbug.com/279472
2019 if (!use_core_animation_)
2020 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
2021 if (allow_overlapping_views_)
2022 DestroyCompositedIOSurfaceAndLayer(kLeaveContextBoundToView);
2024 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
2028 void RenderWidgetHostViewMac::TimerSinceGotAcceleratedFrameFired() {
2029 [compositing_iosurface_layer_ timerSinceGotNewFrameFired];
2032 void RenderWidgetHostViewMac::SetActive(bool active) {
2033 if (render_widget_host_) {
2034 render_widget_host_->SetActive(active);
2037 render_widget_host_->Focus();
2039 render_widget_host_->Blur();
2043 SetTextInputActive(active);
2045 [cocoa_view_ setPluginImeActive:NO];
2050 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
2051 if (render_widget_host_) {
2052 render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
2053 render_widget_host_->GetRoutingID(), visible));
2057 void RenderWidgetHostViewMac::WindowFrameChanged() {
2058 if (render_widget_host_) {
2059 render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
2060 render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
2064 if (compositing_iosurface_ && !use_core_animation_) {
2065 // This will migrate the context to the appropriate window.
2066 if (!EnsureCompositedIOSurface())
2067 GotAcceleratedCompositingError();
2071 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
2072 RenderWidgetHostViewMacDictionaryHelper helper(this);
2073 helper.ShowDefinitionForSelection();
2076 void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) {
2077 RenderWidgetHostViewBase::SetBackground(background);
2078 if (render_widget_host_)
2079 render_widget_host_->Send(new ViewMsg_SetBackground(
2080 render_widget_host_->GetRoutingID(), background));
2083 void RenderWidgetHostViewMac::CreateBrowserAccessibilityManagerIfNeeded() {
2084 if (!GetBrowserAccessibilityManager()) {
2085 SetBrowserAccessibilityManager(
2086 new BrowserAccessibilityManagerMac(
2088 BrowserAccessibilityManagerMac::GetEmptyDocument(),
2093 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
2095 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2096 EnablePasswordInput();
2098 DisablePasswordInput();
2100 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2101 DisablePasswordInput();
2105 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
2107 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
2110 void RenderWidgetHostViewMac::OnStartPluginIme() {
2111 [cocoa_view_ setPluginImeActive:YES];
2114 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
2115 const gfx::Rect& rect) {
2116 gfx::Rect src_gl_subrect = rect;
2117 src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
2119 return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
2120 ViewScaleFactor()));
2123 void RenderWidgetHostViewMac::AddPendingLatencyInfo(
2124 const std::vector<ui::LatencyInfo>& latency_info) {
2125 // If a screenshot is being taken when using CoreAnimation, send a few extra
2126 // calls to setNeedsDisplay and wait for their resulting display calls,
2127 // before reporting that the frame has reached the screen.
2128 if (use_core_animation_) {
2129 bool should_defer = false;
2130 for (size_t i = 0; i < latency_info.size(); i++) {
2131 if (latency_info[i].FindLatency(
2132 ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT,
2133 render_widget_host_->GetLatencyComponentId(),
2135 should_defer = true;
2139 // Multiple pending screenshot requests will work, but if every frame
2140 // requests a screenshot, then the delay will never expire. Assert this
2141 // here to avoid this.
2142 CHECK_EQ(pending_latency_info_delay_, 0u);
2143 // Wait a fixed number of frames (calls to CALayer::display) before
2144 // claiming that the screenshot has reached the screen. This number
2145 // comes from taking the first number where tests didn't fail (six),
2147 const uint32 kScreenshotLatencyDelayInFrames = 12;
2148 pending_latency_info_delay_ = kScreenshotLatencyDelayInFrames;
2149 TickPendingLatencyInfoDelay();
2153 for (size_t i = 0; i < latency_info.size(); i++) {
2154 pending_latency_info_.push_back(latency_info[i]);
2158 void RenderWidgetHostViewMac::SendPendingLatencyInfoToHost() {
2159 if (pending_latency_info_delay_) {
2160 pending_latency_info_delay_ -= 1;
2163 pending_latency_info_delay_weak_ptr_factory_.InvalidateWeakPtrs();
2165 for (size_t i = 0; i < pending_latency_info_.size(); i++) {
2166 pending_latency_info_[i].AddLatencyNumber(
2167 ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
2168 render_widget_host_->FrameSwapped(pending_latency_info_[i]);
2170 pending_latency_info_.clear();
2173 void RenderWidgetHostViewMac::TickPendingLatencyInfoDelay() {
2174 if (compositing_iosurface_layer_) {
2175 // Keep calling gotNewFrame in a loop until enough display calls come in.
2176 // Each call will be separated by about a vsync.
2177 base::MessageLoop::current()->PostTask(
2179 base::Bind(&RenderWidgetHostViewMac::TickPendingLatencyInfoDelay,
2180 pending_latency_info_delay_weak_ptr_factory_.GetWeakPtr()));
2181 [compositing_iosurface_layer_ gotNewFrame];
2183 if (software_layer_) {
2184 // In software mode, setNeedsDisplay will almost immediately result in the
2185 // layer's draw function being called, so manually insert a pretend-vsync
2187 base::MessageLoop::current()->PostDelayedTask(
2189 base::Bind(&RenderWidgetHostViewMac::TickPendingLatencyInfoDelay,
2190 pending_latency_info_delay_weak_ptr_factory_.GetWeakPtr()),
2191 base::TimeDelta::FromMilliseconds(1000/60));
2192 [software_layer_ setNeedsDisplay];
2196 void RenderWidgetHostViewMac::AddPendingSwapAck(
2197 int32 route_id, int gpu_host_id, int32 renderer_id) {
2198 // Note that multiple un-acked swaps can come in the event of a GPU process
2199 // loss. Drop the old acks.
2200 pending_swap_ack_.reset(new PendingSwapAck(
2201 route_id, gpu_host_id, renderer_id));
2204 void RenderWidgetHostViewMac::SendPendingSwapAck() {
2205 if (!pending_swap_ack_)
2208 AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
2209 ack_params.sync_point = 0;
2210 ack_params.renderer_id = pending_swap_ack_->renderer_id;
2211 RenderWidgetHostImpl::AcknowledgeBufferPresent(pending_swap_ack_->route_id,
2212 pending_swap_ack_->gpu_host_id,
2214 if (render_widget_host_)
2215 render_widget_host_->AcknowledgeSwapBuffersToRenderer();
2217 pending_swap_ack_.reset();
2220 void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() {
2221 if (!render_widget_host_ || render_widget_host_->is_hidden())
2224 // Pausing for the overlay view prevents the underlay from receiving
2225 // frames. This may lead to large delays, causing overlaps. If both
2226 // overlay and underlay resize at the same time, let them both to have
2227 // some time waiting. See crbug.com/352020.
2228 if (underlay_view_ &&
2229 underlay_view_->render_widget_host_ &&
2230 !underlay_view_->render_widget_host_->
2231 CanPauseForPendingResizeOrRepaints())
2234 // Ensure that all frames are acked before waiting for a frame to come in.
2235 // Note that we will draw a frame at the end of this function, so it is safe
2236 // to ack a never-drawn frame here.
2237 SendPendingSwapAck();
2239 // Wait for a frame of the right size to come in.
2240 render_widget_host_->PauseForPendingResizeOrRepaints();
2242 // Immediately draw any frames that haven't been drawn yet. This is necessary
2243 // to keep the window and the window's contents in sync.
2244 [cocoa_view_ displayIfNeeded];
2245 [software_layer_ displayIfNeeded];
2246 [compositing_iosurface_layer_ displayIfNeeded];
2249 void RenderWidgetHostViewMac::LayoutLayers() {
2250 if (!use_core_animation_)
2253 // Disable animation of the layer's resizing or change in contents scale.
2254 ScopedCAActionDisabler disabler;
2256 CGRect new_background_frame = NSRectToCGRect([cocoa_view() bounds]);
2258 // Dynamically calling setContentsScale on a CAOpenGLLayer for which
2259 // setAsynchronous is dynamically toggled can result in flashes of corrupt
2260 // content. Work around this by replacing the entire layer when the scale
2262 if (compositing_iosurface_ &&
2263 [compositing_iosurface_layer_
2264 respondsToSelector:(@selector(contentsScale))]) {
2265 if (compositing_iosurface_->scale_factor() !=
2266 [compositing_iosurface_layer_ contentsScale]) {
2267 DestroyCompositedIOSurfaceLayer(kRemoveLayerFromHierarchy);
2268 EnsureCompositedIOSurfaceLayer();
2271 if (compositing_iosurface_ &&
2272 compositing_iosurface_->HasIOSurface() &&
2273 compositing_iosurface_layer_) {
2274 CGRect layer_bounds = CGRectMake(
2277 compositing_iosurface_->dip_io_surface_size().width(),
2278 compositing_iosurface_->dip_io_surface_size().height());
2279 CGPoint layer_position = CGPointMake(
2281 CGRectGetHeight(new_background_frame) - CGRectGetHeight(layer_bounds));
2282 bool bounds_changed = !CGRectEqualToRect(
2283 layer_bounds, [compositing_iosurface_layer_ bounds]);
2284 [compositing_iosurface_layer_ setPosition:layer_position];
2285 [compositing_iosurface_layer_ setBounds:layer_bounds];
2287 // If the bounds changed, then draw the frame immediately, to ensure that
2288 // content displayed is in sync with the window size.
2289 if (bounds_changed) {
2290 // Also, sometimes, especially when infobars are being removed, the
2291 // setNeedsDisplay calls are dropped on the floor, and stale content is
2292 // displayed. Calling displayIfNeeded will ensure that the right size
2293 // frame is drawn to the screen.
2294 // http://crbug.com/350817
2295 [compositing_iosurface_layer_ setNeedsDisplay];
2296 [compositing_iosurface_layer_ displayIfNeeded];
2300 // Dynamically update the software layer's contents scale to match the
2302 if (software_frame_manager_->HasCurrentFrame() &&
2303 [software_layer_ respondsToSelector:(@selector(contentsScale))] &&
2304 [software_layer_ respondsToSelector:(@selector(setContentsScale:))]) {
2305 if (software_frame_manager_->GetCurrentFrameDeviceScaleFactor() !=
2306 [software_layer_ contentsScale]) {
2307 [software_layer_ setContentsScale:
2308 software_frame_manager_->GetCurrentFrameDeviceScaleFactor()];
2311 // Changing the software layer's bounds and position doesn't always result
2312 // in the layer being anchored to the top-left. Set the layer's frame
2313 // explicitly, since this is more reliable in practice.
2314 if (software_layer_) {
2315 bool frame_changed = !CGRectEqualToRect(
2316 new_background_frame, [software_layer_ frame]);
2317 if (frame_changed) {
2318 [software_layer_ setFrame:new_background_frame];
2319 [software_layer_ setNeedsDisplay];
2324 SkBitmap::Config RenderWidgetHostViewMac::PreferredReadbackFormat() {
2325 return SkBitmap::kARGB_8888_Config;
2328 } // namespace content
2330 // RenderWidgetHostViewCocoa ---------------------------------------------------
2332 @implementation RenderWidgetHostViewCocoa
2333 @synthesize selectedRange = selectedRange_;
2334 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
2335 @synthesize markedRange = markedRange_;
2337 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
2338 self = [super initWithFrame:NSZeroRect];
2340 self.acceptsTouchEvents = YES;
2341 editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
2342 editCommand_helper_->AddEditingSelectorsToClass([self class]);
2344 renderWidgetHostView_.reset(r);
2345 canBeKeyView_ = YES;
2346 focusedPluginIdentifier_ = -1;
2347 renderWidgetHostView_->backing_store_scale_factor_ =
2348 ScaleFactorForView(self);
2351 if ([self respondsToSelector:
2352 @selector(setWantsBestResolutionOpenGLSurface:)]) {
2353 [self setWantsBestResolutionOpenGLSurface:YES];
2355 handlingGlobalFrameDidChange_ = NO;
2356 [[NSNotificationCenter defaultCenter]
2358 selector:@selector(globalFrameDidChange:)
2359 name:NSViewGlobalFrameDidChangeNotification
2361 [[NSNotificationCenter defaultCenter]
2363 selector:@selector(didChangeScreenParameters:)
2364 name:NSApplicationDidChangeScreenParametersNotification
2371 // Unbind the GL context from this view. If this is not done before super's
2372 // dealloc is called then the GL context will crash when it reaches into
2373 // the view in its destructor.
2374 // http://crbug.com/255608
2375 if (renderWidgetHostView_)
2376 renderWidgetHostView_->AcceleratedSurfaceRelease();
2378 if (responderDelegate_ &&
2379 [responderDelegate_ respondsToSelector:@selector(viewGone:)])
2380 [responderDelegate_ viewGone:self];
2381 responderDelegate_.reset();
2383 [[NSNotificationCenter defaultCenter] removeObserver:self];
2388 - (void)didChangeScreenParameters:(NSNotification*)notify {
2389 g_screen_info_up_to_date = false;
2392 - (void)setResponderDelegate:
2393 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate {
2394 DCHECK(!responderDelegate_);
2395 responderDelegate_.reset([delegate retain]);
2398 - (void)resetCursorRects {
2399 if (currentCursor_) {
2400 [self addCursorRect:[self visibleRect] cursor:currentCursor_];
2401 [currentCursor_ setOnMouseEntered:YES];
2405 - (void)gotUnhandledWheelEvent {
2406 if (responderDelegate_ &&
2408 respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
2409 [responderDelegate_ gotUnhandledWheelEvent];
2413 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
2414 if (responderDelegate_ &&
2416 respondsToSelector:@selector(scrollOffsetPinnedToLeft:toRight:)]) {
2417 [responderDelegate_ scrollOffsetPinnedToLeft:left toRight:right];
2421 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
2422 if (responderDelegate_ &&
2424 respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
2425 [responderDelegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
2429 - (BOOL)respondsToSelector:(SEL)selector {
2430 // Trickiness: this doesn't mean "does this object's superclass respond to
2431 // this selector" but rather "does the -respondsToSelector impl from the
2432 // superclass say that this class responds to the selector".
2433 if ([super respondsToSelector:selector])
2436 if (responderDelegate_)
2437 return [responderDelegate_ respondsToSelector:selector];
2442 - (id)forwardingTargetForSelector:(SEL)selector {
2443 if ([responderDelegate_ respondsToSelector:selector])
2444 return responderDelegate_.get();
2446 return [super forwardingTargetForSelector:selector];
2449 - (void)setCanBeKeyView:(BOOL)can {
2450 canBeKeyView_ = can;
2453 - (BOOL)acceptsMouseEventsWhenInactive {
2454 // Some types of windows (balloons, always-on-top panels) want to accept mouse
2455 // clicks w/o the first click being treated as 'activation'. Same applies to
2456 // mouse move events.
2457 return [[self window] level] > NSNormalWindowLevel;
2460 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
2461 return [self acceptsMouseEventsWhenInactive];
2464 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
2465 takesFocusOnlyOnMouseDown_ = b;
2468 - (void)setCloseOnDeactivate:(BOOL)b {
2469 closeOnDeactivate_ = b;
2472 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
2473 NSWindow* window = [self window];
2474 // If this is a background window, don't handle mouse movement events. This
2475 // is the expected behavior on the Mac as evidenced by other applications.
2476 if ([theEvent type] == NSMouseMoved &&
2477 ![self acceptsMouseEventsWhenInactive] &&
2478 ![window isKeyWindow]) {
2482 // Use hitTest to check whether the mouse is over a nonWebContentView - in
2483 // which case the mouse event should not be handled by the render host.
2484 const SEL nonWebContentViewSelector = @selector(nonWebContentView);
2485 NSView* contentView = [window contentView];
2486 NSView* view = [contentView hitTest:[theEvent locationInWindow]];
2487 // Traverse the superview hierarchy as the hitTest will return the frontmost
2488 // view, such as an NSTextView, while nonWebContentView may be specified by
2491 if ([view respondsToSelector:nonWebContentViewSelector] &&
2492 [view performSelector:nonWebContentViewSelector]) {
2493 // The cursor is over a nonWebContentView - ignore this mouse event.
2496 if ([view isKindOfClass:[self class]] && ![view isEqual:self] &&
2497 !hasOpenMouseDown_) {
2498 // The cursor is over an overlapping render widget. This check is done by
2499 // both views so the one that's returned by -hitTest: will end up
2500 // processing the event.
2501 // Note that while dragging, we only get events for the render view where
2502 // drag started, even if mouse is actually over another view or outside
2503 // the window. Cocoa does this for us. We should handle these events and
2504 // not ignore (since there is no other render view to handle them). Thus
2505 // the |!hasOpenMouseDown_| check above.
2508 view = [view superview];
2513 - (void)mouseEvent:(NSEvent*)theEvent {
2514 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
2515 if (responderDelegate_ &&
2516 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2517 BOOL handled = [responderDelegate_ handleEvent:theEvent];
2522 if ([self shouldIgnoreMouseEvent:theEvent]) {
2523 // If this is the first such event, send a mouse exit to the host view.
2524 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
2525 WebMouseEvent exitEvent =
2526 WebInputEventFactory::mouseEvent(theEvent, self);
2527 exitEvent.type = WebInputEvent::MouseLeave;
2528 exitEvent.button = WebMouseEvent::ButtonNone;
2529 renderWidgetHostView_->ForwardMouseEvent(exitEvent);
2531 mouseEventWasIgnored_ = YES;
2535 if (mouseEventWasIgnored_) {
2536 // If this is the first mouse event after a previous event that was ignored
2537 // due to the hitTest, send a mouse enter event to the host view.
2538 if (renderWidgetHostView_->render_widget_host_) {
2539 WebMouseEvent enterEvent =
2540 WebInputEventFactory::mouseEvent(theEvent, self);
2541 enterEvent.type = WebInputEvent::MouseMove;
2542 enterEvent.button = WebMouseEvent::ButtonNone;
2543 renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2546 mouseEventWasIgnored_ = NO;
2548 // TODO(rohitrao): Probably need to handle other mouse down events here.
2549 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2550 if (renderWidgetHostView_->render_widget_host_)
2551 renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2553 // Manually take focus after the click but before forwarding it to the
2555 [[self window] makeFirstResponder:self];
2558 // Don't cancel child popups; killing them on a mouse click would prevent the
2559 // user from positioning the insertion point in the text field spawning the
2560 // popup. A click outside the text field would cause the text field to drop
2561 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2562 // the popup anyway, so we're OK.
2564 NSEventType type = [theEvent type];
2565 if (type == NSLeftMouseDown)
2566 hasOpenMouseDown_ = YES;
2567 else if (type == NSLeftMouseUp)
2568 hasOpenMouseDown_ = NO;
2570 // TODO(suzhe): We should send mouse events to the input method first if it
2571 // wants to handle them. But it won't work without implementing method
2572 // - (NSUInteger)characterIndexForPoint:.
2573 // See: http://code.google.com/p/chromium/issues/detail?id=47141
2574 // Instead of sending mouse events to the input method first, we now just
2575 // simply confirm all ongoing composition here.
2576 if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2577 type == NSOtherMouseDown) {
2578 [self confirmComposition];
2581 const WebMouseEvent event =
2582 WebInputEventFactory::mouseEvent(theEvent, self);
2583 renderWidgetHostView_->ForwardMouseEvent(event);
2586 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2587 // |performKeyEquivalent:| is sent to all views of a window, not only down the
2588 // responder chain (cf. "Handling Key Equivalents" in
2589 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2590 // ). We only want to handle key equivalents if we're first responder.
2591 if ([[self window] firstResponder] != self)
2594 // If we return |NO| from this function, cocoa will send the key event to
2595 // the menu and only if the menu does not process the event to |keyDown:|. We
2596 // want to send the event to a renderer _before_ sending it to the menu, so
2597 // we need to return |YES| for all events that might be swallowed by the menu.
2598 // We do not return |YES| for every keypress because we don't get |keyDown:|
2599 // events for keys that we handle this way.
2600 NSUInteger modifierFlags = [theEvent modifierFlags];
2601 if ((modifierFlags & NSCommandKeyMask) == 0) {
2602 // Make sure the menu does not contain key equivalents that don't
2604 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2608 // Command key combinations are sent via performKeyEquivalent rather than
2609 // keyDown:. We just forward this on and if WebCore doesn't want to handle
2610 // it, we let the WebContentsView figure out how to reinject it.
2611 [self keyEvent:theEvent wasKeyEquivalent:YES];
2615 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2616 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2617 // returned NO. If this function returns |YES|, Cocoa sends the event to
2618 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2619 // to us instead of doing key view loop control, ctrl-left/right get handled
2621 // (However, there are still some keys that Cocoa swallows, e.g. the key
2622 // equivalent that Cocoa uses for toggling the input language. In this case,
2623 // that's actually a good thing, though -- see http://crbug.com/26115 .)
2627 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2628 if (responderDelegate_ &&
2629 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2630 BOOL handled = [responderDelegate_ handleEvent:theEvent];
2632 return kEventHandled;
2635 [self keyEvent:theEvent wasKeyEquivalent:NO];
2636 return kEventHandled;
2639 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2640 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2641 DCHECK([theEvent type] != NSKeyDown ||
2642 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2644 if ([theEvent type] == NSFlagsChanged) {
2645 // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2646 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2647 int keyCode = [theEvent keyCode];
2648 if (!keyCode || keyCode == 10 || keyCode == 63)
2652 // Don't cancel child popups; the key events are probably what's triggering
2653 // the popup in the first place.
2655 RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2658 NativeWebKeyboardEvent event(theEvent);
2660 // Force fullscreen windows to close on Escape so they won't keep the keyboard
2661 // grabbed or be stuck onscreen if the renderer is hanging.
2662 if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2663 event.windowsKeyCode == ui::VKEY_ESCAPE &&
2664 renderWidgetHostView_->pepper_fullscreen_window()) {
2665 RenderWidgetHostViewMac* parent =
2666 renderWidgetHostView_->fullscreen_parent_host_view();
2668 parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2669 widgetHost->Shutdown();
2673 // Suppress the escape key up event if necessary.
2674 if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2675 if (event.type == NativeWebKeyboardEvent::KeyUp)
2676 suppressNextEscapeKeyUp_ = NO;
2680 // We only handle key down events and just simply forward other events.
2681 if ([theEvent type] != NSKeyDown) {
2682 widgetHost->ForwardKeyboardEvent(event);
2684 // Possibly autohide the cursor.
2685 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2686 [NSCursor setHiddenUntilMouseMoves:YES];
2691 base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2693 // Records the current marked text state, so that we can know if the marked
2694 // text was deleted or not after handling the key down event.
2695 BOOL oldHasMarkedText = hasMarkedText_;
2697 // This method should not be called recursively.
2698 DCHECK(!handlingKeyDown_);
2700 // Tells insertText: and doCommandBySelector: that we are handling a key
2702 handlingKeyDown_ = YES;
2704 // These variables might be set when handling the keyboard event.
2705 // Clear them here so that we can know whether they have changed afterwards.
2706 textToBeInserted_.clear();
2707 markedText_.clear();
2708 underlines_.clear();
2709 unmarkTextCalled_ = NO;
2710 hasEditCommands_ = NO;
2711 editCommands_.clear();
2713 // Before doing anything with a key down, check to see if plugin IME has been
2714 // cancelled, since the plugin host needs to be informed of that before
2715 // receiving the keydown.
2716 if ([theEvent type] == NSKeyDown)
2717 [self checkForPluginImeCancellation];
2719 // Sends key down events to input method first, then we can decide what should
2720 // be done according to input method's feedback.
2721 // If a plugin is active, bypass this step since events are forwarded directly
2722 // to the plugin IME.
2723 if (focusedPluginIdentifier_ == -1)
2724 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2726 handlingKeyDown_ = NO;
2728 // Indicates if we should send the key event and corresponding editor commands
2729 // after processing the input method result.
2730 BOOL delayEventUntilAfterImeCompostion = NO;
2732 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2733 // while an input method is composing or inserting a text.
2734 // Gmail checks this code in its onkeydown handler to stop auto-completing
2735 // e-mail addresses while composing a CJK text.
2736 // If the text to be inserted has only one character, then we don't need this
2737 // trick, because we'll send the text as a key press event instead.
2738 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2739 NativeWebKeyboardEvent fakeEvent = event;
2740 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
2741 fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2742 fakeEvent.skip_in_browser = true;
2743 widgetHost->ForwardKeyboardEvent(fakeEvent);
2744 // If this key event was handled by the input method, but
2745 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2746 // enqueued edit commands, then in order to let webkit handle them
2747 // correctly, we need to send the real key event and corresponding edit
2748 // commands after processing the input method result.
2749 // We shouldn't do this if a new marked text was set by the input method,
2750 // otherwise the new marked text might be cancelled by webkit.
2751 if (hasEditCommands_ && !hasMarkedText_)
2752 delayEventUntilAfterImeCompostion = YES;
2754 if (!editCommands_.empty()) {
2755 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2756 widgetHost->GetRoutingID(), editCommands_));
2758 widgetHost->ForwardKeyboardEvent(event);
2761 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2762 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2763 // be set to NULL. So we check it here and return immediately if it's NULL.
2764 if (!renderWidgetHostView_->render_widget_host_)
2767 // Then send keypress and/or composition related events.
2768 // If there was a marked text or the text to be inserted is longer than 1
2769 // character, then we send the text by calling ConfirmComposition().
2770 // Otherwise, if the text to be inserted only contains 1 character, then we
2771 // can just send a keypress event which is fabricated by changing the type of
2772 // the keydown event, so that we can retain all necessary informations, such
2773 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2774 // prevent the browser from handling it again.
2775 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2776 // handle BMP characters here, as we can always insert non-BMP characters as
2778 BOOL textInserted = NO;
2779 if (textToBeInserted_.length() >
2780 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2781 widgetHost->ImeConfirmComposition(
2782 textToBeInserted_, gfx::Range::InvalidRange(), false);
2786 // Updates or cancels the composition. If some text has been inserted, then
2787 // we don't need to cancel the composition explicitly.
2788 if (hasMarkedText_ && markedText_.length()) {
2789 // Sends the updated marked text to the renderer so it can update the
2790 // composition node in WebKit.
2791 // When marked text is available, |selectedRange_| will be the range being
2792 // selected inside the marked text.
2793 widgetHost->ImeSetComposition(markedText_, underlines_,
2794 selectedRange_.location,
2795 NSMaxRange(selectedRange_));
2796 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2797 if (unmarkTextCalled_) {
2798 widgetHost->ImeConfirmComposition(
2799 base::string16(), gfx::Range::InvalidRange(), false);
2801 widgetHost->ImeCancelComposition();
2805 // If the key event was handled by the input method but it also generated some
2806 // edit commands, then we need to send the real key event and corresponding
2807 // edit commands here. This usually occurs when the input method wants to
2808 // finish current composition session but still wants the application to
2809 // handle the key event. See http://crbug.com/48161 for reference.
2810 if (delayEventUntilAfterImeCompostion) {
2811 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2812 // with windowsKeyCode == 0xE5 has already been sent to webkit.
2813 // So before sending the real key down event, we need to send a fake key up
2814 // event to balance it.
2815 NativeWebKeyboardEvent fakeEvent = event;
2816 fakeEvent.type = blink::WebInputEvent::KeyUp;
2817 fakeEvent.skip_in_browser = true;
2818 widgetHost->ForwardKeyboardEvent(fakeEvent);
2819 // Not checking |renderWidgetHostView_->render_widget_host_| here because
2820 // a key event with |skip_in_browser| == true won't be handled by browser,
2821 // thus it won't destroy the widget.
2823 if (!editCommands_.empty()) {
2824 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2825 widgetHost->GetRoutingID(), editCommands_));
2827 widgetHost->ForwardKeyboardEvent(event);
2829 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2830 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2831 // be set to NULL. So we check it here and return immediately if it's NULL.
2832 if (!renderWidgetHostView_->render_widget_host_)
2836 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2837 // Only send a corresponding key press event if there is no marked text.
2838 if (!hasMarkedText_) {
2839 if (!textInserted && textToBeInserted_.length() == 1) {
2840 // If a single character was inserted, then we just send it as a keypress
2842 event.type = blink::WebInputEvent::Char;
2843 event.text[0] = textToBeInserted_[0];
2845 event.skip_in_browser = true;
2846 widgetHost->ForwardKeyboardEvent(event);
2847 } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2848 [[theEvent characters] length] > 0 &&
2849 (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2850 (hasEditCommands_ && editCommands_.empty()))) {
2851 // We don't get insertText: calls if ctrl or cmd is down, or the key event
2852 // generates an insert command. So synthesize a keypress event for these
2853 // cases, unless the key event generated any other command.
2854 event.type = blink::WebInputEvent::Char;
2855 event.skip_in_browser = true;
2856 widgetHost->ForwardKeyboardEvent(event);
2860 // Possibly autohide the cursor.
2861 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2862 [NSCursor setHiddenUntilMouseMoves:YES];
2865 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2866 DCHECK(base::mac::IsOSLionOrLater());
2868 if ([event phase] != NSEventPhaseEnded &&
2869 [event phase] != NSEventPhaseCancelled) {
2873 if (renderWidgetHostView_->render_widget_host_) {
2874 const WebMouseWheelEvent& webEvent =
2875 WebInputEventFactory::mouseWheelEvent(event, self);
2876 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2879 if (endWheelMonitor_) {
2880 [NSEvent removeMonitor:endWheelMonitor_];
2881 endWheelMonitor_ = nil;
2885 - (void)beginGestureWithEvent:(NSEvent*)event {
2886 [responderDelegate_ beginGestureWithEvent:event];
2888 - (void)endGestureWithEvent:(NSEvent*)event {
2889 [responderDelegate_ endGestureWithEvent:event];
2891 - (void)touchesMovedWithEvent:(NSEvent*)event {
2892 [responderDelegate_ touchesMovedWithEvent:event];
2894 - (void)touchesBeganWithEvent:(NSEvent*)event {
2895 [responderDelegate_ touchesBeganWithEvent:event];
2897 - (void)touchesCancelledWithEvent:(NSEvent*)event {
2898 [responderDelegate_ touchesCancelledWithEvent:event];
2900 - (void)touchesEndedWithEvent:(NSEvent*)event {
2901 [responderDelegate_ touchesEndedWithEvent:event];
2904 // This is invoked only on 10.8 or newer when the user taps a word using
2906 - (void)quickLookWithEvent:(NSEvent*)event {
2907 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
2908 TextInputClientMac::GetInstance()->GetStringAtPoint(
2909 renderWidgetHostView_->render_widget_host_,
2910 gfx::Point(point.x, NSHeight([self frame]) - point.y),
2911 ^(NSAttributedString* string, NSPoint baselinePoint) {
2912 if (string && [string length] > 0) {
2913 dispatch_async(dispatch_get_main_queue(), ^{
2914 [self showDefinitionForAttributedString:string
2915 atPoint:baselinePoint];
2922 // This method handles 2 different types of hardware events.
2923 // (Apple does not distinguish between them).
2924 // a. Scrolling the middle wheel of a mouse.
2925 // b. Swiping on the track pad.
2927 // This method is responsible for 2 types of behavior:
2928 // a. Scrolling the content of window.
2929 // b. Navigating forwards/backwards in history.
2931 // This is a brief description of the logic:
2932 // 1. If the content can be scrolled, scroll the content.
2933 // (This requires a roundtrip to blink to determine whether the content
2934 // can be scrolled.)
2935 // Once this logic is triggered, the navigate logic cannot be triggered
2936 // until the gesture finishes.
2937 // 2. If the user is making a horizontal swipe, start the navigate
2938 // forward/backwards UI.
2939 // Once this logic is triggered, the user can either cancel or complete
2940 // the gesture. If the user completes the gesture, all remaining touches
2941 // are swallowed, and not allowed to scroll the content. If the user
2942 // cancels the gesture, all remaining touches are forwarded to the content
2943 // scroll logic. The user cannot trigger the navigation logic again.
2944 - (void)scrollWheel:(NSEvent*)event {
2945 if (responderDelegate_ &&
2946 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2947 BOOL handled = [responderDelegate_ handleEvent:event];
2952 // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
2953 // the event is received even when the mouse cursor is no longer over the view
2954 // when the scrolling ends (e.g. if the tab was switched). This is necessary
2955 // for ending rubber-banding in such cases.
2956 if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
2957 !endWheelMonitor_) {
2959 [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
2960 handler:^(NSEvent* blockEvent) {
2961 [self shortCircuitScrollWheelEvent:blockEvent];
2966 // This is responsible for content scrolling!
2967 if (renderWidgetHostView_->render_widget_host_) {
2968 const WebMouseWheelEvent& webEvent =
2969 WebInputEventFactory::mouseWheelEvent(event, self);
2970 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2974 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
2975 NSWindow* oldWindow = [self window];
2977 // We're messing with the window, so do this to ensure no flashes. This one
2978 // prevents a flash when the current tab is closed.
2979 if (!renderWidgetHostView_->use_core_animation_)
2980 [oldWindow disableScreenUpdatesUntilFlush];
2982 NSNotificationCenter* notificationCenter =
2983 [NSNotificationCenter defaultCenter];
2985 // Backing property notifications crash on 10.6 when building with the 10.7
2986 // SDK, see http://crbug.com/260595.
2987 static BOOL supportsBackingPropertiesNotification =
2988 SupportsBackingPropertiesChangedNotification();
2991 if (supportsBackingPropertiesNotification) {
2994 name:NSWindowDidChangeBackingPropertiesNotification
2999 name:NSWindowDidMoveNotification
3003 name:NSWindowDidEndLiveResizeNotification
3007 if (supportsBackingPropertiesNotification) {
3010 selector:@selector(windowDidChangeBackingProperties:)
3011 name:NSWindowDidChangeBackingPropertiesNotification
3016 selector:@selector(windowChangedGlobalFrame:)
3017 name:NSWindowDidMoveNotification
3021 selector:@selector(windowChangedGlobalFrame:)
3022 name:NSWindowDidEndLiveResizeNotification
3027 - (void)updateScreenProperties{
3028 renderWidgetHostView_->UpdateBackingStoreScaleFactor();
3029 renderWidgetHostView_->UpdateDisplayLink();
3032 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
3033 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
3034 // Background tabs check if their scale factor or vsync properties changed
3035 // when they are added to a window.
3037 // Allocating a CGLayerRef with the current scale factor immediately from
3038 // this handler doesn't work. Schedule the backing store update on the
3039 // next runloop cycle, then things are read for CGLayerRef allocations to
3041 [self performSelector:@selector(updateScreenProperties)
3046 - (void)globalFrameDidChange:(NSNotification*)notification {
3047 if (handlingGlobalFrameDidChange_)
3050 handlingGlobalFrameDidChange_ = YES;
3051 if (!renderWidgetHostView_->use_core_animation_ &&
3052 renderWidgetHostView_->compositing_iosurface_context_) {
3053 [renderWidgetHostView_->compositing_iosurface_context_->nsgl_context()
3056 handlingGlobalFrameDidChange_ = NO;
3059 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
3060 renderWidgetHostView_->UpdateScreenInfo(
3061 renderWidgetHostView_->GetNativeView());
3064 - (void)setFrameSize:(NSSize)newSize {
3065 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
3067 // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
3068 // -setFrame: isn't neccessary.
3069 [super setFrameSize:newSize];
3071 if (!renderWidgetHostView_->render_widget_host_)
3074 // Move the CALayers to their positions in the new view size. Note that
3075 // this will not draw anything because the non-background layers' sizes
3076 // didn't actually change.
3077 renderWidgetHostView_->LayoutLayers();
3079 renderWidgetHostView_->render_widget_host_->SendScreenRects();
3080 renderWidgetHostView_->render_widget_host_->WasResized();
3082 // Wait for the frame that WasResize might have requested. If the view is
3083 // being made visible at a new size, then this call will have no effect
3084 // because the view widget is still hidden, and the pause call in WasShown
3085 // will have this effect for us.
3086 renderWidgetHostView_->PauseForPendingResizeOrRepaintsAndDraw();
3089 // Fills with white the parts of the area to the right and bottom for |rect|
3090 // that intersect |damagedRect|.
3091 - (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
3092 dirtyRect:(gfx::Rect)damagedRect
3093 inContext:(CGContextRef)context {
3094 if (damagedRect.right() > rect.right()) {
3095 int x = std::max(rect.right(), damagedRect.x());
3096 int y = std::min(rect.bottom(), damagedRect.bottom());
3097 int width = damagedRect.right() - x;
3098 int height = damagedRect.y() - y;
3100 // Extra fun to get around the fact that gfx::Rects can't have
3111 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
3112 CGContextSetFillColorWithColor(context,
3113 CGColorGetConstantColor(kCGColorWhite));
3114 CGContextFillRect(context, NSRectToCGRect(r));
3116 if (damagedRect.bottom() > rect.bottom()) {
3117 int x = damagedRect.x();
3118 int y = damagedRect.bottom();
3119 int width = damagedRect.right() - x;
3120 int height = std::max(rect.bottom(), damagedRect.y()) - y;
3122 // Extra fun to get around the fact that gfx::Rects can't have
3133 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
3134 CGContextSetFillColorWithColor(context,
3135 CGColorGetConstantColor(kCGColorWhite));
3136 CGContextFillRect(context, NSRectToCGRect(r));
3140 - (void)drawRect:(NSRect)dirtyRect {
3141 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::drawRect");
3142 DCHECK(!renderWidgetHostView_->use_core_animation_);
3144 if (!renderWidgetHostView_->render_widget_host_) {
3145 // When using CoreAnimation, this path is used to paint the contents area
3146 // white before any frames come in. When layers to draw frames exist, this
3148 [[NSColor whiteColor] set];
3149 NSRectFill(dirtyRect);
3153 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
3154 renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
3156 const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
3158 if (renderWidgetHostView_->last_frame_was_accelerated_ &&
3159 renderWidgetHostView_->compositing_iosurface_) {
3160 if (renderWidgetHostView_->allow_overlapping_views_) {
3161 // If overlapping views need to be allowed, punch a hole in the window
3162 // to expose the GL underlay.
3163 TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
3164 "h", damagedRect.height());
3165 // NSRectFill is extremely slow (15ms for a window on a fast MacPro), so
3166 // this is only done when it's a real invalidation from window damage (not
3167 // when a BuffersSwapped was received). Note that even a 1x1 NSRectFill
3168 // can take many milliseconds sometimes (!) so this is skipped completely
3169 // for drawRects that are triggered by BuffersSwapped messages.
3170 [[NSColor clearColor] set];
3171 NSRectFill(dirtyRect);
3174 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
3175 renderWidgetHostView_->compositing_iosurface_context_->cgl_context());
3176 renderWidgetHostView_->DrawIOSurfaceWithoutCoreAnimation();
3180 CGContextRef context = static_cast<CGContextRef>(
3181 [[NSGraphicsContext currentContext] graphicsPort]);
3182 [self drawBackingStore:backingStore
3183 dirtyRect:NSRectToCGRect(dirtyRect)
3187 - (void)drawBackingStore:(BackingStoreMac*)backingStore
3188 dirtyRect:(CGRect)dirtyRect
3189 inContext:(CGContextRef)context {
3190 content::SoftwareFrameManager* software_frame_manager =
3191 renderWidgetHostView_->software_frame_manager_.get();
3192 // There should never be both a legacy software and software composited
3194 DCHECK(!backingStore || !software_frame_manager->HasCurrentFrame());
3196 if (backingStore || software_frame_manager->HasCurrentFrame()) {
3197 // Note: All coordinates are in view units, not pixels.
3198 gfx::Rect bitmapRect(
3199 software_frame_manager->HasCurrentFrame() ?
3200 software_frame_manager->GetCurrentFrameSizeInDIP() :
3201 backingStore->size());
3203 // Specify the proper y offset to ensure that the view is rooted to the
3204 // upper left corner. This can be negative, if the window was resized
3205 // smaller and the renderer hasn't yet repainted.
3206 int yOffset = NSHeight([self bounds]) - bitmapRect.height();
3208 NSRect nsDirtyRect = NSRectFromCGRect(dirtyRect);
3209 const gfx::Rect damagedRect([self flipNSRectToRect:nsDirtyRect]);
3211 gfx::Rect paintRect = gfx::IntersectRects(bitmapRect, damagedRect);
3212 if (!paintRect.IsEmpty()) {
3213 if (software_frame_manager->HasCurrentFrame()) {
3214 // If a software compositor framebuffer is present, draw using that.
3215 gfx::Size sizeInPixels =
3216 software_frame_manager->GetCurrentFrameSizeInPixels();
3217 base::ScopedCFTypeRef<CGDataProviderRef> dataProvider(
3218 CGDataProviderCreateWithData(
3220 software_frame_manager->GetCurrentFramePixels(),
3221 4 * sizeInPixels.width() * sizeInPixels.height(),
3223 base::ScopedCFTypeRef<CGImageRef> image(
3225 sizeInPixels.width(),
3226 sizeInPixels.height(),
3229 4 * sizeInPixels.width(),
3230 base::mac::GetSystemColorSpace(),
3231 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
3235 kCGRenderingIntentDefault));
3236 CGRect imageRect = bitmapRect.ToCGRect();
3237 imageRect.origin.y = yOffset;
3238 CGContextDrawImage(context, imageRect, image);
3239 } else if (backingStore->cg_layer()) {
3240 // If we have a CGLayer, draw that into the window
3241 // TODO: add clipping to dirtyRect if it improves drawing performance.
3242 CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset),
3243 backingStore->cg_layer());
3245 // If we haven't created a layer yet, draw the cached bitmap into
3246 // the window. The CGLayer will be created the next time the renderer
3248 base::ScopedCFTypeRef<CGImageRef> image(
3249 CGBitmapContextCreateImage(backingStore->cg_bitmap()));
3250 CGRect imageRect = bitmapRect.ToCGRect();
3251 imageRect.origin.y = yOffset;
3252 CGContextDrawImage(context, imageRect, image);
3256 renderWidgetHostView_->SendPendingLatencyInfoToHost();
3258 // Fill the remaining portion of the damagedRect with white
3259 [self fillBottomRightRemainderOfRect:bitmapRect
3260 dirtyRect:damagedRect
3263 if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
3264 base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
3265 renderWidgetHostView_->whiteout_start_time_;
3266 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
3268 // Reset the start time to 0 so that we start recording again the next
3269 // time the backing store is NULL...
3270 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
3272 if (!renderWidgetHostView_->web_contents_switch_paint_time_.is_null()) {
3273 base::TimeDelta web_contents_switch_paint_duration =
3274 base::TimeTicks::Now() -
3275 renderWidgetHostView_->web_contents_switch_paint_time_;
3276 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
3277 web_contents_switch_paint_duration);
3278 // Reset contents_switch_paint_time_ to 0 so future tab selections are
3280 renderWidgetHostView_->web_contents_switch_paint_time_ =
3284 CGContextSetFillColorWithColor(context,
3285 CGColorGetConstantColor(kCGColorWhite));
3286 CGContextFillRect(context, dirtyRect);
3287 if (renderWidgetHostView_->whiteout_start_time_.is_null())
3288 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
3292 - (BOOL)canBecomeKeyView {
3293 if (!renderWidgetHostView_->render_widget_host_)
3296 return canBeKeyView_;
3299 - (BOOL)acceptsFirstResponder {
3300 if (!renderWidgetHostView_->render_widget_host_)
3303 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
3306 - (BOOL)becomeFirstResponder {
3307 if (!renderWidgetHostView_->render_widget_host_)
3310 renderWidgetHostView_->render_widget_host_->Focus();
3311 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
3312 renderWidgetHostView_->SetTextInputActive(true);
3314 // Cancel any onging composition text which was left before we lost focus.
3315 // TODO(suzhe): We should do it in -resignFirstResponder: method, but
3316 // somehow that method won't be called when switching among different tabs.
3317 // See http://crbug.com/47209
3318 [self cancelComposition];
3320 NSNumber* direction = [NSNumber numberWithUnsignedInteger:
3321 [[self window] keyViewSelectionDirection]];
3322 NSDictionary* userInfo =
3323 [NSDictionary dictionaryWithObject:direction
3324 forKey:kSelectionDirection];
3325 [[NSNotificationCenter defaultCenter]
3326 postNotificationName:kViewDidBecomeFirstResponder
3333 - (BOOL)resignFirstResponder {
3334 renderWidgetHostView_->SetTextInputActive(false);
3335 if (!renderWidgetHostView_->render_widget_host_)
3338 if (closeOnDeactivate_)
3339 renderWidgetHostView_->KillSelf();
3341 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
3342 renderWidgetHostView_->render_widget_host_->Blur();
3344 // We should cancel any onging composition whenever RWH's Blur() method gets
3345 // called, because in this case, webkit will confirm the ongoing composition
3347 [self cancelComposition];
3352 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
3353 if (responderDelegate_ &&
3355 respondsToSelector:@selector(validateUserInterfaceItem:
3359 [responderDelegate_ validateUserInterfaceItem:item isValidItem:&valid];
3364 SEL action = [item action];
3366 if (action == @selector(stopSpeaking:)) {
3367 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3368 renderWidgetHostView_->IsSpeaking();
3370 if (action == @selector(startSpeaking:)) {
3371 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3372 renderWidgetHostView_->SupportsSpeech();
3375 // For now, these actions are always enabled for render view,
3376 // this is sub-optimal.
3377 // TODO(suzhe): Plumb the "can*" methods up from WebCore.
3378 if (action == @selector(undo:) ||
3379 action == @selector(redo:) ||
3380 action == @selector(cut:) ||
3381 action == @selector(copy:) ||
3382 action == @selector(copyToFindPboard:) ||
3383 action == @selector(paste:) ||
3384 action == @selector(pasteAndMatchStyle:)) {
3385 return renderWidgetHostView_->render_widget_host_->IsRenderView();
3388 return editCommand_helper_->IsMenuItemEnabled(action, self);
3391 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
3392 return renderWidgetHostView_.get();
3395 // Determine whether we should autohide the cursor (i.e., hide it until mouse
3396 // move) for the given event. Customize here to be more selective about which
3397 // key presses to autohide on.
3398 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
3399 return ([event type] == NSKeyDown &&
3400 !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
3403 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
3404 index:(NSUInteger)index
3405 maxCount:(NSUInteger)maxCount {
3406 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3407 NSUInteger totalLength = [fullArray count];
3408 if (index >= totalLength)
3410 NSUInteger length = MIN(totalLength - index, maxCount);
3411 return [fullArray subarrayWithRange:NSMakeRange(index, length)];
3414 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
3415 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3416 return [fullArray count];
3419 - (id)accessibilityAttributeValue:(NSString *)attribute {
3420 BrowserAccessibilityManager* manager =
3421 renderWidgetHostView_->GetBrowserAccessibilityManager();
3423 // Contents specifies document view of RenderWidgetHostViewCocoa provided by
3424 // BrowserAccessibilityManager. Children includes all subviews in addition to
3425 // contents. Currently we do not have subviews besides the document view.
3426 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
3427 [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
3429 return [NSArray arrayWithObjects:manager->
3430 GetRoot()->ToBrowserAccessibilityCocoa(), nil];
3431 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
3432 return NSAccessibilityScrollAreaRole;
3434 id ret = [super accessibilityAttributeValue:attribute];
3438 - (NSArray*)accessibilityAttributeNames {
3439 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
3440 [ret addObject:NSAccessibilityContentsAttribute];
3441 [ret addObjectsFromArray:[super accessibilityAttributeNames]];
3445 - (id)accessibilityHitTest:(NSPoint)point {
3446 if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
3448 NSPoint pointInWindow = [[self window] convertScreenToBase:point];
3449 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
3450 localPoint.y = NSHeight([self bounds]) - localPoint.y;
3451 BrowserAccessibilityCocoa* root = renderWidgetHostView_->
3452 GetBrowserAccessibilityManager()->
3453 GetRoot()->ToBrowserAccessibilityCocoa();
3454 id obj = [root accessibilityHitTest:localPoint];
3458 - (BOOL)accessibilityIsIgnored {
3459 return !renderWidgetHostView_->GetBrowserAccessibilityManager();
3462 - (NSUInteger)accessibilityGetIndexOf:(id)child {
3463 BrowserAccessibilityManager* manager =
3464 renderWidgetHostView_->GetBrowserAccessibilityManager();
3465 // Only child is root.
3467 manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
3474 - (id)accessibilityFocusedUIElement {
3475 BrowserAccessibilityManager* manager =
3476 renderWidgetHostView_->GetBrowserAccessibilityManager();
3478 BrowserAccessibility* focused_item = manager->GetFocus(NULL);
3479 DCHECK(focused_item);
3481 BrowserAccessibilityCocoa* focused_item_cocoa =
3482 focused_item->ToBrowserAccessibilityCocoa();
3483 DCHECK(focused_item_cocoa);
3484 if (focused_item_cocoa)
3485 return focused_item_cocoa;
3488 return [super accessibilityFocusedUIElement];
3491 - (void)doDefaultAction:(int32)accessibilityObjectId {
3492 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3493 rwh->Send(new AccessibilityMsg_DoDefaultAction(
3494 rwh->GetRoutingID(), accessibilityObjectId));
3497 // VoiceOver uses this method to move the caret to the beginning of the next
3498 // word in a text field.
3499 - (void)accessibilitySetTextSelection:(int32)accId
3500 startOffset:(int32)startOffset
3501 endOffset:(int32)endOffset {
3502 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3503 rwh->AccessibilitySetTextSelection(accId, startOffset, endOffset);
3506 // Convert a web accessibility's location in web coordinates into a cocoa
3507 // screen coordinate.
3508 - (NSPoint)accessibilityPointInScreen:(NSPoint)origin
3510 origin.y = NSHeight([self bounds]) - origin.y;
3511 NSPoint originInWindow = [self convertPoint:origin toView:nil];
3512 NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow];
3513 originInScreen.y = originInScreen.y - size.height;
3514 return originInScreen;
3517 - (void)setAccessibilityFocus:(BOOL)focus
3518 accessibilityId:(int32)accessibilityObjectId {
3520 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3521 rwh->Send(new AccessibilityMsg_SetFocus(
3522 rwh->GetRoutingID(), accessibilityObjectId));
3524 // Immediately set the focused item even though we have not officially set
3525 // focus on it as VoiceOver expects to get the focused item after this
3527 BrowserAccessibilityManager* manager =
3528 renderWidgetHostView_->GetBrowserAccessibilityManager();
3529 manager->SetFocus(manager->GetFromRendererID(accessibilityObjectId), false);
3533 - (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
3534 // Performs a right click copying WebKit's
3535 // accessibilityPerformShowMenuAction.
3536 NSPoint origin = [accessibility origin];
3537 NSSize size = [[accessibility size] sizeValue];
3538 NSPoint location = [self accessibilityPointInScreen:origin size:size];
3539 location = [[self window] convertScreenToBase:location];
3540 location.x += size.width/2;
3541 location.y += size.height/2;
3543 NSEvent* fakeRightClick = [NSEvent
3544 mouseEventWithType:NSRightMouseDown
3548 windowNumber:[[self window] windowNumber]
3549 context:[NSGraphicsContext currentContext]
3554 [self mouseEvent:fakeRightClick];
3557 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
3558 // with minor modifications for code style and commenting.
3560 // The 'public' interface is -setToolTipAtMousePoint:. This differs from
3561 // -setToolTip: in that the updated tooltip takes effect immediately,
3562 // without the user's having to move the mouse out of and back into the view.
3564 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
3565 // the view, which in turn requires overriding some internal tracking-rect
3566 // methods (to keep track of its owner & userdata, which need to be filled out
3567 // in the fake events.) --snej 7/6/09
3571 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3572 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
3574 * Redistribution and use in source and binary forms, with or without
3575 * modification, are permitted provided that the following conditions
3578 * 1. Redistributions of source code must retain the above copyright
3579 * notice, this list of conditions and the following disclaimer.
3580 * 2. Redistributions in binary form must reproduce the above copyright
3581 * notice, this list of conditions and the following disclaimer in the
3582 * documentation and/or other materials provided with the distribution.
3583 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
3584 * its contributors may be used to endorse or promote products derived
3585 * from this software without specific prior written permission.
3587 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
3588 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3589 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3590 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
3591 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3592 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
3593 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3594 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3595 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3596 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3599 // Any non-zero value will do, but using something recognizable might help us
3601 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
3603 // Override of a public NSView method, replacing the inherited functionality.
3604 // See above for rationale.
3605 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
3607 userData:(void *)data
3608 assumeInside:(BOOL)assumeInside {
3609 DCHECK(trackingRectOwner_ == nil);
3610 trackingRectOwner_ = owner;
3611 trackingRectUserData_ = data;
3612 return kTrackingRectTag;
3615 // Override of (apparently) a private NSView method(!) See above for rationale.
3616 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
3618 userData:(void *)data
3619 assumeInside:(BOOL)assumeInside
3620 useTrackingNum:(int)tag {
3621 DCHECK(tag == 0 || tag == kTrackingRectTag);
3622 DCHECK(trackingRectOwner_ == nil);
3623 trackingRectOwner_ = owner;
3624 trackingRectUserData_ = data;
3625 return kTrackingRectTag;
3628 // Override of (apparently) a private NSView method(!) See above for rationale.
3629 - (void)_addTrackingRects:(NSRect *)rects
3631 userDataList:(void **)userDataList
3632 assumeInsideList:(BOOL *)assumeInsideList
3633 trackingNums:(NSTrackingRectTag *)trackingNums
3636 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3637 DCHECK(trackingRectOwner_ == nil);
3638 trackingRectOwner_ = owner;
3639 trackingRectUserData_ = userDataList[0];
3640 trackingNums[0] = kTrackingRectTag;
3643 // Override of a public NSView method, replacing the inherited functionality.
3644 // See above for rationale.
3645 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3649 if (tag == kTrackingRectTag) {
3650 trackingRectOwner_ = nil;
3654 if (tag == lastToolTipTag_) {
3655 [super removeTrackingRect:tag];
3656 lastToolTipTag_ = 0;
3660 // If any other tracking rect is being removed, we don't know how it was
3661 // created and it's possible there's a leak involved (see Radar 3500217).
3665 // Override of (apparently) a private NSView method(!)
3666 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3667 for (int i = 0; i < count; ++i) {
3671 DCHECK(tag == kTrackingRectTag);
3672 trackingRectOwner_ = nil;
3676 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3677 - (void)_sendToolTipMouseExited {
3678 // Nothing matters except window, trackingNumber, and userData.
3679 int windowNumber = [[self window] windowNumber];
3680 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3681 location:NSZeroPoint
3684 windowNumber:windowNumber
3687 trackingNumber:kTrackingRectTag
3688 userData:trackingRectUserData_];
3689 [trackingRectOwner_ mouseExited:fakeEvent];
3692 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3693 - (void)_sendToolTipMouseEntered {
3694 // Nothing matters except window, trackingNumber, and userData.
3695 int windowNumber = [[self window] windowNumber];
3696 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3697 location:NSZeroPoint
3700 windowNumber:windowNumber
3703 trackingNumber:kTrackingRectTag
3704 userData:trackingRectUserData_];
3705 [trackingRectOwner_ mouseEntered:fakeEvent];
3708 // Sets the view's current tooltip, to be displayed at the current mouse
3709 // location. (This does not make the tooltip appear -- as usual, it only
3710 // appears after a delay.) Pass null to remove the tooltip.
3711 - (void)setToolTipAtMousePoint:(NSString *)string {
3712 NSString *toolTip = [string length] == 0 ? nil : string;
3713 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3714 (!toolTip && !toolTip_)) {
3719 [self _sendToolTipMouseExited];
3722 toolTip_.reset([toolTip copy]);
3725 // See radar 3500217 for why we remove all tooltips
3726 // rather than just the single one we created.
3727 [self removeAllToolTips];
3728 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3729 lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3732 [self _sendToolTipMouseEntered];
3736 // NSView calls this to get the text when displaying the tooltip.
3737 - (NSString *)view:(NSView *)view
3738 stringForToolTip:(NSToolTipTag)tag
3739 point:(NSPoint)point
3740 userData:(void *)data {
3741 return [[toolTip_ copy] autorelease];
3744 // Below is our NSTextInputClient implementation.
3746 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3747 // functions to process this event.
3749 // [WebHTMLView keyDown] ->
3750 // EventHandler::keyEvent() ->
3752 // [WebEditorClient handleKeyboardEvent] ->
3753 // [WebHTMLView _interceptEditingKeyEvent] ->
3754 // [NSResponder interpretKeyEvents] ->
3755 // [WebHTMLView insertText] ->
3756 // Editor::insertText()
3758 // Unfortunately, it is hard for Chromium to use this implementation because
3759 // it causes key-typing jank.
3760 // RenderWidgetHostViewMac is running in a browser process. On the other
3761 // hand, Editor and EventHandler are running in a renderer process.
3762 // So, if we used this implementation, a NSKeyDown event is dispatched to
3763 // the following functions of Chromium.
3765 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3766 // |Sync IPC (KeyDown)| (*1) ->
3767 // EventHandler::keyEvent() (renderer) ->
3769 // EditorClientImpl::handleKeyboardEvent() (renderer) ->
3770 // |Sync IPC| (*2) ->
3771 // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3772 // [self interpretKeyEvents] ->
3773 // [RenderWidgetHostViewMac insertText] (browser) ->
3775 // Editor::insertText() (renderer)
3777 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3778 // result of EventHandler::keyEvent().
3779 // (*2) we need to wait until this call finishes since WebEditorClient uses
3780 // the result of [WebHTMLView _interceptEditingKeyEvent].
3782 // This needs many sync IPC messages sent between a browser and a renderer for
3783 // each key event, which would probably result in key-typing jank.
3784 // To avoid this problem, this implementation processes key events (and input
3785 // method events) totally in a browser process and sends asynchronous input
3786 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3787 // renderer process.
3789 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3790 // |Async IPC (RawKeyDown)| ->
3791 // [self interpretKeyEvents] ->
3792 // [RenderWidgetHostViewMac insertText] (browser) ->
3793 // |Async IPC (Char)| ->
3794 // Editor::insertText() (renderer)
3796 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3797 // make any key-typing jank. --hbono 7/23/09
3800 extern NSString *NSTextInputReplacementRangeAttributeName;
3803 - (NSArray *)validAttributesForMarkedText {
3804 // This code is just copied from WebKit except renaming variables.
3805 if (!validAttributesForMarkedText_) {
3806 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3807 NSUnderlineStyleAttributeName,
3808 NSUnderlineColorAttributeName,
3809 NSMarkedClauseSegmentAttributeName,
3810 NSTextInputReplacementRangeAttributeName,
3813 return validAttributesForMarkedText_.get();
3816 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3817 DCHECK([self window]);
3818 // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3819 // coordinates (upper left origin). Scroll offsets will be taken care of in
3821 thePoint = [[self window] convertScreenToBase:thePoint];
3822 thePoint = [self convertPoint:thePoint fromView:nil];
3823 thePoint.y = NSHeight([self frame]) - thePoint.y;
3826 TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3827 renderWidgetHostView_->render_widget_host_,
3828 gfx::Point(thePoint.x, thePoint.y));
3832 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3833 actualRange:(NSRangePointer)actualRange {
3835 if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3839 rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3840 renderWidgetHostView_->render_widget_host_, theRange);
3842 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3844 *actualRange = theRange;
3847 // The returned rectangle is in WebKit coordinates (upper left origin), so
3848 // flip the coordinate system.
3849 NSRect viewFrame = [self frame];
3850 rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3854 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3855 actualRange:(NSRangePointer)actualRange {
3856 NSRect rect = [self firstViewRectForCharacterRange:theRange
3857 actualRange:actualRange];
3859 // Convert into screen coordinates for return.
3860 rect = [self convertRect:rect toView:nil];
3861 rect.origin = [[self window] convertBaseToScreen:rect.origin];
3865 - (NSRange)markedRange {
3866 // An input method calls this method to check if an application really has
3867 // a text being composed when hasMarkedText call returns true.
3868 // Returns the range saved in the setMarkedText method so the input method
3869 // calls the setMarkedText method and we can update the composition node
3870 // there. (When this method returns an empty range, the input method doesn't
3871 // call the setMarkedText method.)
3872 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3875 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3876 actualRange:(NSRangePointer)actualRange {
3877 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3879 *actualRange = range;
3880 NSAttributedString* str =
3881 TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3882 renderWidgetHostView_->render_widget_host_, range);
3886 - (NSInteger)conversationIdentifier {
3887 return reinterpret_cast<NSInteger>(self);
3890 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3891 // nil when the caret is in non-editable content or password box to avoid
3892 // making input methods do their work.
3893 - (NSTextInputContext *)inputContext {
3894 if (focusedPluginIdentifier_ != -1)
3895 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3897 switch(renderWidgetHostView_->text_input_type_) {
3898 case ui::TEXT_INPUT_TYPE_NONE:
3899 case ui::TEXT_INPUT_TYPE_PASSWORD:
3902 return [super inputContext];
3906 - (BOOL)hasMarkedText {
3907 // An input method calls this function to figure out whether or not an
3908 // application is really composing a text. If it is composing, it calls
3909 // the markedRange method, and maybe calls the setMarkedText method.
3910 // It seems an input method usually calls this function when it is about to
3911 // cancel an ongoing composition. If an application has a non-empty marked
3912 // range, it calls the setMarkedText method to delete the range.
3913 return hasMarkedText_;
3916 - (void)unmarkText {
3917 // Delete the composition node of the renderer and finish an ongoing
3919 // It seems an input method calls the setMarkedText method and set an empty
3920 // text when it cancels an ongoing composition, i.e. I have never seen an
3921 // input method calls this method.
3922 hasMarkedText_ = NO;
3923 markedText_.clear();
3924 underlines_.clear();
3926 // If we are handling a key down event, then ConfirmComposition() will be
3927 // called in keyEvent: method.
3928 if (!handlingKeyDown_) {
3929 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3930 base::string16(), gfx::Range::InvalidRange(), false);
3932 unmarkTextCalled_ = YES;
3936 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3937 replacementRange:(NSRange)replacementRange {
3938 // An input method updates the composition string.
3939 // We send the given text and range to the renderer so it can update the
3940 // composition node of WebKit.
3941 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3942 // the full web content.
3943 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3944 NSString* im_text = isAttributedString ? [string string] : string;
3945 int length = [im_text length];
3947 // |markedRange_| will get set on a callback from ImeSetComposition().
3948 selectedRange_ = newSelRange;
3949 markedText_ = base::SysNSStringToUTF16(im_text);
3950 hasMarkedText_ = (length > 0);
3952 underlines_.clear();
3953 if (isAttributedString) {
3954 ExtractUnderlines(string, &underlines_);
3956 // Use a thin black underline by default.
3957 underlines_.push_back(
3958 blink::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
3961 // If we are handling a key down event, then SetComposition() will be
3962 // called in keyEvent: method.
3963 // Input methods of Mac use setMarkedText calls with an empty text to cancel
3964 // an ongoing composition. So, we should check whether or not the given text
3965 // is empty to update the input method state. (Our input method backend can
3966 // automatically cancels an ongoing composition when we send an empty text.
3967 // So, it is OK to send an empty text to the renderer.)
3968 if (!handlingKeyDown_) {
3969 renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3970 markedText_, underlines_,
3971 newSelRange.location, NSMaxRange(newSelRange));
3975 - (void)doCommandBySelector:(SEL)selector {
3976 // An input method calls this function to dispatch an editing command to be
3977 // handled by this view.
3978 if (selector == @selector(noop:))
3981 std::string command(
3982 [RenderWidgetHostViewMacEditCommandHelper::
3983 CommandNameForSelector(selector) UTF8String]);
3985 // If this method is called when handling a key down event, then we need to
3986 // handle the command in the key event handler. Otherwise we can just handle
3988 if (handlingKeyDown_) {
3989 hasEditCommands_ = YES;
3990 // We ignore commands that insert characters, because this was causing
3991 // strange behavior (e.g. tab always inserted a tab rather than moving to
3992 // the next field on the page).
3993 if (!StartsWithASCII(command, "insert", false))
3994 editCommands_.push_back(EditCommand(command, ""));
3996 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3997 rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
4002 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
4003 // An input method has characters to be inserted.
4004 // Same as Linux, Mac calls this method not only:
4005 // * when an input method finishs composing text, but also;
4006 // * when we type an ASCII character (without using input methods).
4007 // When we aren't using input methods, we should send the given character as
4008 // a Char event so it is dispatched to an onkeypress() event handler of
4010 // On the other hand, when we are using input methods, we should send the
4011 // given characters as an input method event and prevent the characters from
4012 // being dispatched to onkeypress() event handlers.
4013 // Text inserting might be initiated by other source instead of keyboard
4014 // events, such as the Characters dialog. In this case the text should be
4015 // sent as an input method event as well.
4016 // TODO(suzhe): It's hard for us to support replacementRange without accessing
4017 // the full web content.
4018 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
4019 NSString* im_text = isAttributedString ? [string string] : string;
4020 if (handlingKeyDown_) {
4021 textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
4023 gfx::Range replacement_range(replacementRange);
4024 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
4025 base::SysNSStringToUTF16(im_text), replacement_range, false);
4028 // Inserting text will delete all marked text automatically.
4029 hasMarkedText_ = NO;
4032 - (void)insertText:(id)string {
4033 [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
4036 - (void)viewDidMoveToWindow {
4038 [self updateScreenProperties];
4040 if (canBeKeyView_) {
4041 NSWindow* newWindow = [self window];
4042 // Pointer comparison only, since we don't know if lastWindow_ is still
4045 // If we move into a new window, refresh the frame information. We
4046 // don't need to do it if it was the same window as it used to be in,
4047 // since that case is covered by WasShown(). We only want to do this for
4048 // real browser views, not popups.
4049 if (newWindow != lastWindow_) {
4050 lastWindow_ = newWindow;
4051 renderWidgetHostView_->WindowFrameChanged();
4056 // If we switch windows (or are removed from the view hierarchy), cancel any
4057 // open mouse-downs.
4058 if (hasOpenMouseDown_) {
4059 WebMouseEvent event;
4060 event.type = WebInputEvent::MouseUp;
4061 event.button = WebMouseEvent::ButtonLeft;
4062 renderWidgetHostView_->ForwardMouseEvent(event);
4064 hasOpenMouseDown_ = NO;
4068 - (void)undo:(id)sender {
4069 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4074 - (void)redo:(id)sender {
4075 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4080 - (void)cut:(id)sender {
4081 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4086 - (void)copy:(id)sender {
4087 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4092 - (void)copyToFindPboard:(id)sender {
4093 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4095 host->CopyToFindPboard();
4098 - (void)paste:(id)sender {
4099 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4104 - (void)pasteAndMatchStyle:(id)sender {
4105 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4107 host->PasteAndMatchStyle();
4110 - (void)selectAll:(id)sender {
4111 // editCommand_helper_ adds implementations for most NSResponder methods
4112 // dynamically. But the renderer side only sends selection results back to
4113 // the browser if they were triggered by a keyboard event or went through
4114 // one of the Select methods on RWH. Since selectAll: is called from the
4115 // menu handler, neither is true.
4116 // Explicitly call SelectAll() here to make sure the renderer returns
4117 // selection results.
4118 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4123 - (void)startSpeaking:(id)sender {
4124 renderWidgetHostView_->SpeakSelection();
4127 - (void)stopSpeaking:(id)sender {
4128 renderWidgetHostView_->StopSpeaking();
4131 - (void)cancelComposition {
4132 if (!hasMarkedText_)
4135 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
4136 // doesn't call any NSTextInput functions, such as setMarkedText or
4137 // insertText. So, we need to send an IPC message to a renderer so it can
4138 // delete the composition node.
4139 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
4140 [currentInputManager markedTextAbandoned:self];
4142 hasMarkedText_ = NO;
4143 // Should not call [self unmarkText] here, because it'll send unnecessary
4144 // cancel composition IPC message to the renderer.
4147 - (void)confirmComposition {
4148 if (!hasMarkedText_)
4151 if (renderWidgetHostView_->render_widget_host_)
4152 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
4153 base::string16(), gfx::Range::InvalidRange(), false);
4155 [self cancelComposition];
4158 - (void)setPluginImeActive:(BOOL)active {
4159 if (active == pluginImeActive_)
4162 pluginImeActive_ = active;
4164 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
4165 renderWidgetHostView_->PluginImeCompositionCompleted(
4166 base::string16(), focusedPluginIdentifier_);
4170 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
4172 focusedPluginIdentifier_ = pluginId;
4173 else if (focusedPluginIdentifier_ == pluginId)
4174 focusedPluginIdentifier_ = -1;
4176 // Whenever plugin focus changes, plugin IME resets.
4177 [self setPluginImeActive:NO];
4180 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
4181 if (!pluginImeActive_)
4184 ComplexTextInputPanel* inputPanel =
4185 [ComplexTextInputPanel sharedComplexTextInputPanel];
4186 NSString* composited_string = nil;
4187 BOOL handled = [inputPanel interpretKeyEvent:event
4188 string:&composited_string];
4189 if (composited_string) {
4190 renderWidgetHostView_->PluginImeCompositionCompleted(
4191 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
4192 pluginImeActive_ = NO;
4197 - (void)checkForPluginImeCancellation {
4198 if (pluginImeActive_ &&
4199 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
4200 renderWidgetHostView_->PluginImeCompositionCompleted(
4201 base::string16(), focusedPluginIdentifier_);
4202 pluginImeActive_ = NO;
4206 // Overriding a NSResponder method to support application services.
4208 - (id)validRequestorForSendType:(NSString*)sendType
4209 returnType:(NSString*)returnType {
4211 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
4212 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
4213 BOOL hasText = !renderWidgetHostView_->selected_text().empty();
4215 renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
4217 if (sendTypeIsString && hasText && !returnType) {
4219 } else if (!sendType && returnTypeIsString && takesText) {
4221 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
4224 requestor = [super validRequestorForSendType:sendType
4225 returnType:returnType];
4230 - (void)viewWillStartLiveResize {
4231 [super viewWillStartLiveResize];
4232 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
4234 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
4237 - (void)viewDidEndLiveResize {
4238 [super viewDidEndLiveResize];
4239 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
4241 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
4244 - (void)updateCursor:(NSCursor*)cursor {
4245 if (currentCursor_ == cursor)
4248 currentCursor_.reset([cursor retain]);
4249 [[self window] invalidateCursorRectsForView:self];
4252 - (void)popupWindowWillClose:(NSNotification *)notification {
4253 renderWidgetHostView_->KillSelf();
4259 // Supporting application services
4261 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
4263 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
4264 types:(NSArray*)types {
4265 const std::string& str = renderWidgetHostView_->selected_text();
4266 if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
4268 base::scoped_nsobject<NSString> text(
4269 [[NSString alloc] initWithUTF8String:str.c_str()]);
4270 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
4271 [pboard declareTypes:toDeclare owner:nil];
4272 return [pboard setString:text forType:NSStringPboardType];
4275 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
4276 NSString *string = [pboard stringForType:NSStringPboardType];
4277 if (!string) return NO;
4279 // If the user is currently using an IME, confirm the IME input,
4280 // and then insert the text from the service, the same as TextEdit and Safari.
4281 [self confirmComposition];
4282 [self insertText:string];
4287 if (renderWidgetHostView_->use_core_animation_)
4289 return [super isOpaque];
4292 // "-webkit-app-region: drag | no-drag" is implemented on Mac by excluding
4293 // regions that are not draggable. (See ControlRegionView in
4294 // native_app_window_cocoa.mm). This requires the render host view to be
4295 // draggable by default.
4296 - (BOOL)mouseDownCanMoveWindow {
4302 @implementation SoftwareLayer
4304 - (id)initWithRenderWidgetHostViewMac:(content::RenderWidgetHostViewMac*)r {
4305 if (self = [super init]) {
4306 renderWidgetHostView_ = r;
4308 [self setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
4309 [self setAnchorPoint:CGPointMake(0, 0)];
4310 // Setting contents gravity is necessary to prevent the layer from being
4311 // scaled during dyanmic resizes (especially with devtools open).
4312 [self setContentsGravity:kCAGravityTopLeft];
4313 if (renderWidgetHostView_->software_frame_manager_->HasCurrentFrame() &&
4314 [self respondsToSelector:(@selector(setContentsScale:))]) {
4315 [self setContentsScale:renderWidgetHostView_->software_frame_manager_->
4316 GetCurrentFrameDeviceScaleFactor()];
4319 // Ensure that the transition between frames not be animated.
4320 [self setActions:@{ @"contents" : [NSNull null] }];
4325 - (void)drawInContext:(CGContextRef)context {
4326 TRACE_EVENT0("browser", "SoftwareLayer::drawInContext");
4328 CGRect clipRect = CGContextGetClipBoundingBox(context);
4329 if (renderWidgetHostView_) {
4330 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
4331 renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
4332 [renderWidgetHostView_->cocoa_view() drawBackingStore:backingStore
4336 CGContextSetFillColorWithColor(context,
4337 CGColorGetConstantColor(kCGColorWhite));
4338 CGContextFillRect(context, clipRect);
4342 - (void)disableRendering {
4343 // Disable the fade-out animation as the layer is removed.
4344 ScopedCAActionDisabler disabler;
4345 [self removeFromSuperlayer];
4346 renderWidgetHostView_ = nil;
4349 @end // implementation SoftwareLayer