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();
387 ///////////////////////////////////////////////////////////////////////////////
388 // RenderWidgetHostView, public:
391 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
392 RenderWidgetHost* widget) {
393 return new RenderWidgetHostViewMac(widget);
397 void RenderWidgetHostViewPort::GetDefaultScreenInfo(
398 blink::WebScreenInfo* results) {
399 *results = GetWebScreenInfo(NULL);
402 ///////////////////////////////////////////////////////////////////////////////
403 // RenderWidgetHostViewMac, public:
405 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
406 : render_widget_host_(RenderWidgetHostImpl::From(widget)),
407 last_frame_was_accelerated_(false),
408 text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
409 can_compose_inline_(true),
410 compositing_iosurface_layer_async_timer_(
411 FROM_HERE, base::TimeDelta::FromMilliseconds(250),
412 this, &RenderWidgetHostViewMac::TimerSinceGotAcceleratedFrameFired),
413 allow_overlapping_views_(false),
414 use_core_animation_(false),
415 pending_latency_info_delay_(0),
416 pending_latency_info_delay_weak_ptr_factory_(this),
417 backing_store_scale_factor_(1),
420 fullscreen_parent_host_view_(NULL),
421 underlay_view_has_drawn_(false),
422 overlay_view_weak_factory_(this),
423 software_frame_weak_ptr_factory_(this) {
424 software_frame_manager_.reset(new SoftwareFrameManager(
425 software_frame_weak_ptr_factory_.GetWeakPtr()));
426 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
427 // goes away. Since we autorelease it, our caller must put
428 // |GetNativeView()| into the view hierarchy right after calling us.
429 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
430 initWithRenderWidgetHostViewMac:this] autorelease];
432 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED) {
433 use_core_animation_ = true;
434 background_layer_.reset([[CALayer alloc] init]);
436 setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
437 [cocoa_view_ setLayer:background_layer_];
438 [cocoa_view_ setWantsLayer:YES];
441 render_widget_host_->SetView(this);
444 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
445 // This is being called from |cocoa_view_|'s destructor, so invalidate the
451 // Make sure that the layer doesn't reach into the now-invalid object.
452 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
453 DestroySoftwareLayer();
455 // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
456 // RenderWidgetHost does we need to tell it not to hold a stale pointer to
458 if (render_widget_host_)
459 render_widget_host_->SetView(NULL);
462 void RenderWidgetHostViewMac::SetDelegate(
463 NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
464 [cocoa_view_ setResponderDelegate:delegate];
467 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
468 if (allow_overlapping_views_ == overlapping)
470 allow_overlapping_views_ = overlapping;
471 [cocoa_view_ setNeedsDisplay:YES];
472 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
475 ///////////////////////////////////////////////////////////////////////////////
476 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
478 bool RenderWidgetHostViewMac::EnsureCompositedIOSurface() {
479 // If the context or the IOSurface's context has had an error, re-build
480 // everything from scratch.
481 if (compositing_iosurface_context_ &&
482 compositing_iosurface_context_->HasBeenPoisoned()) {
483 LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
484 << "context was poisoned";
487 if (compositing_iosurface_ &&
488 compositing_iosurface_->HasBeenPoisoned()) {
489 LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
490 << "surface was poisoned";
494 int current_window_number = use_core_animation_ ?
495 CompositingIOSurfaceContext::kOffscreenContextWindowNumber :
497 bool new_surface_needed = !compositing_iosurface_;
498 bool new_context_needed =
499 !compositing_iosurface_context_ ||
500 (compositing_iosurface_context_ &&
501 compositing_iosurface_context_->window_number() !=
502 current_window_number);
504 if (!new_surface_needed && !new_context_needed)
507 // Create the GL context and shaders.
508 if (new_context_needed) {
509 scoped_refptr<CompositingIOSurfaceContext> new_context =
510 CompositingIOSurfaceContext::Get(current_window_number);
511 // Un-bind the GL context from this view before binding the new GL
512 // context. Having two GL contexts bound to a view will result in
513 // crashes and corruption.
514 // http://crbug.com/230883
515 ClearBoundContextDrawable();
517 LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
520 compositing_iosurface_context_ = new_context;
523 // Create the IOSurface texture.
524 if (new_surface_needed) {
525 compositing_iosurface_.reset(CompositingIOSurfaceMac::Create());
526 if (!compositing_iosurface_) {
527 LOG(ERROR) << "Failed to create CompositingIOSurface";
535 void RenderWidgetHostViewMac::EnsureSoftwareLayer() {
536 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::EnsureSoftwareLayer");
537 if (software_layer_ || !use_core_animation_)
540 software_layer_.reset([[SoftwareLayer alloc]
541 initWithRenderWidgetHostViewMac:this]);
542 DCHECK(software_layer_);
544 // Disable the fade-in animation as the layer is added.
545 ScopedCAActionDisabler disabler;
546 [background_layer_ addSublayer:software_layer_];
549 void RenderWidgetHostViewMac::DestroySoftwareLayer() {
550 if (!software_layer_)
553 // Disable the fade-out animation as the layer is removed.
554 ScopedCAActionDisabler disabler;
555 [software_layer_ removeFromSuperlayer];
556 [software_layer_ disableRendering];
557 software_layer_.reset();
560 void RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer() {
561 TRACE_EVENT0("browser",
562 "RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer");
563 DCHECK(compositing_iosurface_context_);
564 if (compositing_iosurface_layer_ || !use_core_animation_)
567 compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
568 initWithRenderWidgetHostViewMac:this]);
569 DCHECK(compositing_iosurface_layer_);
571 // Disable the fade-in animation as the layer is added.
572 ScopedCAActionDisabler disabler;
573 [background_layer_ addSublayer:compositing_iosurface_layer_];
576 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceLayer() {
577 if (!compositing_iosurface_layer_)
580 // Disable the fade-out animation as the layer is removed.
581 ScopedCAActionDisabler disabler;
582 [compositing_iosurface_layer_ removeFromSuperlayer];
583 [compositing_iosurface_layer_ disableCompositing];
584 compositing_iosurface_layer_.reset();
587 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer(
588 DestroyContextBehavior destroy_context_behavior) {
589 // Any pending frames will not be displayed, so ack them now.
590 SendPendingSwapAck();
592 DestroyCompositedIOSurfaceLayer();
593 compositing_iosurface_.reset();
595 switch (destroy_context_behavior) {
596 case kLeaveContextBoundToView:
598 case kDestroyContext:
599 ClearBoundContextDrawable();
600 compositing_iosurface_context_ = NULL;
608 void RenderWidgetHostViewMac::ClearBoundContextDrawable() {
609 if (use_core_animation_)
612 if (compositing_iosurface_context_ &&
614 [[compositing_iosurface_context_->nsgl_context() view]
615 isEqual:cocoa_view_]) {
616 // Disable screen updates because removing the GL context from below can
618 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
619 [compositing_iosurface_context_->nsgl_context() clearDrawable];
623 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
625 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
626 IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
627 IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
628 IPC_MESSAGE_UNHANDLED(handled = false)
629 IPC_END_MESSAGE_MAP()
633 void RenderWidgetHostViewMac::InitAsChild(
634 gfx::NativeView parent_view) {
637 void RenderWidgetHostViewMac::InitAsPopup(
638 RenderWidgetHostView* parent_host_view,
639 const gfx::Rect& pos) {
640 bool activatable = popup_type_ == blink::WebPopupTypeNone;
641 [cocoa_view_ setCloseOnDeactivate:YES];
642 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
644 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
645 origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height());
647 popup_window_.reset([[RenderWidgetPopupWindow alloc]
648 initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
649 pos.width(), pos.height())
650 styleMask:NSBorderlessWindowMask
651 backing:NSBackingStoreBuffered
653 [popup_window_ setLevel:NSPopUpMenuWindowLevel];
654 [popup_window_ setReleasedWhenClosed:NO];
655 [popup_window_ makeKeyAndOrderFront:nil];
656 [[popup_window_ contentView] addSubview:cocoa_view_];
657 [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
658 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
659 [[NSNotificationCenter defaultCenter]
660 addObserver:cocoa_view_
661 selector:@selector(popupWindowWillClose:)
662 name:NSWindowWillCloseNotification
663 object:popup_window_];
666 // This function creates the fullscreen window and hides the dock and menubar if
667 // necessary. Note, this codepath is only used for pepper flash when
668 // pp::FlashFullScreen::SetFullscreen() is called. If
669 // pp::FullScreen::SetFullscreen() is called then the entire browser window
670 // will enter fullscreen instead.
671 void RenderWidgetHostViewMac::InitAsFullscreen(
672 RenderWidgetHostView* reference_host_view) {
673 fullscreen_parent_host_view_ =
674 static_cast<RenderWidgetHostViewMac*>(reference_host_view);
675 NSWindow* parent_window = nil;
676 if (reference_host_view)
677 parent_window = [reference_host_view->GetNativeView() window];
678 NSScreen* screen = [parent_window screen];
680 screen = [NSScreen mainScreen];
682 pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
683 initWithContentRect:[screen frame]
684 styleMask:NSBorderlessWindowMask
685 backing:NSBackingStoreBuffered
687 [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
688 [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
689 [cocoa_view_ setCanBeKeyView:YES];
690 [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
691 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
692 // If the pepper fullscreen window isn't opaque then there are performance
693 // issues when it's on the discrete GPU and the Chrome window is being drawn
694 // to. http://crbug.com/171911
695 [pepper_fullscreen_window_ setOpaque:YES];
697 // Note that this forms a reference cycle between the fullscreen window and
698 // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
699 // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
700 // This cycle is normally broken when -keyEvent: receives an <esc> key, which
701 // explicitly calls Shutdown on the render_widget_host_, which calls
702 // Destroy() on RWHVMac, which drops the reference to
703 // pepper_fullscreen_window_.
704 [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
706 // Note that this keeps another reference to pepper_fullscreen_window_.
707 fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
708 initWithWindow:pepper_fullscreen_window_.get()
709 desiredScreen:screen]);
710 [fullscreen_window_manager_ enterFullscreenMode];
711 [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
714 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
715 // See comment in InitAsFullscreen(): There is a reference cycle between
716 // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
717 // Tests that test pepper fullscreen mode without sending an <esc> event
718 // need to call this method to break the reference cycle.
719 [fullscreen_window_manager_ exitFullscreenMode];
720 fullscreen_window_manager_.reset();
721 [pepper_fullscreen_window_ close];
722 pepper_fullscreen_window_.reset();
725 int RenderWidgetHostViewMac::window_number() const {
726 NSWindow* window = [cocoa_view_ window];
729 return [window windowNumber];
732 float RenderWidgetHostViewMac::ViewScaleFactor() const {
733 return ScaleFactorForView(cocoa_view_);
736 void RenderWidgetHostViewMac::UpdateDisplayLink() {
737 static bool is_vsync_disabled =
738 CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync);
739 if (is_vsync_disabled)
742 NSScreen* screen = [[cocoa_view_ window] screen];
743 NSDictionary* screen_description = [screen deviceDescription];
744 NSNumber* screen_number = [screen_description objectForKey:@"NSScreenNumber"];
745 CGDirectDisplayID display_id = [screen_number unsignedIntValue];
747 display_link_ = DisplayLinkMac::GetForDisplay(display_id);
748 if (!display_link_) {
749 // Note that on some headless systems, the display link will fail to be
750 // created, so this should not be a fatal error.
751 LOG(ERROR) << "Failed to create display link.";
755 void RenderWidgetHostViewMac::SendVSyncParametersToRenderer() {
756 if (!render_widget_host_ || !display_link_)
759 base::TimeTicks timebase;
760 base::TimeDelta interval;
761 if (!display_link_->GetVSyncParameters(&timebase, &interval))
764 render_widget_host_->UpdateVSyncParameters(timebase, interval);
767 void RenderWidgetHostViewMac::UpdateBackingStoreScaleFactor() {
768 if (!render_widget_host_)
771 float new_scale_factor = ScaleFactorForView(cocoa_view_);
772 if (new_scale_factor == backing_store_scale_factor_)
774 backing_store_scale_factor_ = new_scale_factor;
776 BackingStoreMac* backing_store = static_cast<BackingStoreMac*>(
777 render_widget_host_->GetBackingStore(false));
779 backing_store->ScaleFactorChanged(backing_store_scale_factor_);
781 render_widget_host_->NotifyScreenInfoChanged();
784 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
785 return render_widget_host_;
788 void RenderWidgetHostViewMac::WasShown() {
789 if (!render_widget_host_->is_hidden())
792 if (web_contents_switch_paint_time_.is_null())
793 web_contents_switch_paint_time_ = base::TimeTicks::Now();
794 render_widget_host_->WasShown();
795 software_frame_manager_->SetVisibility(true);
797 // Call setNeedsDisplay before pausing for new frames to come in -- if any
798 // do, and are drawn, then the needsDisplay bit will be cleared.
799 [software_layer_ setNeedsDisplay];
800 [compositing_iosurface_layer_ setNeedsDisplay];
801 PauseForPendingResizeOrRepaintsAndDraw();
803 // We're messing with the window, so do this to ensure no flashes.
804 if (!use_core_animation_)
805 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
808 void RenderWidgetHostViewMac::WasHidden() {
809 if (render_widget_host_->is_hidden())
812 // Any pending frames will not be displayed until this is shown again. Ack
814 SendPendingSwapAck();
816 // If we have a renderer, then inform it that we are being hidden so it can
817 // reduce its resource utilization.
818 render_widget_host_->WasHidden();
819 software_frame_manager_->SetVisibility(false);
821 // There can be a transparent flash as this view is removed and the next is
822 // added, because of OSX windowing races between displaying the contents of
823 // the NSView and its corresponding OpenGL context.
824 // disableScreenUpdatesUntilFlush prevents the transparent flash by avoiding
825 // screen updates until the next tab draws.
826 if (!use_core_animation_)
827 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
829 web_contents_switch_paint_time_ = base::TimeTicks();
832 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
833 gfx::Rect rect = GetViewBounds();
838 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
839 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
840 // TODO(thakis): fix, http://crbug.com/73362
841 if (render_widget_host_->is_hidden())
844 // During the initial creation of the RenderWidgetHostView in
845 // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
846 // an empty size. In the Windows code flow, it is not ignored because
847 // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
848 // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
849 // flags to keep things sized properly. On the other hand, if the size is not
850 // empty then this is a valid request for a pop-up.
851 if (rect.size().IsEmpty())
854 // Ignore the position of |rect| for non-popup rwhvs. This is because
855 // background tabs do not have a window, but the window is required for the
856 // coordinate conversions. Popups are always for a visible tab.
858 // Note: If |cocoa_view_| has been removed from the view hierarchy, it's still
859 // valid for resizing to be requested (e.g., during tab capture, to size the
860 // view to screen-capture resolution). In this case, simply treat the view as
861 // relative to the screen.
862 BOOL isRelativeToScreen = IsPopup() ||
863 ![[cocoa_view_ superview] isKindOfClass:[BaseView class]];
864 if (isRelativeToScreen) {
865 // The position of |rect| is screen coordinate system and we have to
866 // consider Cocoa coordinate system is upside-down and also multi-screen.
867 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
868 NSSize size = NSMakeSize(rect.width(), rect.height());
869 size = [cocoa_view_ convertSize:size toView:nil];
870 origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height);
871 NSRect frame = NSMakeRect(origin_global.x, origin_global.y,
872 size.width, size.height);
874 [popup_window_ setFrame:frame display:YES];
876 [cocoa_view_ setFrame:frame];
878 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
879 gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
880 rect2.set_width(rect.width());
881 rect2.set_height(rect.height());
882 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
886 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
890 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
891 return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
894 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
896 return static_cast<gfx::NativeViewAccessible>(NULL);
899 void RenderWidgetHostViewMac::MovePluginWindows(
900 const gfx::Vector2d& scroll_offset,
901 const std::vector<WebPluginGeometry>& moves) {
902 // Must be overridden, but unused on this platform. Core Animation
903 // plugins are drawn by the GPU process (through the compositor),
904 // and Core Graphics plugins are drawn by the renderer process.
905 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
908 void RenderWidgetHostViewMac::Focus() {
909 [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
912 void RenderWidgetHostViewMac::Blur() {
914 [[cocoa_view_ window] makeFirstResponder:nil];
917 bool RenderWidgetHostViewMac::HasFocus() const {
918 return [[cocoa_view_ window] firstResponder] == cocoa_view_;
921 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
922 return software_frame_manager_->HasCurrentFrame() ||
923 (compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) ||
924 !!render_widget_host_->GetBackingStore(false);
927 void RenderWidgetHostViewMac::Show() {
928 [cocoa_view_ setHidden:NO];
933 void RenderWidgetHostViewMac::Hide() {
934 // We're messing with the window, so do this to ensure no flashes.
935 if (!use_core_animation_)
936 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
938 [cocoa_view_ setHidden:YES];
943 bool RenderWidgetHostViewMac::IsShowing() {
944 return ![cocoa_view_ isHidden];
947 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
948 NSRect bounds = [cocoa_view_ bounds];
949 // TODO(shess): In case of !window, the view has been removed from
950 // the view hierarchy because the tab isn't main. Could retrieve
951 // the information from the main tab for our window.
952 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
953 if (!enclosing_window)
954 return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
956 bounds = [cocoa_view_ convertRect:bounds toView:nil];
957 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
958 return FlipNSRectToRectScreen(bounds);
961 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
962 WebCursor web_cursor = cursor;
963 [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
966 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
967 is_loading_ = is_loading;
968 // If we ever decide to show the waiting cursor while the page is loading
969 // like Chrome does on Windows, call |UpdateCursor()| here.
972 void RenderWidgetHostViewMac::TextInputTypeChanged(
973 ui::TextInputType type,
974 ui::TextInputMode input_mode,
975 bool can_compose_inline) {
976 if (text_input_type_ != type
977 || can_compose_inline_ != can_compose_inline) {
978 text_input_type_ = type;
979 can_compose_inline_ = can_compose_inline;
981 SetTextInputActive(true);
983 // Let AppKit cache the new input context to make IMEs happy.
984 // See http://crbug.com/73039.
985 [NSApp updateWindows];
988 UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
994 void RenderWidgetHostViewMac::ImeCancelComposition() {
995 [cocoa_view_ cancelComposition];
998 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
999 const gfx::Range& range,
1000 const std::vector<gfx::Rect>& character_bounds) {
1001 // The RangeChanged message is only sent with valid values. The current
1002 // caret position (start == end) will be sent if there is no IME range.
1003 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1004 composition_range_ = range;
1005 composition_bounds_ = character_bounds;
1008 void RenderWidgetHostViewMac::DidUpdateBackingStore(
1009 const gfx::Rect& scroll_rect,
1010 const gfx::Vector2d& scroll_delta,
1011 const std::vector<gfx::Rect>& copy_rects,
1012 const std::vector<ui::LatencyInfo>& latency_info) {
1013 // This can be called while already inside of a display function. Process the
1014 // new frame in a callback to ensure a clean stack.
1015 // TODO(ccameron): This should never be called. Remove the remaining callers
1016 // and remove all places where the backing store is drawn.
1017 AddPendingLatencyInfo(latency_info);
1018 base::MessageLoop::current()->PostTask(
1020 base::Bind(&RenderWidgetHostViewMac::GotSoftwareFrame,
1021 weak_factory_.GetWeakPtr()));
1024 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
1029 void RenderWidgetHostViewMac::Destroy() {
1030 [[NSNotificationCenter defaultCenter]
1031 removeObserver:cocoa_view_
1032 name:NSWindowWillCloseNotification
1033 object:popup_window_];
1035 // We've been told to destroy.
1036 [cocoa_view_ retain];
1037 [cocoa_view_ removeFromSuperview];
1038 [cocoa_view_ autorelease];
1040 [popup_window_ close];
1041 popup_window_.autorelease();
1043 [fullscreen_window_manager_ exitFullscreenMode];
1044 fullscreen_window_manager_.reset();
1045 [pepper_fullscreen_window_ close];
1047 // This can be called as part of processing the window's responder
1048 // chain, for instance |-performKeyEquivalent:|. In that case the
1049 // object needs to survive until the stack unwinds.
1050 pepper_fullscreen_window_.autorelease();
1052 // We get this call just before |render_widget_host_| deletes
1053 // itself. But we are owned by |cocoa_view_|, which may be retained
1054 // by some other code. Examples are WebContentsViewMac's
1055 // |latent_focus_view_| and TabWindowController's
1056 // |cachedContentView_|.
1057 render_widget_host_ = NULL;
1060 // Called from the renderer to tell us what the tooltip text should be. It
1061 // calls us frequently so we need to cache the value to prevent doing a lot
1063 void RenderWidgetHostViewMac::SetTooltipText(
1064 const base::string16& tooltip_text) {
1065 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1066 tooltip_text_ = tooltip_text;
1068 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1069 // Windows; we're just trying to be polite. Don't persist the trimmed
1070 // string, as then the comparison above will always fail and we'll try to
1071 // set it again every single time the mouse moves.
1072 base::string16 display_text = tooltip_text_;
1073 if (tooltip_text_.length() > kMaxTooltipLength)
1074 display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1076 NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1077 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1081 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1082 return [NSApp respondsToSelector:@selector(speakString:)] &&
1083 [NSApp respondsToSelector:@selector(stopSpeaking:)];
1086 void RenderWidgetHostViewMac::SpeakSelection() {
1087 if ([NSApp respondsToSelector:@selector(speakString:)])
1088 [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
1091 bool RenderWidgetHostViewMac::IsSpeaking() const {
1092 return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1096 void RenderWidgetHostViewMac::StopSpeaking() {
1097 if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1098 [NSApp stopSpeaking:cocoa_view_];
1102 // RenderWidgetHostViewCocoa uses the stored selection text,
1103 // which implements NSServicesRequests protocol.
1105 void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text,
1107 const gfx::Range& range) {
1108 if (range.is_empty() || text.empty()) {
1109 selected_text_.clear();
1111 size_t pos = range.GetMin() - offset;
1112 size_t n = range.length();
1114 DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1115 if (pos >= text.length()) {
1116 DCHECK(false) << "The text can not cover range.";
1119 selected_text_ = base::UTF16ToUTF8(text.substr(pos, n));
1122 [cocoa_view_ setSelectedRange:range.ToNSRange()];
1123 // Updates markedRange when there is no marked text so that retrieving
1124 // markedRange immediately after calling setMarkdText: returns the current
1126 if (![cocoa_view_ hasMarkedText]) {
1127 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1130 RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1133 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1134 const ViewHostMsg_SelectionBounds_Params& params) {
1135 if (params.anchor_rect == params.focus_rect)
1136 caret_rect_ = params.anchor_rect;
1139 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1142 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1143 RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1145 // Create a fake mouse event to inform the render widget that the mouse
1147 NSWindow* window = [cocoa_view_ window];
1148 // TODO(asvitkine): If the location outside of the event stream doesn't
1149 // correspond to the current event (due to delayed event processing), then
1150 // this may result in a cursor flicker if there are later mouse move events
1151 // in the pipeline. Find a way to use the mouse location from the event that
1152 // dismissed the context menu.
1153 NSPoint location = [window mouseLocationOutsideOfEventStream];
1154 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1158 windowNumber:window_number()
1163 WebMouseEvent web_event =
1164 WebInputEventFactory::mouseEvent(event, cocoa_view_);
1166 web_event.type = WebInputEvent::MouseLeave;
1167 ForwardMouseEvent(web_event);
1170 bool RenderWidgetHostViewMac::IsPopup() const {
1171 return popup_type_ != blink::WebPopupTypeNone;
1174 BackingStore* RenderWidgetHostViewMac::AllocBackingStore(
1175 const gfx::Size& size) {
1176 float scale = ScaleFactorForView(cocoa_view_);
1177 return new BackingStoreMac(render_widget_host_, size, scale);
1180 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1181 const gfx::Rect& src_subrect,
1182 const gfx::Size& dst_size,
1183 const base::Callback<void(bool, const SkBitmap&)>& callback,
1184 const SkBitmap::Config config) {
1185 if (config != SkBitmap::kARGB_8888_Config) {
1187 callback.Run(false, SkBitmap());
1189 base::ScopedClosureRunner scoped_callback_runner(
1190 base::Bind(callback, false, SkBitmap()));
1191 float scale = ScaleFactorForView(cocoa_view_);
1192 gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1193 gfx::ScaleSize(dst_size, scale));
1194 if (compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) {
1195 ignore_result(scoped_callback_runner.Release());
1196 compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1199 } else if (software_frame_manager_->HasCurrentFrame()) {
1200 gfx::Rect src_pixel_rect = gfx::ToEnclosingRect(gfx::ScaleRect(
1202 software_frame_manager_->GetCurrentFrameDeviceScaleFactor()));
1203 SkBitmap source_bitmap;
1204 source_bitmap.setConfig(
1205 SkBitmap::kARGB_8888_Config,
1206 software_frame_manager_->GetCurrentFrameSizeInPixels().width(),
1207 software_frame_manager_->GetCurrentFrameSizeInPixels().height(),
1209 kOpaque_SkAlphaType);
1210 source_bitmap.setPixels(software_frame_manager_->GetCurrentFramePixels());
1212 SkBitmap target_bitmap;
1213 target_bitmap.setConfig(
1214 SkBitmap::kARGB_8888_Config,
1215 dst_pixel_size.width(),
1216 dst_pixel_size.height(),
1218 kOpaque_SkAlphaType);
1219 if (!target_bitmap.allocPixels())
1222 SkCanvas target_canvas(target_bitmap);
1223 SkRect src_pixel_skrect = SkRect::MakeXYWH(
1224 src_pixel_rect.x(), src_pixel_rect.y(),
1225 src_pixel_rect.width(), src_pixel_rect.height());
1226 target_canvas.drawBitmapRectToRect(
1229 SkRect::MakeXYWH(0, 0, dst_pixel_size.width(), dst_pixel_size.height()),
1231 SkCanvas::kNone_DrawBitmapRectFlag);
1233 ignore_result(scoped_callback_runner.Release());
1234 callback.Run(true, target_bitmap);
1238 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1239 const gfx::Rect& src_subrect,
1240 const scoped_refptr<media::VideoFrame>& target,
1241 const base::Callback<void(bool)>& callback) {
1242 base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1243 if (!render_widget_host_->is_accelerated_compositing_active() ||
1244 !compositing_iosurface_ ||
1245 !compositing_iosurface_->HasIOSurface())
1248 if (!target.get()) {
1253 if (target->format() != media::VideoFrame::YV12 &&
1254 target->format() != media::VideoFrame::I420) {
1259 if (src_subrect.IsEmpty())
1262 ignore_result(scoped_callback_runner.Release());
1263 compositing_iosurface_->CopyToVideoFrame(
1264 GetScaledOpenGLPixelRect(src_subrect),
1269 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1270 return (!render_widget_host_->GetBackingStore(false) &&
1271 !software_frame_manager_->HasCurrentFrame() &&
1272 render_widget_host_->is_accelerated_compositing_active() &&
1273 compositing_iosurface_ &&
1274 compositing_iosurface_->HasIOSurface());
1277 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1278 return !software_frame_manager_->HasCurrentFrame();
1281 void RenderWidgetHostViewMac::BeginFrameSubscription(
1282 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1283 frame_subscriber_ = subscriber.Pass();
1286 void RenderWidgetHostViewMac::EndFrameSubscription() {
1287 frame_subscriber_.reset();
1290 // Sets whether or not to accept first responder status.
1291 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1292 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1295 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1296 if (render_widget_host_)
1297 render_widget_host_->ForwardMouseEvent(event);
1299 if (event.type == WebInputEvent::MouseLeave) {
1300 [cocoa_view_ setToolTipAtMousePoint:nil];
1301 tooltip_text_.clear();
1305 void RenderWidgetHostViewMac::KillSelf() {
1306 if (!weak_factory_.HasWeakPtrs()) {
1307 [cocoa_view_ setHidden:YES];
1308 base::MessageLoop::current()->PostTask(FROM_HERE,
1309 base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1310 weak_factory_.GetWeakPtr()));
1314 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1315 const NativeWebKeyboardEvent& event) {
1316 // Check WebInputEvent type since multiple types of events can be sent into
1317 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1318 // necessary to avoid double processing.
1319 // Also check the native type, since NSFlagsChanged is considered a key event
1320 // for WebKit purposes, but isn't considered a key event by the OS.
1321 if (event.type == WebInputEvent::RawKeyDown &&
1322 [event.os_event type] == NSKeyDown)
1323 return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1327 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1328 const base::string16& text, int plugin_id) {
1329 if (render_widget_host_) {
1330 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1331 render_widget_host_->GetRoutingID(), text, plugin_id));
1335 void RenderWidgetHostViewMac::CompositorSwapBuffers(
1336 uint64 surface_handle,
1337 const gfx::Size& size,
1338 float surface_scale_factor,
1339 const std::vector<ui::LatencyInfo>& latency_info) {
1340 // Ensure that the frame be acked unless it is explicitly passed to a
1341 // display function.
1342 base::ScopedClosureRunner scoped_ack(
1343 base::Bind(&RenderWidgetHostViewMac::SendPendingSwapAck,
1344 weak_factory_.GetWeakPtr()));
1346 if (render_widget_host_->is_hidden())
1349 // Ensure that if this function exits before the frame is set up (but not
1350 // necessarily drawn) then it is treated as an error.
1351 base::ScopedClosureRunner scoped_error(
1352 base::Bind(&RenderWidgetHostViewMac::GotAcceleratedCompositingError,
1353 weak_factory_.GetWeakPtr()));
1355 AddPendingLatencyInfo(latency_info);
1357 // Ensure compositing_iosurface_ and compositing_iosurface_context_ be
1359 if (!EnsureCompositedIOSurface()) {
1360 LOG(ERROR) << "Failed EnsureCompositingIOSurface";
1364 // Make the context current and update the IOSurface with the handle
1365 // passed in by the swap command.
1366 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1367 compositing_iosurface_context_->cgl_context());
1368 if (!compositing_iosurface_->SetIOSurfaceWithContextCurrent(
1369 compositing_iosurface_context_, surface_handle, size,
1370 surface_scale_factor)) {
1371 LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1375 // Grab video frames now that the IOSurface has been set up. Note that this
1376 // will be done in an offscreen context, so it is necessary to re-set the
1377 // current context afterward.
1378 bool frame_was_captured = false;
1379 if (frame_subscriber_) {
1380 const base::TimeTicks present_time = base::TimeTicks::Now();
1381 scoped_refptr<media::VideoFrame> frame;
1382 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1383 if (frame_subscriber_->ShouldCaptureFrame(present_time,
1384 &frame, &callback)) {
1385 // Flush the context that updated the IOSurface, to ensure that the
1386 // context that does the copy picks up the correct version.
1388 compositing_iosurface_->CopyToVideoFrame(
1389 gfx::Rect(size), frame,
1390 base::Bind(callback, present_time));
1391 DCHECK_EQ(CGLGetCurrentContext(),
1392 compositing_iosurface_context_->cgl_context());
1393 frame_was_captured = true;
1397 // At this point the surface, its context, and its layer have been set up, so
1398 // don't generate an error (one may be generated when drawing).
1399 ignore_result(scoped_error.Release());
1401 GotAcceleratedFrame();
1403 gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1404 if (window_size.IsEmpty()) {
1405 // setNeedsDisplay will never display and we'll never ack if the window is
1406 // empty, so ack now and don't bother calling setNeedsDisplay below.
1409 if (window_number() <= 0) {
1410 // It's normal for a backgrounded tab that is being captured to have no
1411 // window but not be hidden. Immediately ack the frame, and don't try to
1413 if (frame_was_captured)
1416 // If this frame was not captured, there is likely some sort of bug. Ack
1417 // the frame and hope for the best. Because the IOSurface and layer are
1418 // populated, it will likely be displayed when the view is added to a
1419 // window's hierarchy.
1421 // TODO(shess) If the view does not have a window, or the window
1422 // does not have backing, the IOSurface will log "invalid drawable"
1423 // in -setView:. It is not clear how this code is reached with such
1424 // a case, so record some info into breakpad (some subset of
1425 // browsers are likely to crash later for unrelated reasons).
1426 // http://crbug.com/148882
1427 const char* const kCrashKey = "rwhvm_window";
1428 NSWindow* window = [cocoa_view_ window];
1430 base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1433 base::StringPrintf("window %s delegate %s controller %s",
1434 object_getClassName(window),
1435 object_getClassName([window delegate]),
1436 object_getClassName([window windowController]));
1437 base::debug::SetCrashKeyValue(kCrashKey, value);
1442 // If we reach here, then the frame will be displayed by a future draw
1443 // call, so don't make the callback.
1444 ignore_result(scoped_ack.Release());
1445 if (use_core_animation_) {
1446 DCHECK(compositing_iosurface_layer_);
1447 compositing_iosurface_layer_async_timer_.Reset();
1448 [compositing_iosurface_layer_ gotNewFrame];
1450 DrawIOSurfaceWithoutCoreAnimation();
1453 // The IOSurface's size may have changed, so re-layout the layers to take
1454 // this into account. This may force an immediate draw.
1458 void RenderWidgetHostViewMac::DrawIOSurfaceWithoutCoreAnimation() {
1459 CHECK(!use_core_animation_);
1460 CHECK(compositing_iosurface_);
1462 // If there is a pending frame, it should be acked by the end of this
1463 // function. Note that the ack should happen only after all drawing is
1464 // complete, so that the ack happens after any blocking due to vsync.
1465 base::ScopedClosureRunner scoped_ack(
1466 base::Bind(&RenderWidgetHostViewMac::SendPendingSwapAck,
1467 weak_factory_.GetWeakPtr()));
1469 GLint old_gl_surface_order = 0;
1470 GLint new_gl_surface_order = allow_overlapping_views_ ? -1 : 1;
1471 [compositing_iosurface_context_->nsgl_context()
1472 getValues:&old_gl_surface_order
1473 forParameter:NSOpenGLCPSurfaceOrder];
1474 if (old_gl_surface_order != new_gl_surface_order) {
1475 [compositing_iosurface_context_->nsgl_context()
1476 setValues:&new_gl_surface_order
1477 forParameter:NSOpenGLCPSurfaceOrder];
1480 // Instead of drawing, request that underlay view redraws.
1481 if (underlay_view_ &&
1482 underlay_view_->compositing_iosurface_ &&
1483 underlay_view_has_drawn_) {
1484 [underlay_view_->cocoa_view() setNeedsDisplayInRect:NSMakeRect(0, 0, 1, 1)];
1488 bool has_overlay = overlay_view_ && overlay_view_->compositing_iosurface_;
1490 // Un-bind the overlay view's OpenGL context, since its content will be
1491 // drawn by this context. Not doing this can result in corruption.
1492 // http://crbug.com/330701
1493 overlay_view_->ClearBoundContextDrawable();
1495 [compositing_iosurface_context_->nsgl_context() setView:cocoa_view_];
1497 gfx::Rect view_rect(NSRectToCGRect([cocoa_view_ frame]));
1498 if (!compositing_iosurface_->DrawIOSurface(
1499 compositing_iosurface_context_, view_rect,
1500 ViewScaleFactor(), !has_overlay)) {
1501 GotAcceleratedCompositingError();
1506 overlay_view_->underlay_view_has_drawn_ = true;
1507 gfx::Rect overlay_view_rect(
1508 NSRectToCGRect([overlay_view_->cocoa_view() frame]));
1509 overlay_view_rect.set_x(overlay_view_offset_.x());
1510 overlay_view_rect.set_y(view_rect.height() -
1511 overlay_view_rect.height() -
1512 overlay_view_offset_.y());
1513 if (!overlay_view_->compositing_iosurface_->DrawIOSurface(
1514 compositing_iosurface_context_, overlay_view_rect,
1515 overlay_view_->ViewScaleFactor(), true)) {
1516 GotAcceleratedCompositingError();
1521 SendPendingLatencyInfoToHost();
1524 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1525 LOG(ERROR) << "Encountered accelerated compositing error";
1526 base::MessageLoop::current()->PostTask(
1528 base::Bind(&RenderWidgetHostViewMac::DestroyCompositingStateOnError,
1529 weak_factory_.GetWeakPtr()));
1532 void RenderWidgetHostViewMac::DestroyCompositingStateOnError() {
1533 // This should be called with a clean stack. Make sure that no context is
1535 DCHECK(!CGLGetCurrentContext());
1537 // The existing GL contexts may be in a bad state, so don't re-use any of the
1538 // existing ones anymore, rather, allocate new ones.
1539 if (compositing_iosurface_context_)
1540 compositing_iosurface_context_->PoisonContextAndSharegroup();
1542 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1544 // Request that a new frame be generated and dirty the view.
1545 if (render_widget_host_)
1546 render_widget_host_->ScheduleComposite();
1547 [cocoa_view_ setNeedsDisplay:YES];
1549 // Mark the last frame as not accelerated (so that the window is prepared for
1550 // an underlay next time an accelerated frame comes in).
1551 last_frame_was_accelerated_ = false;
1553 // TODO(ccameron): It may be a good idea to request that the renderer recreate
1554 // its GL context as well, and fall back to software if this happens
1558 void RenderWidgetHostViewMac::SetOverlayView(
1559 RenderWidgetHostViewMac* overlay, const gfx::Point& offset) {
1560 if (use_core_animation_)
1564 overlay_view_->underlay_view_.reset();
1566 overlay_view_ = overlay->overlay_view_weak_factory_.GetWeakPtr();
1567 overlay_view_offset_ = offset;
1568 overlay_view_->underlay_view_ = overlay_view_weak_factory_.GetWeakPtr();
1569 overlay_view_->underlay_view_has_drawn_ = false;
1571 [cocoa_view_ setNeedsDisplay:YES];
1572 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1575 void RenderWidgetHostViewMac::RemoveOverlayView() {
1576 if (use_core_animation_)
1579 if (overlay_view_) {
1580 overlay_view_->underlay_view_.reset();
1581 overlay_view_.reset();
1583 [cocoa_view_ setNeedsDisplay:YES];
1584 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1587 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1588 const std::vector<gfx::Rect>& bounds,
1589 const gfx::Range& range,
1590 size_t* line_break_point) {
1591 DCHECK(line_break_point);
1592 if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1595 // We can't check line breaking completely from only rectangle array. Thus we
1596 // assume the line breaking as the next character's y offset is larger than
1597 // a threshold. Currently the threshold is determined as minimum y offset plus
1598 // 75% of maximum height.
1599 // TODO(nona): Check the threshold is reliable or not.
1600 // TODO(nona): Bidi support.
1601 const size_t loop_end_idx = std::min(bounds.size(), range.end());
1603 int min_y_offset = kint32max;
1604 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1605 max_height = std::max(max_height, bounds[idx].height());
1606 min_y_offset = std::min(min_y_offset, bounds[idx].y());
1608 int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1609 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1610 if (bounds[idx].y() > line_break_threshold) {
1611 *line_break_point = idx;
1618 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1619 const gfx::Range& range,
1620 gfx::Range* actual_range) {
1621 DCHECK(actual_range);
1622 DCHECK(!composition_bounds_.empty());
1623 DCHECK(range.start() <= composition_bounds_.size());
1624 DCHECK(range.end() <= composition_bounds_.size());
1626 if (range.is_empty()) {
1627 *actual_range = range;
1628 if (range.start() == composition_bounds_.size()) {
1629 return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1630 composition_bounds_[range.start() - 1].y(),
1632 composition_bounds_[range.start() - 1].height());
1634 return gfx::Rect(composition_bounds_[range.start()].x(),
1635 composition_bounds_[range.start()].y(),
1637 composition_bounds_[range.start()].height());
1642 if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1643 end_idx = range.end();
1645 *actual_range = gfx::Range(range.start(), end_idx);
1646 gfx::Rect rect = composition_bounds_[range.start()];
1647 for (size_t i = range.start() + 1; i < end_idx; ++i) {
1648 rect.Union(composition_bounds_[i]);
1653 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1654 const gfx::Range& request_range) {
1655 if (composition_range_.is_empty())
1656 return gfx::Range::InvalidRange();
1658 if (request_range.is_reversed())
1659 return gfx::Range::InvalidRange();
1661 if (request_range.start() < composition_range_.start() ||
1662 request_range.start() > composition_range_.end() ||
1663 request_range.end() > composition_range_.end()) {
1664 return gfx::Range::InvalidRange();
1668 request_range.start() - composition_range_.start(),
1669 request_range.end() - composition_range_.start());
1672 RenderFrameHost* RenderWidgetHostViewMac::GetFocusedFrame() {
1673 if (!render_widget_host_->IsRenderView())
1676 RenderViewHost* rvh = RenderViewHost::From(render_widget_host_);
1677 RenderFrameHostImpl* rfh =
1678 static_cast<RenderFrameHostImpl*>(rvh->GetMainFrame());
1679 FrameTreeNode* focused_frame =
1680 rfh->frame_tree_node()->frame_tree()->GetFocusedFrame();
1684 return focused_frame->current_frame_host();
1687 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1690 NSRange* actual_range) {
1692 // This exists to make IMEs more responsive, see http://crbug.com/115920
1693 TRACE_EVENT0("browser",
1694 "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1696 // If requested range is same as caret location, we can just return it.
1697 if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1699 *actual_range = range;
1700 *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1704 const gfx::Range request_range_in_composition =
1705 ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1706 if (request_range_in_composition == gfx::Range::InvalidRange())
1709 // If firstRectForCharacterRange in WebFrame is failed in renderer,
1710 // ImeCompositionRangeChanged will be sent with empty vector.
1711 if (composition_bounds_.empty())
1713 DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1715 gfx::Range ui_actual_range;
1716 *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1717 request_range_in_composition,
1718 &ui_actual_range).ToCGRect());
1720 *actual_range = gfx::Range(
1721 composition_range_.start() + ui_actual_range.start(),
1722 composition_range_.start() + ui_actual_range.end()).ToNSRange();
1727 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1728 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1730 TRACE_EVENT0("browser",
1731 "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1732 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1734 AddPendingSwapAck(params.route_id,
1736 compositing_iosurface_ ?
1737 compositing_iosurface_->GetRendererID() : 0);
1738 CompositorSwapBuffers(params.surface_handle,
1740 params.scale_factor,
1741 params.latency_info);
1744 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1745 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1747 TRACE_EVENT0("browser",
1748 "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1749 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1751 AddPendingSwapAck(params.route_id,
1753 compositing_iosurface_ ?
1754 compositing_iosurface_->GetRendererID() : 0);
1755 CompositorSwapBuffers(params.surface_handle,
1756 params.surface_size,
1757 params.surface_scale_factor,
1758 params.latency_info);
1761 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1762 if (compositing_iosurface_)
1763 compositing_iosurface_->UnrefIOSurface();
1766 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1767 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1770 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1771 const gfx::Size& desired_size) {
1772 if (last_frame_was_accelerated_) {
1773 return compositing_iosurface_ &&
1774 compositing_iosurface_->HasIOSurface() &&
1775 (desired_size.IsEmpty() ||
1776 compositing_iosurface_->dip_io_surface_size() == desired_size);
1778 return (software_frame_manager_->HasCurrentFrame() &&
1779 (desired_size.IsEmpty() ||
1780 software_frame_manager_->GetCurrentFrameSizeInDIP() ==
1786 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1787 uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1788 // Only software compositor frames are accepted.
1789 if (!frame->software_frame_data) {
1790 DLOG(ERROR) << "Received unexpected frame type.";
1792 base::UserMetricsAction("BadMessageTerminate_UnexpectedFrameType"));
1793 render_widget_host_->GetProcess()->ReceivedBadMessage();
1797 if (!software_frame_manager_->SwapToNewFrame(
1799 frame->software_frame_data.get(),
1800 frame->metadata.device_scale_factor,
1801 render_widget_host_->GetProcess()->GetHandle())) {
1802 render_widget_host_->GetProcess()->ReceivedBadMessage();
1806 // Add latency info to report when the frame finishes drawing.
1807 AddPendingLatencyInfo(frame->metadata.latency_info);
1810 cc::CompositorFrameAck ack;
1811 RenderWidgetHostImpl::SendSwapCompositorFrameAck(
1812 render_widget_host_->GetRoutingID(),
1813 software_frame_manager_->GetCurrentFrameOutputSurfaceId(),
1814 render_widget_host_->GetProcess()->GetID(),
1816 software_frame_manager_->SwapToNewFrameComplete(
1817 !render_widget_host_->is_hidden());
1819 // Notify observers, tab capture observers in particular, that a new software
1820 // frame has come in.
1821 NotificationService::current()->Notify(
1822 NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
1823 Source<RenderWidgetHost>(render_widget_host_),
1824 NotificationService::NoDetails());
1827 void RenderWidgetHostViewMac::OnAcceleratedCompositingStateChange() {
1830 void RenderWidgetHostViewMac::AcceleratedSurfaceInitialized(int host_id,
1834 void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) {
1835 *results = GetWebScreenInfo(GetNativeView());
1838 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1839 // TODO(shess): In case of !window, the view has been removed from
1840 // the view hierarchy because the tab isn't main. Could retrieve
1841 // the information from the main tab for our window.
1842 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1843 if (!enclosing_window)
1846 NSRect bounds = [enclosing_window frame];
1847 return FlipNSRectToRectScreen(bounds);
1850 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1851 // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1852 // completely on Mac OS.
1853 return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
1856 void RenderWidgetHostViewMac::SetHasHorizontalScrollbar(
1857 bool has_horizontal_scrollbar) {
1858 [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
1861 void RenderWidgetHostViewMac::SetScrollOffsetPinning(
1862 bool is_pinned_to_left, bool is_pinned_to_right) {
1863 [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
1864 toRight:is_pinned_to_right];
1867 bool RenderWidgetHostViewMac::LockMouse() {
1871 mouse_locked_ = true;
1873 // Lock position of mouse cursor and hide it.
1874 CGAssociateMouseAndMouseCursorPosition(NO);
1877 // Clear the tooltip window.
1878 SetTooltipText(base::string16());
1883 void RenderWidgetHostViewMac::UnlockMouse() {
1886 mouse_locked_ = false;
1888 // Unlock position of mouse cursor and unhide it.
1889 CGAssociateMouseAndMouseCursorPosition(YES);
1892 if (render_widget_host_)
1893 render_widget_host_->LostMouseLock();
1896 void RenderWidgetHostViewMac::UnhandledWheelEvent(
1897 const blink::WebMouseWheelEvent& event) {
1898 // Only record a wheel event as unhandled if JavaScript handlers got a chance
1899 // to see it (no-op wheel events are ignored by the event dispatcher)
1900 if (event.deltaX || event.deltaY)
1901 [cocoa_view_ gotUnhandledWheelEvent];
1904 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
1905 if (render_widget_host_)
1906 return render_widget_host_->Send(message);
1911 void RenderWidgetHostViewMac::SoftwareFrameWasFreed(
1912 uint32 output_surface_id, unsigned frame_id) {
1913 if (!render_widget_host_)
1915 cc::CompositorFrameAck ack;
1916 ack.last_software_frame_id = frame_id;
1917 RenderWidgetHostImpl::SendReclaimCompositorResources(
1918 render_widget_host_->GetRoutingID(),
1920 render_widget_host_->GetProcess()->GetID(),
1924 void RenderWidgetHostViewMac::ReleaseReferencesToSoftwareFrame() {
1925 DestroySoftwareLayer();
1928 void RenderWidgetHostViewMac::ShutdownHost() {
1929 weak_factory_.InvalidateWeakPtrs();
1930 render_widget_host_->Shutdown();
1931 // Do not touch any members at this point, |this| has been deleted.
1934 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
1935 EnsureCompositedIOSurfaceLayer();
1936 SendVSyncParametersToRenderer();
1937 if (!last_frame_was_accelerated_) {
1938 last_frame_was_accelerated_ = true;
1940 if (!use_core_animation_) {
1941 // Need to wipe the software view with transparency to expose the GL
1942 // underlay. Invalidate the whole window to do that.
1943 [cocoa_view_ setNeedsDisplay:YES];
1946 // Delete software backingstore and layer.
1947 BackingStoreManager::RemoveBackingStore(render_widget_host_);
1948 software_frame_manager_->DiscardCurrentFrame();
1949 DestroySoftwareLayer();
1953 void RenderWidgetHostViewMac::GotSoftwareFrame() {
1954 if (!render_widget_host_)
1957 EnsureSoftwareLayer();
1959 SendVSyncParametersToRenderer();
1961 // Draw the contents of the frame immediately. It is critical that this
1962 // happen before the frame be acked, otherwise the new frame will likely be
1963 // ready before the drawing is complete, thrashing the browser main thread.
1964 if (use_core_animation_) {
1965 [software_layer_ setNeedsDisplay];
1966 [software_layer_ displayIfNeeded];
1968 [cocoa_view_ setNeedsDisplay:YES];
1969 [cocoa_view_ displayIfNeeded];
1972 if (last_frame_was_accelerated_) {
1973 last_frame_was_accelerated_ = false;
1975 // If overlapping views are allowed, then don't unbind the context
1976 // from the view (that is, don't call clearDrawble -- just delete the
1977 // texture and IOSurface). Rather, let it sit behind the software frame
1978 // that will be put up in front. This will prevent transparent
1980 // http://crbug.com/154531
1981 // Also note that it is necessary that clearDrawable be called if
1982 // overlapping views are not allowed, e.g, for content shell.
1983 // http://crbug.com/178408
1984 // Disable screen updates so that the changes of flashes is minimized.
1985 // http://crbug.com/279472
1986 if (!use_core_animation_)
1987 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
1988 if (allow_overlapping_views_)
1989 DestroyCompositedIOSurfaceAndLayer(kLeaveContextBoundToView);
1991 DestroyCompositedIOSurfaceAndLayer(kDestroyContext);
1995 void RenderWidgetHostViewMac::TimerSinceGotAcceleratedFrameFired() {
1996 [compositing_iosurface_layer_ timerSinceGotNewFrameFired];
1999 void RenderWidgetHostViewMac::SetActive(bool active) {
2000 if (render_widget_host_) {
2001 render_widget_host_->SetActive(active);
2004 render_widget_host_->Focus();
2006 render_widget_host_->Blur();
2010 SetTextInputActive(active);
2012 [cocoa_view_ setPluginImeActive:NO];
2017 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
2018 if (render_widget_host_) {
2019 render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
2020 render_widget_host_->GetRoutingID(), visible));
2024 void RenderWidgetHostViewMac::WindowFrameChanged() {
2025 if (render_widget_host_) {
2026 render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
2027 render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
2031 if (compositing_iosurface_) {
2032 // This will migrate the context to the appropriate window.
2033 if (!EnsureCompositedIOSurface())
2034 GotAcceleratedCompositingError();
2038 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
2039 RenderWidgetHostViewMacDictionaryHelper helper(this);
2040 helper.ShowDefinitionForSelection();
2043 void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) {
2044 RenderWidgetHostViewBase::SetBackground(background);
2045 if (render_widget_host_)
2046 render_widget_host_->Send(new ViewMsg_SetBackground(
2047 render_widget_host_->GetRoutingID(), background));
2050 void RenderWidgetHostViewMac::CreateBrowserAccessibilityManagerIfNeeded() {
2051 if (!GetBrowserAccessibilityManager()) {
2052 SetBrowserAccessibilityManager(
2053 new BrowserAccessibilityManagerMac(
2055 BrowserAccessibilityManagerMac::GetEmptyDocument(),
2060 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
2062 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2063 EnablePasswordInput();
2065 DisablePasswordInput();
2067 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2068 DisablePasswordInput();
2072 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
2074 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
2077 void RenderWidgetHostViewMac::OnStartPluginIme() {
2078 [cocoa_view_ setPluginImeActive:YES];
2081 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
2082 const gfx::Rect& rect) {
2083 gfx::Rect src_gl_subrect = rect;
2084 src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
2086 return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
2087 ViewScaleFactor()));
2090 void RenderWidgetHostViewMac::AddPendingLatencyInfo(
2091 const std::vector<ui::LatencyInfo>& latency_info) {
2092 // If a screenshot is being taken when using CoreAnimation, send a few extra
2093 // calls to setNeedsDisplay and wait for their resulting display calls,
2094 // before reporting that the frame has reached the screen.
2095 if (use_core_animation_) {
2096 bool should_defer = false;
2097 for (size_t i = 0; i < latency_info.size(); i++) {
2098 if (latency_info[i].FindLatency(
2099 ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT,
2100 render_widget_host_->GetLatencyComponentId(),
2102 should_defer = true;
2106 // Multiple pending screenshot requests will work, but if every frame
2107 // requests a screenshot, then the delay will never expire. Assert this
2108 // here to avoid this.
2109 CHECK_EQ(pending_latency_info_delay_, 0u);
2110 // Wait a fixed number of frames (calls to CALayer::display) before
2111 // claiming that the screenshot has reached the screen. This number
2112 // comes from taking the first number where tests didn't fail (six),
2114 const uint32 kScreenshotLatencyDelayInFrames = 12;
2115 pending_latency_info_delay_ = kScreenshotLatencyDelayInFrames;
2116 TickPendingLatencyInfoDelay();
2120 for (size_t i = 0; i < latency_info.size(); i++) {
2121 pending_latency_info_.push_back(latency_info[i]);
2125 void RenderWidgetHostViewMac::SendPendingLatencyInfoToHost() {
2126 if (pending_latency_info_delay_) {
2127 pending_latency_info_delay_ -= 1;
2130 pending_latency_info_delay_weak_ptr_factory_.InvalidateWeakPtrs();
2132 for (size_t i = 0; i < pending_latency_info_.size(); i++) {
2133 pending_latency_info_[i].AddLatencyNumber(
2134 ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
2135 render_widget_host_->FrameSwapped(pending_latency_info_[i]);
2137 pending_latency_info_.clear();
2140 void RenderWidgetHostViewMac::TickPendingLatencyInfoDelay() {
2141 if (compositing_iosurface_layer_) {
2142 // Keep calling gotNewFrame in a loop until enough display calls come in.
2143 // Each call will be separated by about a vsync.
2144 base::MessageLoop::current()->PostTask(
2146 base::Bind(&RenderWidgetHostViewMac::TickPendingLatencyInfoDelay,
2147 pending_latency_info_delay_weak_ptr_factory_.GetWeakPtr()));
2148 [compositing_iosurface_layer_ gotNewFrame];
2150 if (software_layer_) {
2151 // In software mode, setNeedsDisplay will almost immediately result in the
2152 // layer's draw function being called, so manually insert a pretend-vsync
2154 base::MessageLoop::current()->PostDelayedTask(
2156 base::Bind(&RenderWidgetHostViewMac::TickPendingLatencyInfoDelay,
2157 pending_latency_info_delay_weak_ptr_factory_.GetWeakPtr()),
2158 base::TimeDelta::FromMilliseconds(1000/60));
2159 [software_layer_ setNeedsDisplay];
2163 void RenderWidgetHostViewMac::AddPendingSwapAck(
2164 int32 route_id, int gpu_host_id, int32 renderer_id) {
2165 // Note that multiple un-acked swaps can come in the event of a GPU process
2166 // loss. Drop the old acks.
2167 pending_swap_ack_.reset(new PendingSwapAck(
2168 route_id, gpu_host_id, renderer_id));
2171 void RenderWidgetHostViewMac::SendPendingSwapAck() {
2172 if (!pending_swap_ack_)
2175 AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
2176 ack_params.sync_point = 0;
2177 ack_params.renderer_id = pending_swap_ack_->renderer_id;
2178 RenderWidgetHostImpl::AcknowledgeBufferPresent(pending_swap_ack_->route_id,
2179 pending_swap_ack_->gpu_host_id,
2181 if (render_widget_host_)
2182 render_widget_host_->AcknowledgeSwapBuffersToRenderer();
2184 pending_swap_ack_.reset();
2187 void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() {
2188 if (!render_widget_host_ || render_widget_host_->is_hidden())
2191 // Ensure that all frames are acked before waiting for a frame to come in.
2192 // Note that we will draw a frame at the end of this function, so it is safe
2193 // to ack a never-drawn frame here.
2194 SendPendingSwapAck();
2196 // Wait for a frame of the right size to come in.
2197 render_widget_host_->PauseForPendingResizeOrRepaints();
2199 // Immediately draw any frames that haven't been drawn yet. This is necessary
2200 // to keep the window and the window's contents in sync.
2201 [cocoa_view_ displayIfNeeded];
2202 [software_layer_ displayIfNeeded];
2203 [compositing_iosurface_layer_ displayIfNeeded];
2206 void RenderWidgetHostViewMac::LayoutLayers() {
2207 if (!use_core_animation_)
2210 // Disable animation of the layer's resizing or change in contents scale.
2211 ScopedCAActionDisabler disabler;
2213 CGRect new_background_frame = NSRectToCGRect([cocoa_view() bounds]);
2215 // Dynamically calling setContentsScale on a CAOpenGLLayer for which
2216 // setAsynchronous is dynamically toggled can result in flashes of corrupt
2217 // content. Work around this by replacing the entire layer when the scale
2219 if (compositing_iosurface_ &&
2220 [compositing_iosurface_layer_
2221 respondsToSelector:(@selector(contentsScale))]) {
2222 if (compositing_iosurface_->scale_factor() !=
2223 [compositing_iosurface_layer_ contentsScale]) {
2224 DestroyCompositedIOSurfaceLayer();
2225 EnsureCompositedIOSurfaceLayer();
2228 if (compositing_iosurface_ && compositing_iosurface_layer_) {
2229 CGRect layer_bounds = CGRectMake(
2232 compositing_iosurface_->dip_io_surface_size().width(),
2233 compositing_iosurface_->dip_io_surface_size().height());
2234 CGPoint layer_position = CGPointMake(
2236 CGRectGetHeight(new_background_frame) - CGRectGetHeight(layer_bounds));
2237 bool bounds_changed = !CGRectEqualToRect(
2238 layer_bounds, [compositing_iosurface_layer_ bounds]);
2239 [compositing_iosurface_layer_ setPosition:layer_position];
2240 [compositing_iosurface_layer_ setBounds:layer_bounds];
2242 // If the bounds changed, then draw the frame immediately, to ensure that
2243 // content displayed is in sync with the window size.
2244 if (bounds_changed) {
2245 // Also, sometimes, especially when infobars are being removed, the
2246 // setNeedsDisplay calls are dropped on the floor, and stale content is
2247 // displayed. Calling displayIfNeeded will ensure that the right size
2248 // frame is drawn to the screen.
2249 // http://crbug.com/350817
2250 [compositing_iosurface_layer_ setNeedsDisplay];
2251 [compositing_iosurface_layer_ displayIfNeeded];
2255 // Dynamically update the software layer's contents scale to match the
2257 if (software_frame_manager_->HasCurrentFrame() &&
2258 [software_layer_ respondsToSelector:(@selector(contentsScale))] &&
2259 [software_layer_ respondsToSelector:(@selector(setContentsScale:))]) {
2260 if (software_frame_manager_->GetCurrentFrameDeviceScaleFactor() !=
2261 [software_layer_ contentsScale]) {
2262 [software_layer_ setContentsScale:
2263 software_frame_manager_->GetCurrentFrameDeviceScaleFactor()];
2266 // Changing the software layer's bounds and position doesn't always result
2267 // in the layer being anchored to the top-left. Set the layer's frame
2268 // explicitly, since this is more reliable in practice.
2269 if (software_layer_) {
2270 [software_layer_ setFrame:new_background_frame];
2274 SkBitmap::Config RenderWidgetHostViewMac::PreferredReadbackFormat() {
2275 return SkBitmap::kARGB_8888_Config;
2278 } // namespace content
2280 // RenderWidgetHostViewCocoa ---------------------------------------------------
2282 @implementation RenderWidgetHostViewCocoa
2283 @synthesize selectedRange = selectedRange_;
2284 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
2285 @synthesize markedRange = markedRange_;
2287 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
2288 self = [super initWithFrame:NSZeroRect];
2290 self.acceptsTouchEvents = YES;
2291 editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
2292 editCommand_helper_->AddEditingSelectorsToClass([self class]);
2294 renderWidgetHostView_.reset(r);
2295 canBeKeyView_ = YES;
2296 focusedPluginIdentifier_ = -1;
2297 renderWidgetHostView_->backing_store_scale_factor_ =
2298 ScaleFactorForView(self);
2301 if ([self respondsToSelector:
2302 @selector(setWantsBestResolutionOpenGLSurface:)]) {
2303 [self setWantsBestResolutionOpenGLSurface:YES];
2305 handlingGlobalFrameDidChange_ = NO;
2306 [[NSNotificationCenter defaultCenter]
2308 selector:@selector(globalFrameDidChange:)
2309 name:NSViewGlobalFrameDidChangeNotification
2311 [[NSNotificationCenter defaultCenter]
2313 selector:@selector(didChangeScreenParameters:)
2314 name:NSApplicationDidChangeScreenParametersNotification
2321 // Unbind the GL context from this view. If this is not done before super's
2322 // dealloc is called then the GL context will crash when it reaches into
2323 // the view in its destructor.
2324 // http://crbug.com/255608
2325 if (renderWidgetHostView_)
2326 renderWidgetHostView_->AcceleratedSurfaceRelease();
2328 if (responderDelegate_ &&
2329 [responderDelegate_ respondsToSelector:@selector(viewGone:)])
2330 [responderDelegate_ viewGone:self];
2331 responderDelegate_.reset();
2333 [[NSNotificationCenter defaultCenter] removeObserver:self];
2338 - (void)didChangeScreenParameters:(NSNotification*)notify {
2339 g_screen_info_up_to_date = false;
2342 - (void)setResponderDelegate:
2343 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate {
2344 DCHECK(!responderDelegate_);
2345 responderDelegate_.reset([delegate retain]);
2348 - (void)resetCursorRects {
2349 if (currentCursor_) {
2350 [self addCursorRect:[self visibleRect] cursor:currentCursor_];
2351 [currentCursor_ setOnMouseEntered:YES];
2355 - (void)gotUnhandledWheelEvent {
2356 if (responderDelegate_ &&
2358 respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
2359 [responderDelegate_ gotUnhandledWheelEvent];
2363 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
2364 if (responderDelegate_ &&
2366 respondsToSelector:@selector(scrollOffsetPinnedToLeft:toRight:)]) {
2367 [responderDelegate_ scrollOffsetPinnedToLeft:left toRight:right];
2371 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
2372 if (responderDelegate_ &&
2374 respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
2375 [responderDelegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
2379 - (BOOL)respondsToSelector:(SEL)selector {
2380 // Trickiness: this doesn't mean "does this object's superclass respond to
2381 // this selector" but rather "does the -respondsToSelector impl from the
2382 // superclass say that this class responds to the selector".
2383 if ([super respondsToSelector:selector])
2386 if (responderDelegate_)
2387 return [responderDelegate_ respondsToSelector:selector];
2392 - (id)forwardingTargetForSelector:(SEL)selector {
2393 if ([responderDelegate_ respondsToSelector:selector])
2394 return responderDelegate_.get();
2396 return [super forwardingTargetForSelector:selector];
2399 - (void)setCanBeKeyView:(BOOL)can {
2400 canBeKeyView_ = can;
2403 - (BOOL)acceptsMouseEventsWhenInactive {
2404 // Some types of windows (balloons, always-on-top panels) want to accept mouse
2405 // clicks w/o the first click being treated as 'activation'. Same applies to
2406 // mouse move events.
2407 return [[self window] level] > NSNormalWindowLevel;
2410 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
2411 return [self acceptsMouseEventsWhenInactive];
2414 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
2415 takesFocusOnlyOnMouseDown_ = b;
2418 - (void)setCloseOnDeactivate:(BOOL)b {
2419 closeOnDeactivate_ = b;
2422 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
2423 NSWindow* window = [self window];
2424 // If this is a background window, don't handle mouse movement events. This
2425 // is the expected behavior on the Mac as evidenced by other applications.
2426 if ([theEvent type] == NSMouseMoved &&
2427 ![self acceptsMouseEventsWhenInactive] &&
2428 ![window isKeyWindow]) {
2432 // Use hitTest to check whether the mouse is over a nonWebContentView - in
2433 // which case the mouse event should not be handled by the render host.
2434 const SEL nonWebContentViewSelector = @selector(nonWebContentView);
2435 NSView* contentView = [window contentView];
2436 NSView* view = [contentView hitTest:[theEvent locationInWindow]];
2437 // Traverse the superview hierarchy as the hitTest will return the frontmost
2438 // view, such as an NSTextView, while nonWebContentView may be specified by
2441 if ([view respondsToSelector:nonWebContentViewSelector] &&
2442 [view performSelector:nonWebContentViewSelector]) {
2443 // The cursor is over a nonWebContentView - ignore this mouse event.
2446 if ([view isKindOfClass:[self class]] && ![view isEqual:self] &&
2447 !hasOpenMouseDown_) {
2448 // The cursor is over an overlapping render widget. This check is done by
2449 // both views so the one that's returned by -hitTest: will end up
2450 // processing the event.
2451 // Note that while dragging, we only get events for the render view where
2452 // drag started, even if mouse is actually over another view or outside
2453 // the window. Cocoa does this for us. We should handle these events and
2454 // not ignore (since there is no other render view to handle them). Thus
2455 // the |!hasOpenMouseDown_| check above.
2458 view = [view superview];
2463 - (void)mouseEvent:(NSEvent*)theEvent {
2464 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
2465 if (responderDelegate_ &&
2466 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2467 BOOL handled = [responderDelegate_ handleEvent:theEvent];
2472 if ([self shouldIgnoreMouseEvent:theEvent]) {
2473 // If this is the first such event, send a mouse exit to the host view.
2474 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
2475 WebMouseEvent exitEvent =
2476 WebInputEventFactory::mouseEvent(theEvent, self);
2477 exitEvent.type = WebInputEvent::MouseLeave;
2478 exitEvent.button = WebMouseEvent::ButtonNone;
2479 renderWidgetHostView_->ForwardMouseEvent(exitEvent);
2481 mouseEventWasIgnored_ = YES;
2485 if (mouseEventWasIgnored_) {
2486 // If this is the first mouse event after a previous event that was ignored
2487 // due to the hitTest, send a mouse enter event to the host view.
2488 if (renderWidgetHostView_->render_widget_host_) {
2489 WebMouseEvent enterEvent =
2490 WebInputEventFactory::mouseEvent(theEvent, self);
2491 enterEvent.type = WebInputEvent::MouseMove;
2492 enterEvent.button = WebMouseEvent::ButtonNone;
2493 renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2496 mouseEventWasIgnored_ = NO;
2498 // TODO(rohitrao): Probably need to handle other mouse down events here.
2499 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2500 if (renderWidgetHostView_->render_widget_host_)
2501 renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2503 // Manually take focus after the click but before forwarding it to the
2505 [[self window] makeFirstResponder:self];
2508 // Don't cancel child popups; killing them on a mouse click would prevent the
2509 // user from positioning the insertion point in the text field spawning the
2510 // popup. A click outside the text field would cause the text field to drop
2511 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2512 // the popup anyway, so we're OK.
2514 NSEventType type = [theEvent type];
2515 if (type == NSLeftMouseDown)
2516 hasOpenMouseDown_ = YES;
2517 else if (type == NSLeftMouseUp)
2518 hasOpenMouseDown_ = NO;
2520 // TODO(suzhe): We should send mouse events to the input method first if it
2521 // wants to handle them. But it won't work without implementing method
2522 // - (NSUInteger)characterIndexForPoint:.
2523 // See: http://code.google.com/p/chromium/issues/detail?id=47141
2524 // Instead of sending mouse events to the input method first, we now just
2525 // simply confirm all ongoing composition here.
2526 if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2527 type == NSOtherMouseDown) {
2528 [self confirmComposition];
2531 const WebMouseEvent event =
2532 WebInputEventFactory::mouseEvent(theEvent, self);
2533 renderWidgetHostView_->ForwardMouseEvent(event);
2536 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2537 // |performKeyEquivalent:| is sent to all views of a window, not only down the
2538 // responder chain (cf. "Handling Key Equivalents" in
2539 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2540 // ). We only want to handle key equivalents if we're first responder.
2541 if ([[self window] firstResponder] != self)
2544 // If we return |NO| from this function, cocoa will send the key event to
2545 // the menu and only if the menu does not process the event to |keyDown:|. We
2546 // want to send the event to a renderer _before_ sending it to the menu, so
2547 // we need to return |YES| for all events that might be swallowed by the menu.
2548 // We do not return |YES| for every keypress because we don't get |keyDown:|
2549 // events for keys that we handle this way.
2550 NSUInteger modifierFlags = [theEvent modifierFlags];
2551 if ((modifierFlags & NSCommandKeyMask) == 0) {
2552 // Make sure the menu does not contain key equivalents that don't
2554 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2558 // Command key combinations are sent via performKeyEquivalent rather than
2559 // keyDown:. We just forward this on and if WebCore doesn't want to handle
2560 // it, we let the WebContentsView figure out how to reinject it.
2561 [self keyEvent:theEvent wasKeyEquivalent:YES];
2565 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2566 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2567 // returned NO. If this function returns |YES|, Cocoa sends the event to
2568 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2569 // to us instead of doing key view loop control, ctrl-left/right get handled
2571 // (However, there are still some keys that Cocoa swallows, e.g. the key
2572 // equivalent that Cocoa uses for toggling the input language. In this case,
2573 // that's actually a good thing, though -- see http://crbug.com/26115 .)
2577 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2578 if (responderDelegate_ &&
2579 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2580 BOOL handled = [responderDelegate_ handleEvent:theEvent];
2582 return kEventHandled;
2585 [self keyEvent:theEvent wasKeyEquivalent:NO];
2586 return kEventHandled;
2589 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2590 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2591 DCHECK([theEvent type] != NSKeyDown ||
2592 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2594 if ([theEvent type] == NSFlagsChanged) {
2595 // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2596 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2597 int keyCode = [theEvent keyCode];
2598 if (!keyCode || keyCode == 10 || keyCode == 63)
2602 // Don't cancel child popups; the key events are probably what's triggering
2603 // the popup in the first place.
2605 RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2608 NativeWebKeyboardEvent event(theEvent);
2610 // Force fullscreen windows to close on Escape so they won't keep the keyboard
2611 // grabbed or be stuck onscreen if the renderer is hanging.
2612 if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2613 event.windowsKeyCode == ui::VKEY_ESCAPE &&
2614 renderWidgetHostView_->pepper_fullscreen_window()) {
2615 RenderWidgetHostViewMac* parent =
2616 renderWidgetHostView_->fullscreen_parent_host_view();
2618 parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2619 widgetHost->Shutdown();
2623 // Suppress the escape key up event if necessary.
2624 if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2625 if (event.type == NativeWebKeyboardEvent::KeyUp)
2626 suppressNextEscapeKeyUp_ = NO;
2630 // We only handle key down events and just simply forward other events.
2631 if ([theEvent type] != NSKeyDown) {
2632 widgetHost->ForwardKeyboardEvent(event);
2634 // Possibly autohide the cursor.
2635 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2636 [NSCursor setHiddenUntilMouseMoves:YES];
2641 base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2643 // Records the current marked text state, so that we can know if the marked
2644 // text was deleted or not after handling the key down event.
2645 BOOL oldHasMarkedText = hasMarkedText_;
2647 // This method should not be called recursively.
2648 DCHECK(!handlingKeyDown_);
2650 // Tells insertText: and doCommandBySelector: that we are handling a key
2652 handlingKeyDown_ = YES;
2654 // These variables might be set when handling the keyboard event.
2655 // Clear them here so that we can know whether they have changed afterwards.
2656 textToBeInserted_.clear();
2657 markedText_.clear();
2658 underlines_.clear();
2659 unmarkTextCalled_ = NO;
2660 hasEditCommands_ = NO;
2661 editCommands_.clear();
2663 // Before doing anything with a key down, check to see if plugin IME has been
2664 // cancelled, since the plugin host needs to be informed of that before
2665 // receiving the keydown.
2666 if ([theEvent type] == NSKeyDown)
2667 [self checkForPluginImeCancellation];
2669 // Sends key down events to input method first, then we can decide what should
2670 // be done according to input method's feedback.
2671 // If a plugin is active, bypass this step since events are forwarded directly
2672 // to the plugin IME.
2673 if (focusedPluginIdentifier_ == -1)
2674 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2676 handlingKeyDown_ = NO;
2678 // Indicates if we should send the key event and corresponding editor commands
2679 // after processing the input method result.
2680 BOOL delayEventUntilAfterImeCompostion = NO;
2682 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2683 // while an input method is composing or inserting a text.
2684 // Gmail checks this code in its onkeydown handler to stop auto-completing
2685 // e-mail addresses while composing a CJK text.
2686 // If the text to be inserted has only one character, then we don't need this
2687 // trick, because we'll send the text as a key press event instead.
2688 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2689 NativeWebKeyboardEvent fakeEvent = event;
2690 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
2691 fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2692 fakeEvent.skip_in_browser = true;
2693 widgetHost->ForwardKeyboardEvent(fakeEvent);
2694 // If this key event was handled by the input method, but
2695 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2696 // enqueued edit commands, then in order to let webkit handle them
2697 // correctly, we need to send the real key event and corresponding edit
2698 // commands after processing the input method result.
2699 // We shouldn't do this if a new marked text was set by the input method,
2700 // otherwise the new marked text might be cancelled by webkit.
2701 if (hasEditCommands_ && !hasMarkedText_)
2702 delayEventUntilAfterImeCompostion = YES;
2704 if (!editCommands_.empty()) {
2705 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2706 widgetHost->GetRoutingID(), editCommands_));
2708 widgetHost->ForwardKeyboardEvent(event);
2711 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2712 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2713 // be set to NULL. So we check it here and return immediately if it's NULL.
2714 if (!renderWidgetHostView_->render_widget_host_)
2717 // Then send keypress and/or composition related events.
2718 // If there was a marked text or the text to be inserted is longer than 1
2719 // character, then we send the text by calling ConfirmComposition().
2720 // Otherwise, if the text to be inserted only contains 1 character, then we
2721 // can just send a keypress event which is fabricated by changing the type of
2722 // the keydown event, so that we can retain all necessary informations, such
2723 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2724 // prevent the browser from handling it again.
2725 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2726 // handle BMP characters here, as we can always insert non-BMP characters as
2728 BOOL textInserted = NO;
2729 if (textToBeInserted_.length() >
2730 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2731 widgetHost->ImeConfirmComposition(
2732 textToBeInserted_, gfx::Range::InvalidRange(), false);
2736 // Updates or cancels the composition. If some text has been inserted, then
2737 // we don't need to cancel the composition explicitly.
2738 if (hasMarkedText_ && markedText_.length()) {
2739 // Sends the updated marked text to the renderer so it can update the
2740 // composition node in WebKit.
2741 // When marked text is available, |selectedRange_| will be the range being
2742 // selected inside the marked text.
2743 widgetHost->ImeSetComposition(markedText_, underlines_,
2744 selectedRange_.location,
2745 NSMaxRange(selectedRange_));
2746 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2747 if (unmarkTextCalled_) {
2748 widgetHost->ImeConfirmComposition(
2749 base::string16(), gfx::Range::InvalidRange(), false);
2751 widgetHost->ImeCancelComposition();
2755 // If the key event was handled by the input method but it also generated some
2756 // edit commands, then we need to send the real key event and corresponding
2757 // edit commands here. This usually occurs when the input method wants to
2758 // finish current composition session but still wants the application to
2759 // handle the key event. See http://crbug.com/48161 for reference.
2760 if (delayEventUntilAfterImeCompostion) {
2761 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2762 // with windowsKeyCode == 0xE5 has already been sent to webkit.
2763 // So before sending the real key down event, we need to send a fake key up
2764 // event to balance it.
2765 NativeWebKeyboardEvent fakeEvent = event;
2766 fakeEvent.type = blink::WebInputEvent::KeyUp;
2767 fakeEvent.skip_in_browser = true;
2768 widgetHost->ForwardKeyboardEvent(fakeEvent);
2769 // Not checking |renderWidgetHostView_->render_widget_host_| here because
2770 // a key event with |skip_in_browser| == true won't be handled by browser,
2771 // thus it won't destroy the widget.
2773 if (!editCommands_.empty()) {
2774 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2775 widgetHost->GetRoutingID(), editCommands_));
2777 widgetHost->ForwardKeyboardEvent(event);
2779 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2780 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2781 // be set to NULL. So we check it here and return immediately if it's NULL.
2782 if (!renderWidgetHostView_->render_widget_host_)
2786 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2787 // Only send a corresponding key press event if there is no marked text.
2788 if (!hasMarkedText_) {
2789 if (!textInserted && textToBeInserted_.length() == 1) {
2790 // If a single character was inserted, then we just send it as a keypress
2792 event.type = blink::WebInputEvent::Char;
2793 event.text[0] = textToBeInserted_[0];
2795 event.skip_in_browser = true;
2796 widgetHost->ForwardKeyboardEvent(event);
2797 } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2798 [[theEvent characters] length] > 0 &&
2799 (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2800 (hasEditCommands_ && editCommands_.empty()))) {
2801 // We don't get insertText: calls if ctrl or cmd is down, or the key event
2802 // generates an insert command. So synthesize a keypress event for these
2803 // cases, unless the key event generated any other command.
2804 event.type = blink::WebInputEvent::Char;
2805 event.skip_in_browser = true;
2806 widgetHost->ForwardKeyboardEvent(event);
2810 // Possibly autohide the cursor.
2811 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2812 [NSCursor setHiddenUntilMouseMoves:YES];
2815 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2816 DCHECK(base::mac::IsOSLionOrLater());
2818 if ([event phase] != NSEventPhaseEnded &&
2819 [event phase] != NSEventPhaseCancelled) {
2823 if (renderWidgetHostView_->render_widget_host_) {
2824 const WebMouseWheelEvent& webEvent =
2825 WebInputEventFactory::mouseWheelEvent(event, self);
2826 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2829 if (endWheelMonitor_) {
2830 [NSEvent removeMonitor:endWheelMonitor_];
2831 endWheelMonitor_ = nil;
2835 - (void)beginGestureWithEvent:(NSEvent*)event {
2836 [responderDelegate_ beginGestureWithEvent:event];
2838 - (void)endGestureWithEvent:(NSEvent*)event {
2839 [responderDelegate_ endGestureWithEvent:event];
2841 - (void)touchesMovedWithEvent:(NSEvent*)event {
2842 [responderDelegate_ touchesMovedWithEvent:event];
2844 - (void)touchesBeganWithEvent:(NSEvent*)event {
2845 [responderDelegate_ touchesBeganWithEvent:event];
2847 - (void)touchesCancelledWithEvent:(NSEvent*)event {
2848 [responderDelegate_ touchesCancelledWithEvent:event];
2850 - (void)touchesEndedWithEvent:(NSEvent*)event {
2851 [responderDelegate_ touchesEndedWithEvent:event];
2854 // This is invoked only on 10.8 or newer when the user taps a word using
2856 - (void)quickLookWithEvent:(NSEvent*)event {
2857 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
2858 TextInputClientMac::GetInstance()->GetStringAtPoint(
2859 renderWidgetHostView_->render_widget_host_,
2860 gfx::Point(point.x, NSHeight([self frame]) - point.y),
2861 ^(NSAttributedString* string, NSPoint baselinePoint) {
2862 if (string && [string length] > 0) {
2863 dispatch_async(dispatch_get_main_queue(), ^{
2864 [self showDefinitionForAttributedString:string
2865 atPoint:baselinePoint];
2872 // This method handles 2 different types of hardware events.
2873 // (Apple does not distinguish between them).
2874 // a. Scrolling the middle wheel of a mouse.
2875 // b. Swiping on the track pad.
2877 // This method is responsible for 2 types of behavior:
2878 // a. Scrolling the content of window.
2879 // b. Navigating forwards/backwards in history.
2881 // This is a brief description of the logic:
2882 // 1. If the content can be scrolled, scroll the content.
2883 // (This requires a roundtrip to blink to determine whether the content
2884 // can be scrolled.)
2885 // Once this logic is triggered, the navigate logic cannot be triggered
2886 // until the gesture finishes.
2887 // 2. If the user is making a horizontal swipe, start the navigate
2888 // forward/backwards UI.
2889 // Once this logic is triggered, the user can either cancel or complete
2890 // the gesture. If the user completes the gesture, all remaining touches
2891 // are swallowed, and not allowed to scroll the content. If the user
2892 // cancels the gesture, all remaining touches are forwarded to the content
2893 // scroll logic. The user cannot trigger the navigation logic again.
2894 - (void)scrollWheel:(NSEvent*)event {
2895 if (responderDelegate_ &&
2896 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2897 BOOL handled = [responderDelegate_ handleEvent:event];
2902 // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
2903 // the event is received even when the mouse cursor is no longer over the view
2904 // when the scrolling ends (e.g. if the tab was switched). This is necessary
2905 // for ending rubber-banding in such cases.
2906 if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
2907 !endWheelMonitor_) {
2909 [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
2910 handler:^(NSEvent* blockEvent) {
2911 [self shortCircuitScrollWheelEvent:blockEvent];
2916 // This is responsible for content scrolling!
2917 if (renderWidgetHostView_->render_widget_host_) {
2918 const WebMouseWheelEvent& webEvent =
2919 WebInputEventFactory::mouseWheelEvent(event, self);
2920 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2924 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
2925 NSWindow* oldWindow = [self window];
2927 // We're messing with the window, so do this to ensure no flashes. This one
2928 // prevents a flash when the current tab is closed.
2929 if (!renderWidgetHostView_->use_core_animation_)
2930 [oldWindow disableScreenUpdatesUntilFlush];
2932 NSNotificationCenter* notificationCenter =
2933 [NSNotificationCenter defaultCenter];
2935 // Backing property notifications crash on 10.6 when building with the 10.7
2936 // SDK, see http://crbug.com/260595.
2937 static BOOL supportsBackingPropertiesNotification =
2938 SupportsBackingPropertiesChangedNotification();
2941 if (supportsBackingPropertiesNotification) {
2944 name:NSWindowDidChangeBackingPropertiesNotification
2949 name:NSWindowDidMoveNotification
2953 name:NSWindowDidEndLiveResizeNotification
2957 if (supportsBackingPropertiesNotification) {
2960 selector:@selector(windowDidChangeBackingProperties:)
2961 name:NSWindowDidChangeBackingPropertiesNotification
2966 selector:@selector(windowChangedGlobalFrame:)
2967 name:NSWindowDidMoveNotification
2971 selector:@selector(windowChangedGlobalFrame:)
2972 name:NSWindowDidEndLiveResizeNotification
2977 - (void)updateScreenProperties{
2978 renderWidgetHostView_->UpdateBackingStoreScaleFactor();
2979 renderWidgetHostView_->UpdateDisplayLink();
2982 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
2983 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
2984 // Background tabs check if their scale factor or vsync properties changed
2985 // when they are added to a window.
2987 // Allocating a CGLayerRef with the current scale factor immediately from
2988 // this handler doesn't work. Schedule the backing store update on the
2989 // next runloop cycle, then things are read for CGLayerRef allocations to
2991 [self performSelector:@selector(updateScreenProperties)
2996 - (void)globalFrameDidChange:(NSNotification*)notification {
2997 if (handlingGlobalFrameDidChange_)
3000 handlingGlobalFrameDidChange_ = YES;
3001 if (!renderWidgetHostView_->use_core_animation_ &&
3002 renderWidgetHostView_->compositing_iosurface_context_) {
3003 [renderWidgetHostView_->compositing_iosurface_context_->nsgl_context()
3006 handlingGlobalFrameDidChange_ = NO;
3009 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
3010 renderWidgetHostView_->UpdateScreenInfo(
3011 renderWidgetHostView_->GetNativeView());
3014 - (void)setFrameSize:(NSSize)newSize {
3015 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
3017 // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
3018 // -setFrame: isn't neccessary.
3019 [super setFrameSize:newSize];
3021 if (!renderWidgetHostView_->render_widget_host_)
3024 // Move the CALayers to their positions in the new view size. Note that
3025 // this will not draw anything because the non-background layers' sizes
3026 // didn't actually change.
3027 renderWidgetHostView_->LayoutLayers();
3029 renderWidgetHostView_->render_widget_host_->SendScreenRects();
3030 renderWidgetHostView_->render_widget_host_->WasResized();
3032 // Wait for the frame that WasResize might have requested. If the view is
3033 // being made visible at a new size, then this call will have no effect
3034 // because the view widget is still hidden, and the pause call in WasShown
3035 // will have this effect for us.
3036 renderWidgetHostView_->PauseForPendingResizeOrRepaintsAndDraw();
3039 // Fills with white the parts of the area to the right and bottom for |rect|
3040 // that intersect |damagedRect|.
3041 - (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
3042 dirtyRect:(gfx::Rect)damagedRect
3043 inContext:(CGContextRef)context {
3044 if (damagedRect.right() > rect.right()) {
3045 int x = std::max(rect.right(), damagedRect.x());
3046 int y = std::min(rect.bottom(), damagedRect.bottom());
3047 int width = damagedRect.right() - x;
3048 int height = damagedRect.y() - y;
3050 // Extra fun to get around the fact that gfx::Rects can't have
3061 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
3062 CGContextSetFillColorWithColor(context,
3063 CGColorGetConstantColor(kCGColorWhite));
3064 CGContextFillRect(context, NSRectToCGRect(r));
3066 if (damagedRect.bottom() > rect.bottom()) {
3067 int x = damagedRect.x();
3068 int y = damagedRect.bottom();
3069 int width = damagedRect.right() - x;
3070 int height = std::max(rect.bottom(), damagedRect.y()) - y;
3072 // Extra fun to get around the fact that gfx::Rects can't have
3083 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
3084 CGContextSetFillColorWithColor(context,
3085 CGColorGetConstantColor(kCGColorWhite));
3086 CGContextFillRect(context, NSRectToCGRect(r));
3090 - (void)drawRect:(NSRect)dirtyRect {
3091 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::drawRect");
3092 DCHECK(!renderWidgetHostView_->use_core_animation_);
3094 if (!renderWidgetHostView_->render_widget_host_) {
3095 // When using CoreAnimation, this path is used to paint the contents area
3096 // white before any frames come in. When layers to draw frames exist, this
3098 [[NSColor whiteColor] set];
3099 NSRectFill(dirtyRect);
3103 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
3104 renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
3106 const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
3108 if (renderWidgetHostView_->last_frame_was_accelerated_ &&
3109 renderWidgetHostView_->compositing_iosurface_) {
3110 if (renderWidgetHostView_->allow_overlapping_views_) {
3111 // If overlapping views need to be allowed, punch a hole in the window
3112 // to expose the GL underlay.
3113 TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
3114 "h", damagedRect.height());
3115 // NSRectFill is extremely slow (15ms for a window on a fast MacPro), so
3116 // this is only done when it's a real invalidation from window damage (not
3117 // when a BuffersSwapped was received). Note that even a 1x1 NSRectFill
3118 // can take many milliseconds sometimes (!) so this is skipped completely
3119 // for drawRects that are triggered by BuffersSwapped messages.
3120 [[NSColor clearColor] set];
3121 NSRectFill(dirtyRect);
3124 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
3125 renderWidgetHostView_->compositing_iosurface_context_->cgl_context());
3126 renderWidgetHostView_->DrawIOSurfaceWithoutCoreAnimation();
3130 CGContextRef context = static_cast<CGContextRef>(
3131 [[NSGraphicsContext currentContext] graphicsPort]);
3132 [self drawBackingStore:backingStore
3133 dirtyRect:NSRectToCGRect(dirtyRect)
3137 - (void)drawBackingStore:(BackingStoreMac*)backingStore
3138 dirtyRect:(CGRect)dirtyRect
3139 inContext:(CGContextRef)context {
3140 content::SoftwareFrameManager* software_frame_manager =
3141 renderWidgetHostView_->software_frame_manager_.get();
3142 // There should never be both a legacy software and software composited
3144 DCHECK(!backingStore || !software_frame_manager->HasCurrentFrame());
3146 if (backingStore || software_frame_manager->HasCurrentFrame()) {
3147 // Note: All coordinates are in view units, not pixels.
3148 gfx::Rect bitmapRect(
3149 software_frame_manager->HasCurrentFrame() ?
3150 software_frame_manager->GetCurrentFrameSizeInDIP() :
3151 backingStore->size());
3153 // Specify the proper y offset to ensure that the view is rooted to the
3154 // upper left corner. This can be negative, if the window was resized
3155 // smaller and the renderer hasn't yet repainted.
3156 int yOffset = NSHeight([self bounds]) - bitmapRect.height();
3158 NSRect nsDirtyRect = NSRectFromCGRect(dirtyRect);
3159 const gfx::Rect damagedRect([self flipNSRectToRect:nsDirtyRect]);
3161 gfx::Rect paintRect = gfx::IntersectRects(bitmapRect, damagedRect);
3162 if (!paintRect.IsEmpty()) {
3163 if (software_frame_manager->HasCurrentFrame()) {
3164 // If a software compositor framebuffer is present, draw using that.
3165 gfx::Size sizeInPixels =
3166 software_frame_manager->GetCurrentFrameSizeInPixels();
3167 base::ScopedCFTypeRef<CGDataProviderRef> dataProvider(
3168 CGDataProviderCreateWithData(
3170 software_frame_manager->GetCurrentFramePixels(),
3171 4 * sizeInPixels.width() * sizeInPixels.height(),
3173 base::ScopedCFTypeRef<CGImageRef> image(
3175 sizeInPixels.width(),
3176 sizeInPixels.height(),
3179 4 * sizeInPixels.width(),
3180 base::mac::GetSystemColorSpace(),
3181 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
3185 kCGRenderingIntentDefault));
3186 CGRect imageRect = bitmapRect.ToCGRect();
3187 imageRect.origin.y = yOffset;
3188 CGContextDrawImage(context, imageRect, image);
3189 } else if (backingStore->cg_layer()) {
3190 // If we have a CGLayer, draw that into the window
3191 // TODO: add clipping to dirtyRect if it improves drawing performance.
3192 CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset),
3193 backingStore->cg_layer());
3195 // If we haven't created a layer yet, draw the cached bitmap into
3196 // the window. The CGLayer will be created the next time the renderer
3198 base::ScopedCFTypeRef<CGImageRef> image(
3199 CGBitmapContextCreateImage(backingStore->cg_bitmap()));
3200 CGRect imageRect = bitmapRect.ToCGRect();
3201 imageRect.origin.y = yOffset;
3202 CGContextDrawImage(context, imageRect, image);
3206 renderWidgetHostView_->SendPendingLatencyInfoToHost();
3208 // Fill the remaining portion of the damagedRect with white
3209 [self fillBottomRightRemainderOfRect:bitmapRect
3210 dirtyRect:damagedRect
3213 if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
3214 base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
3215 renderWidgetHostView_->whiteout_start_time_;
3216 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
3218 // Reset the start time to 0 so that we start recording again the next
3219 // time the backing store is NULL...
3220 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
3222 if (!renderWidgetHostView_->web_contents_switch_paint_time_.is_null()) {
3223 base::TimeDelta web_contents_switch_paint_duration =
3224 base::TimeTicks::Now() -
3225 renderWidgetHostView_->web_contents_switch_paint_time_;
3226 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
3227 web_contents_switch_paint_duration);
3228 // Reset contents_switch_paint_time_ to 0 so future tab selections are
3230 renderWidgetHostView_->web_contents_switch_paint_time_ =
3234 CGContextSetFillColorWithColor(context,
3235 CGColorGetConstantColor(kCGColorWhite));
3236 CGContextFillRect(context, dirtyRect);
3237 if (renderWidgetHostView_->whiteout_start_time_.is_null())
3238 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
3242 - (BOOL)canBecomeKeyView {
3243 if (!renderWidgetHostView_->render_widget_host_)
3246 return canBeKeyView_;
3249 - (BOOL)acceptsFirstResponder {
3250 if (!renderWidgetHostView_->render_widget_host_)
3253 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
3256 - (BOOL)becomeFirstResponder {
3257 if (!renderWidgetHostView_->render_widget_host_)
3260 renderWidgetHostView_->render_widget_host_->Focus();
3261 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
3262 renderWidgetHostView_->SetTextInputActive(true);
3264 // Cancel any onging composition text which was left before we lost focus.
3265 // TODO(suzhe): We should do it in -resignFirstResponder: method, but
3266 // somehow that method won't be called when switching among different tabs.
3267 // See http://crbug.com/47209
3268 [self cancelComposition];
3270 NSNumber* direction = [NSNumber numberWithUnsignedInteger:
3271 [[self window] keyViewSelectionDirection]];
3272 NSDictionary* userInfo =
3273 [NSDictionary dictionaryWithObject:direction
3274 forKey:kSelectionDirection];
3275 [[NSNotificationCenter defaultCenter]
3276 postNotificationName:kViewDidBecomeFirstResponder
3283 - (BOOL)resignFirstResponder {
3284 renderWidgetHostView_->SetTextInputActive(false);
3285 if (!renderWidgetHostView_->render_widget_host_)
3288 if (closeOnDeactivate_)
3289 renderWidgetHostView_->KillSelf();
3291 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
3292 renderWidgetHostView_->render_widget_host_->Blur();
3294 // We should cancel any onging composition whenever RWH's Blur() method gets
3295 // called, because in this case, webkit will confirm the ongoing composition
3297 [self cancelComposition];
3302 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
3303 if (responderDelegate_ &&
3305 respondsToSelector:@selector(validateUserInterfaceItem:
3309 [responderDelegate_ validateUserInterfaceItem:item isValidItem:&valid];
3314 SEL action = [item action];
3316 if (action == @selector(stopSpeaking:)) {
3317 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3318 renderWidgetHostView_->IsSpeaking();
3320 if (action == @selector(startSpeaking:)) {
3321 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3322 renderWidgetHostView_->SupportsSpeech();
3325 // For now, these actions are always enabled for render view,
3326 // this is sub-optimal.
3327 // TODO(suzhe): Plumb the "can*" methods up from WebCore.
3328 if (action == @selector(undo:) ||
3329 action == @selector(redo:) ||
3330 action == @selector(cut:) ||
3331 action == @selector(copy:) ||
3332 action == @selector(copyToFindPboard:) ||
3333 action == @selector(paste:) ||
3334 action == @selector(pasteAndMatchStyle:)) {
3335 return renderWidgetHostView_->render_widget_host_->IsRenderView();
3338 return editCommand_helper_->IsMenuItemEnabled(action, self);
3341 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
3342 return renderWidgetHostView_.get();
3345 // Determine whether we should autohide the cursor (i.e., hide it until mouse
3346 // move) for the given event. Customize here to be more selective about which
3347 // key presses to autohide on.
3348 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
3349 return ([event type] == NSKeyDown &&
3350 !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
3353 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
3354 index:(NSUInteger)index
3355 maxCount:(NSUInteger)maxCount {
3356 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3357 NSUInteger totalLength = [fullArray count];
3358 if (index >= totalLength)
3360 NSUInteger length = MIN(totalLength - index, maxCount);
3361 return [fullArray subarrayWithRange:NSMakeRange(index, length)];
3364 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
3365 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3366 return [fullArray count];
3369 - (id)accessibilityAttributeValue:(NSString *)attribute {
3370 BrowserAccessibilityManager* manager =
3371 renderWidgetHostView_->GetBrowserAccessibilityManager();
3373 // Contents specifies document view of RenderWidgetHostViewCocoa provided by
3374 // BrowserAccessibilityManager. Children includes all subviews in addition to
3375 // contents. Currently we do not have subviews besides the document view.
3376 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
3377 [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
3379 return [NSArray arrayWithObjects:manager->
3380 GetRoot()->ToBrowserAccessibilityCocoa(), nil];
3381 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
3382 return NSAccessibilityScrollAreaRole;
3384 id ret = [super accessibilityAttributeValue:attribute];
3388 - (NSArray*)accessibilityAttributeNames {
3389 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
3390 [ret addObject:NSAccessibilityContentsAttribute];
3391 [ret addObjectsFromArray:[super accessibilityAttributeNames]];
3395 - (id)accessibilityHitTest:(NSPoint)point {
3396 if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
3398 NSPoint pointInWindow = [[self window] convertScreenToBase:point];
3399 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
3400 localPoint.y = NSHeight([self bounds]) - localPoint.y;
3401 BrowserAccessibilityCocoa* root = renderWidgetHostView_->
3402 GetBrowserAccessibilityManager()->
3403 GetRoot()->ToBrowserAccessibilityCocoa();
3404 id obj = [root accessibilityHitTest:localPoint];
3408 - (BOOL)accessibilityIsIgnored {
3409 return !renderWidgetHostView_->GetBrowserAccessibilityManager();
3412 - (NSUInteger)accessibilityGetIndexOf:(id)child {
3413 BrowserAccessibilityManager* manager =
3414 renderWidgetHostView_->GetBrowserAccessibilityManager();
3415 // Only child is root.
3417 manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
3424 - (id)accessibilityFocusedUIElement {
3425 BrowserAccessibilityManager* manager =
3426 renderWidgetHostView_->GetBrowserAccessibilityManager();
3428 BrowserAccessibility* focused_item = manager->GetFocus(NULL);
3429 DCHECK(focused_item);
3431 BrowserAccessibilityCocoa* focused_item_cocoa =
3432 focused_item->ToBrowserAccessibilityCocoa();
3433 DCHECK(focused_item_cocoa);
3434 if (focused_item_cocoa)
3435 return focused_item_cocoa;
3438 return [super accessibilityFocusedUIElement];
3441 - (void)doDefaultAction:(int32)accessibilityObjectId {
3442 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3443 rwh->Send(new AccessibilityMsg_DoDefaultAction(
3444 rwh->GetRoutingID(), accessibilityObjectId));
3447 // VoiceOver uses this method to move the caret to the beginning of the next
3448 // word in a text field.
3449 - (void)accessibilitySetTextSelection:(int32)accId
3450 startOffset:(int32)startOffset
3451 endOffset:(int32)endOffset {
3452 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3453 rwh->AccessibilitySetTextSelection(accId, startOffset, endOffset);
3456 // Convert a web accessibility's location in web coordinates into a cocoa
3457 // screen coordinate.
3458 - (NSPoint)accessibilityPointInScreen:(NSPoint)origin
3460 origin.y = NSHeight([self bounds]) - origin.y;
3461 NSPoint originInWindow = [self convertPoint:origin toView:nil];
3462 NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow];
3463 originInScreen.y = originInScreen.y - size.height;
3464 return originInScreen;
3467 - (void)setAccessibilityFocus:(BOOL)focus
3468 accessibilityId:(int32)accessibilityObjectId {
3470 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3471 rwh->Send(new AccessibilityMsg_SetFocus(
3472 rwh->GetRoutingID(), accessibilityObjectId));
3474 // Immediately set the focused item even though we have not officially set
3475 // focus on it as VoiceOver expects to get the focused item after this
3477 BrowserAccessibilityManager* manager =
3478 renderWidgetHostView_->GetBrowserAccessibilityManager();
3479 manager->SetFocus(manager->GetFromRendererID(accessibilityObjectId), false);
3483 - (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
3484 // Performs a right click copying WebKit's
3485 // accessibilityPerformShowMenuAction.
3486 NSPoint origin = [accessibility origin];
3487 NSSize size = [[accessibility size] sizeValue];
3488 NSPoint location = [self accessibilityPointInScreen:origin size:size];
3489 location = [[self window] convertScreenToBase:location];
3490 location.x += size.width/2;
3491 location.y += size.height/2;
3493 NSEvent* fakeRightClick = [NSEvent
3494 mouseEventWithType:NSRightMouseDown
3498 windowNumber:[[self window] windowNumber]
3499 context:[NSGraphicsContext currentContext]
3504 [self mouseEvent:fakeRightClick];
3507 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
3508 // with minor modifications for code style and commenting.
3510 // The 'public' interface is -setToolTipAtMousePoint:. This differs from
3511 // -setToolTip: in that the updated tooltip takes effect immediately,
3512 // without the user's having to move the mouse out of and back into the view.
3514 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
3515 // the view, which in turn requires overriding some internal tracking-rect
3516 // methods (to keep track of its owner & userdata, which need to be filled out
3517 // in the fake events.) --snej 7/6/09
3521 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3522 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
3524 * Redistribution and use in source and binary forms, with or without
3525 * modification, are permitted provided that the following conditions
3528 * 1. Redistributions of source code must retain the above copyright
3529 * notice, this list of conditions and the following disclaimer.
3530 * 2. Redistributions in binary form must reproduce the above copyright
3531 * notice, this list of conditions and the following disclaimer in the
3532 * documentation and/or other materials provided with the distribution.
3533 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
3534 * its contributors may be used to endorse or promote products derived
3535 * from this software without specific prior written permission.
3537 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
3538 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3539 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3540 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
3541 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3542 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
3543 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3544 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3545 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3546 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3549 // Any non-zero value will do, but using something recognizable might help us
3551 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
3553 // Override of a public NSView method, replacing the inherited functionality.
3554 // See above for rationale.
3555 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
3557 userData:(void *)data
3558 assumeInside:(BOOL)assumeInside {
3559 DCHECK(trackingRectOwner_ == nil);
3560 trackingRectOwner_ = owner;
3561 trackingRectUserData_ = data;
3562 return kTrackingRectTag;
3565 // Override of (apparently) a private NSView method(!) See above for rationale.
3566 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
3568 userData:(void *)data
3569 assumeInside:(BOOL)assumeInside
3570 useTrackingNum:(int)tag {
3571 DCHECK(tag == 0 || tag == kTrackingRectTag);
3572 DCHECK(trackingRectOwner_ == nil);
3573 trackingRectOwner_ = owner;
3574 trackingRectUserData_ = data;
3575 return kTrackingRectTag;
3578 // Override of (apparently) a private NSView method(!) See above for rationale.
3579 - (void)_addTrackingRects:(NSRect *)rects
3581 userDataList:(void **)userDataList
3582 assumeInsideList:(BOOL *)assumeInsideList
3583 trackingNums:(NSTrackingRectTag *)trackingNums
3586 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3587 DCHECK(trackingRectOwner_ == nil);
3588 trackingRectOwner_ = owner;
3589 trackingRectUserData_ = userDataList[0];
3590 trackingNums[0] = kTrackingRectTag;
3593 // Override of a public NSView method, replacing the inherited functionality.
3594 // See above for rationale.
3595 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3599 if (tag == kTrackingRectTag) {
3600 trackingRectOwner_ = nil;
3604 if (tag == lastToolTipTag_) {
3605 [super removeTrackingRect:tag];
3606 lastToolTipTag_ = 0;
3610 // If any other tracking rect is being removed, we don't know how it was
3611 // created and it's possible there's a leak involved (see Radar 3500217).
3615 // Override of (apparently) a private NSView method(!)
3616 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3617 for (int i = 0; i < count; ++i) {
3621 DCHECK(tag == kTrackingRectTag);
3622 trackingRectOwner_ = nil;
3626 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3627 - (void)_sendToolTipMouseExited {
3628 // Nothing matters except window, trackingNumber, and userData.
3629 int windowNumber = [[self window] windowNumber];
3630 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3631 location:NSZeroPoint
3634 windowNumber:windowNumber
3637 trackingNumber:kTrackingRectTag
3638 userData:trackingRectUserData_];
3639 [trackingRectOwner_ mouseExited:fakeEvent];
3642 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3643 - (void)_sendToolTipMouseEntered {
3644 // Nothing matters except window, trackingNumber, and userData.
3645 int windowNumber = [[self window] windowNumber];
3646 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3647 location:NSZeroPoint
3650 windowNumber:windowNumber
3653 trackingNumber:kTrackingRectTag
3654 userData:trackingRectUserData_];
3655 [trackingRectOwner_ mouseEntered:fakeEvent];
3658 // Sets the view's current tooltip, to be displayed at the current mouse
3659 // location. (This does not make the tooltip appear -- as usual, it only
3660 // appears after a delay.) Pass null to remove the tooltip.
3661 - (void)setToolTipAtMousePoint:(NSString *)string {
3662 NSString *toolTip = [string length] == 0 ? nil : string;
3663 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3664 (!toolTip && !toolTip_)) {
3669 [self _sendToolTipMouseExited];
3672 toolTip_.reset([toolTip copy]);
3675 // See radar 3500217 for why we remove all tooltips
3676 // rather than just the single one we created.
3677 [self removeAllToolTips];
3678 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3679 lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3682 [self _sendToolTipMouseEntered];
3686 // NSView calls this to get the text when displaying the tooltip.
3687 - (NSString *)view:(NSView *)view
3688 stringForToolTip:(NSToolTipTag)tag
3689 point:(NSPoint)point
3690 userData:(void *)data {
3691 return [[toolTip_ copy] autorelease];
3694 // Below is our NSTextInputClient implementation.
3696 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3697 // functions to process this event.
3699 // [WebHTMLView keyDown] ->
3700 // EventHandler::keyEvent() ->
3702 // [WebEditorClient handleKeyboardEvent] ->
3703 // [WebHTMLView _interceptEditingKeyEvent] ->
3704 // [NSResponder interpretKeyEvents] ->
3705 // [WebHTMLView insertText] ->
3706 // Editor::insertText()
3708 // Unfortunately, it is hard for Chromium to use this implementation because
3709 // it causes key-typing jank.
3710 // RenderWidgetHostViewMac is running in a browser process. On the other
3711 // hand, Editor and EventHandler are running in a renderer process.
3712 // So, if we used this implementation, a NSKeyDown event is dispatched to
3713 // the following functions of Chromium.
3715 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3716 // |Sync IPC (KeyDown)| (*1) ->
3717 // EventHandler::keyEvent() (renderer) ->
3719 // EditorClientImpl::handleKeyboardEvent() (renderer) ->
3720 // |Sync IPC| (*2) ->
3721 // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3722 // [self interpretKeyEvents] ->
3723 // [RenderWidgetHostViewMac insertText] (browser) ->
3725 // Editor::insertText() (renderer)
3727 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3728 // result of EventHandler::keyEvent().
3729 // (*2) we need to wait until this call finishes since WebEditorClient uses
3730 // the result of [WebHTMLView _interceptEditingKeyEvent].
3732 // This needs many sync IPC messages sent between a browser and a renderer for
3733 // each key event, which would probably result in key-typing jank.
3734 // To avoid this problem, this implementation processes key events (and input
3735 // method events) totally in a browser process and sends asynchronous input
3736 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3737 // renderer process.
3739 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3740 // |Async IPC (RawKeyDown)| ->
3741 // [self interpretKeyEvents] ->
3742 // [RenderWidgetHostViewMac insertText] (browser) ->
3743 // |Async IPC (Char)| ->
3744 // Editor::insertText() (renderer)
3746 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3747 // make any key-typing jank. --hbono 7/23/09
3750 extern NSString *NSTextInputReplacementRangeAttributeName;
3753 - (NSArray *)validAttributesForMarkedText {
3754 // This code is just copied from WebKit except renaming variables.
3755 if (!validAttributesForMarkedText_) {
3756 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3757 NSUnderlineStyleAttributeName,
3758 NSUnderlineColorAttributeName,
3759 NSMarkedClauseSegmentAttributeName,
3760 NSTextInputReplacementRangeAttributeName,
3763 return validAttributesForMarkedText_.get();
3766 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3767 DCHECK([self window]);
3768 // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3769 // coordinates (upper left origin). Scroll offsets will be taken care of in
3771 thePoint = [[self window] convertScreenToBase:thePoint];
3772 thePoint = [self convertPoint:thePoint fromView:nil];
3773 thePoint.y = NSHeight([self frame]) - thePoint.y;
3776 TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3777 renderWidgetHostView_->render_widget_host_,
3778 gfx::Point(thePoint.x, thePoint.y));
3782 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3783 actualRange:(NSRangePointer)actualRange {
3785 if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3789 rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3790 renderWidgetHostView_->render_widget_host_, theRange);
3792 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3794 *actualRange = theRange;
3797 // The returned rectangle is in WebKit coordinates (upper left origin), so
3798 // flip the coordinate system.
3799 NSRect viewFrame = [self frame];
3800 rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3804 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3805 actualRange:(NSRangePointer)actualRange {
3806 NSRect rect = [self firstViewRectForCharacterRange:theRange
3807 actualRange:actualRange];
3809 // Convert into screen coordinates for return.
3810 rect = [self convertRect:rect toView:nil];
3811 rect.origin = [[self window] convertBaseToScreen:rect.origin];
3815 - (NSRange)markedRange {
3816 // An input method calls this method to check if an application really has
3817 // a text being composed when hasMarkedText call returns true.
3818 // Returns the range saved in the setMarkedText method so the input method
3819 // calls the setMarkedText method and we can update the composition node
3820 // there. (When this method returns an empty range, the input method doesn't
3821 // call the setMarkedText method.)
3822 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3825 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3826 actualRange:(NSRangePointer)actualRange {
3827 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3829 *actualRange = range;
3830 NSAttributedString* str =
3831 TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3832 renderWidgetHostView_->render_widget_host_, range);
3836 - (NSInteger)conversationIdentifier {
3837 return reinterpret_cast<NSInteger>(self);
3840 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3841 // nil when the caret is in non-editable content or password box to avoid
3842 // making input methods do their work.
3843 - (NSTextInputContext *)inputContext {
3844 if (focusedPluginIdentifier_ != -1)
3845 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3847 switch(renderWidgetHostView_->text_input_type_) {
3848 case ui::TEXT_INPUT_TYPE_NONE:
3849 case ui::TEXT_INPUT_TYPE_PASSWORD:
3852 return [super inputContext];
3856 - (BOOL)hasMarkedText {
3857 // An input method calls this function to figure out whether or not an
3858 // application is really composing a text. If it is composing, it calls
3859 // the markedRange method, and maybe calls the setMarkedText method.
3860 // It seems an input method usually calls this function when it is about to
3861 // cancel an ongoing composition. If an application has a non-empty marked
3862 // range, it calls the setMarkedText method to delete the range.
3863 return hasMarkedText_;
3866 - (void)unmarkText {
3867 // Delete the composition node of the renderer and finish an ongoing
3869 // It seems an input method calls the setMarkedText method and set an empty
3870 // text when it cancels an ongoing composition, i.e. I have never seen an
3871 // input method calls this method.
3872 hasMarkedText_ = NO;
3873 markedText_.clear();
3874 underlines_.clear();
3876 // If we are handling a key down event, then ConfirmComposition() will be
3877 // called in keyEvent: method.
3878 if (!handlingKeyDown_) {
3879 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3880 base::string16(), gfx::Range::InvalidRange(), false);
3882 unmarkTextCalled_ = YES;
3886 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3887 replacementRange:(NSRange)replacementRange {
3888 // An input method updates the composition string.
3889 // We send the given text and range to the renderer so it can update the
3890 // composition node of WebKit.
3891 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3892 // the full web content.
3893 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3894 NSString* im_text = isAttributedString ? [string string] : string;
3895 int length = [im_text length];
3897 // |markedRange_| will get set on a callback from ImeSetComposition().
3898 selectedRange_ = newSelRange;
3899 markedText_ = base::SysNSStringToUTF16(im_text);
3900 hasMarkedText_ = (length > 0);
3902 underlines_.clear();
3903 if (isAttributedString) {
3904 ExtractUnderlines(string, &underlines_);
3906 // Use a thin black underline by default.
3907 underlines_.push_back(
3908 blink::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
3911 // If we are handling a key down event, then SetComposition() will be
3912 // called in keyEvent: method.
3913 // Input methods of Mac use setMarkedText calls with an empty text to cancel
3914 // an ongoing composition. So, we should check whether or not the given text
3915 // is empty to update the input method state. (Our input method backend can
3916 // automatically cancels an ongoing composition when we send an empty text.
3917 // So, it is OK to send an empty text to the renderer.)
3918 if (!handlingKeyDown_) {
3919 renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3920 markedText_, underlines_,
3921 newSelRange.location, NSMaxRange(newSelRange));
3925 - (void)doCommandBySelector:(SEL)selector {
3926 // An input method calls this function to dispatch an editing command to be
3927 // handled by this view.
3928 if (selector == @selector(noop:))
3931 std::string command(
3932 [RenderWidgetHostViewMacEditCommandHelper::
3933 CommandNameForSelector(selector) UTF8String]);
3935 // If this method is called when handling a key down event, then we need to
3936 // handle the command in the key event handler. Otherwise we can just handle
3938 if (handlingKeyDown_) {
3939 hasEditCommands_ = YES;
3940 // We ignore commands that insert characters, because this was causing
3941 // strange behavior (e.g. tab always inserted a tab rather than moving to
3942 // the next field on the page).
3943 if (!StartsWithASCII(command, "insert", false))
3944 editCommands_.push_back(EditCommand(command, ""));
3946 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3947 rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
3952 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
3953 // An input method has characters to be inserted.
3954 // Same as Linux, Mac calls this method not only:
3955 // * when an input method finishs composing text, but also;
3956 // * when we type an ASCII character (without using input methods).
3957 // When we aren't using input methods, we should send the given character as
3958 // a Char event so it is dispatched to an onkeypress() event handler of
3960 // On the other hand, when we are using input methods, we should send the
3961 // given characters as an input method event and prevent the characters from
3962 // being dispatched to onkeypress() event handlers.
3963 // Text inserting might be initiated by other source instead of keyboard
3964 // events, such as the Characters dialog. In this case the text should be
3965 // sent as an input method event as well.
3966 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3967 // the full web content.
3968 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3969 NSString* im_text = isAttributedString ? [string string] : string;
3970 if (handlingKeyDown_) {
3971 textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
3973 gfx::Range replacement_range(replacementRange);
3974 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3975 base::SysNSStringToUTF16(im_text), replacement_range, false);
3978 // Inserting text will delete all marked text automatically.
3979 hasMarkedText_ = NO;
3982 - (void)insertText:(id)string {
3983 [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
3986 - (void)viewDidMoveToWindow {
3988 [self updateScreenProperties];
3990 if (canBeKeyView_) {
3991 NSWindow* newWindow = [self window];
3992 // Pointer comparison only, since we don't know if lastWindow_ is still
3995 // If we move into a new window, refresh the frame information. We
3996 // don't need to do it if it was the same window as it used to be in,
3997 // since that case is covered by WasShown(). We only want to do this for
3998 // real browser views, not popups.
3999 if (newWindow != lastWindow_) {
4000 lastWindow_ = newWindow;
4001 renderWidgetHostView_->WindowFrameChanged();
4006 // If we switch windows (or are removed from the view hierarchy), cancel any
4007 // open mouse-downs.
4008 if (hasOpenMouseDown_) {
4009 WebMouseEvent event;
4010 event.type = WebInputEvent::MouseUp;
4011 event.button = WebMouseEvent::ButtonLeft;
4012 renderWidgetHostView_->ForwardMouseEvent(event);
4014 hasOpenMouseDown_ = NO;
4018 - (void)undo:(id)sender {
4019 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4024 - (void)redo:(id)sender {
4025 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4030 - (void)cut:(id)sender {
4031 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4036 - (void)copy:(id)sender {
4037 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4042 - (void)copyToFindPboard:(id)sender {
4043 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4045 host->CopyToFindPboard();
4048 - (void)paste:(id)sender {
4049 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4054 - (void)pasteAndMatchStyle:(id)sender {
4055 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4057 host->PasteAndMatchStyle();
4060 - (void)selectAll:(id)sender {
4061 // editCommand_helper_ adds implementations for most NSResponder methods
4062 // dynamically. But the renderer side only sends selection results back to
4063 // the browser if they were triggered by a keyboard event or went through
4064 // one of the Select methods on RWH. Since selectAll: is called from the
4065 // menu handler, neither is true.
4066 // Explicitly call SelectAll() here to make sure the renderer returns
4067 // selection results.
4068 RenderFrameHost* host = renderWidgetHostView_->GetFocusedFrame();
4073 - (void)startSpeaking:(id)sender {
4074 renderWidgetHostView_->SpeakSelection();
4077 - (void)stopSpeaking:(id)sender {
4078 renderWidgetHostView_->StopSpeaking();
4081 - (void)cancelComposition {
4082 if (!hasMarkedText_)
4085 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
4086 // doesn't call any NSTextInput functions, such as setMarkedText or
4087 // insertText. So, we need to send an IPC message to a renderer so it can
4088 // delete the composition node.
4089 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
4090 [currentInputManager markedTextAbandoned:self];
4092 hasMarkedText_ = NO;
4093 // Should not call [self unmarkText] here, because it'll send unnecessary
4094 // cancel composition IPC message to the renderer.
4097 - (void)confirmComposition {
4098 if (!hasMarkedText_)
4101 if (renderWidgetHostView_->render_widget_host_)
4102 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
4103 base::string16(), gfx::Range::InvalidRange(), false);
4105 [self cancelComposition];
4108 - (void)setPluginImeActive:(BOOL)active {
4109 if (active == pluginImeActive_)
4112 pluginImeActive_ = active;
4114 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
4115 renderWidgetHostView_->PluginImeCompositionCompleted(
4116 base::string16(), focusedPluginIdentifier_);
4120 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
4122 focusedPluginIdentifier_ = pluginId;
4123 else if (focusedPluginIdentifier_ == pluginId)
4124 focusedPluginIdentifier_ = -1;
4126 // Whenever plugin focus changes, plugin IME resets.
4127 [self setPluginImeActive:NO];
4130 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
4131 if (!pluginImeActive_)
4134 ComplexTextInputPanel* inputPanel =
4135 [ComplexTextInputPanel sharedComplexTextInputPanel];
4136 NSString* composited_string = nil;
4137 BOOL handled = [inputPanel interpretKeyEvent:event
4138 string:&composited_string];
4139 if (composited_string) {
4140 renderWidgetHostView_->PluginImeCompositionCompleted(
4141 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
4142 pluginImeActive_ = NO;
4147 - (void)checkForPluginImeCancellation {
4148 if (pluginImeActive_ &&
4149 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
4150 renderWidgetHostView_->PluginImeCompositionCompleted(
4151 base::string16(), focusedPluginIdentifier_);
4152 pluginImeActive_ = NO;
4156 // Overriding a NSResponder method to support application services.
4158 - (id)validRequestorForSendType:(NSString*)sendType
4159 returnType:(NSString*)returnType {
4161 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
4162 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
4163 BOOL hasText = !renderWidgetHostView_->selected_text().empty();
4165 renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
4167 if (sendTypeIsString && hasText && !returnType) {
4169 } else if (!sendType && returnTypeIsString && takesText) {
4171 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
4174 requestor = [super validRequestorForSendType:sendType
4175 returnType:returnType];
4180 - (void)viewWillStartLiveResize {
4181 [super viewWillStartLiveResize];
4182 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
4184 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
4187 - (void)viewDidEndLiveResize {
4188 [super viewDidEndLiveResize];
4189 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
4191 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
4194 - (void)updateCursor:(NSCursor*)cursor {
4195 if (currentCursor_ == cursor)
4198 currentCursor_.reset([cursor retain]);
4199 [[self window] invalidateCursorRectsForView:self];
4202 - (void)popupWindowWillClose:(NSNotification *)notification {
4203 renderWidgetHostView_->KillSelf();
4209 // Supporting application services
4211 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
4213 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
4214 types:(NSArray*)types {
4215 const std::string& str = renderWidgetHostView_->selected_text();
4216 if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
4218 base::scoped_nsobject<NSString> text(
4219 [[NSString alloc] initWithUTF8String:str.c_str()]);
4220 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
4221 [pboard declareTypes:toDeclare owner:nil];
4222 return [pboard setString:text forType:NSStringPboardType];
4225 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
4226 NSString *string = [pboard stringForType:NSStringPboardType];
4227 if (!string) return NO;
4229 // If the user is currently using an IME, confirm the IME input,
4230 // and then insert the text from the service, the same as TextEdit and Safari.
4231 [self confirmComposition];
4232 [self insertText:string];
4237 if (renderWidgetHostView_->use_core_animation_)
4239 return [super isOpaque];
4242 // "-webkit-app-region: drag | no-drag" is implemented on Mac by excluding
4243 // regions that are not draggable. (See ControlRegionView in
4244 // native_app_window_cocoa.mm). This requires the render host view to be
4245 // draggable by default.
4246 - (BOOL)mouseDownCanMoveWindow {
4252 @implementation SoftwareLayer
4254 - (id)initWithRenderWidgetHostViewMac:(content::RenderWidgetHostViewMac*)r {
4255 if (self = [super init]) {
4256 renderWidgetHostView_ = r;
4258 [self setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
4259 [self setAnchorPoint:CGPointMake(0, 0)];
4260 // Setting contents gravity is necessary to prevent the layer from being
4261 // scaled during dyanmic resizes (especially with devtools open).
4262 [self setContentsGravity:kCAGravityTopLeft];
4263 if (renderWidgetHostView_->software_frame_manager_->HasCurrentFrame() &&
4264 [self respondsToSelector:(@selector(setContentsScale:))]) {
4265 [self setContentsScale:renderWidgetHostView_->software_frame_manager_->
4266 GetCurrentFrameDeviceScaleFactor()];
4269 // Ensure that the transition between frames not be animated.
4270 [self setActions:@{ @"contents" : [NSNull null] }];
4275 - (void)drawInContext:(CGContextRef)context {
4276 TRACE_EVENT0("browser", "SoftwareLayer::drawInContext");
4278 CGRect clipRect = CGContextGetClipBoundingBox(context);
4279 if (renderWidgetHostView_) {
4280 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
4281 renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
4282 [renderWidgetHostView_->cocoa_view() drawBackingStore:backingStore
4286 CGContextSetFillColorWithColor(context,
4287 CGColorGetConstantColor(kCGColorWhite));
4288 CGContextFillRect(context, clipRect);
4292 - (void)disableRendering {
4293 // Disable the fade-out animation as the layer is removed.
4294 ScopedCAActionDisabler disabler;
4295 [self removeFromSuperlayer];
4296 renderWidgetHostView_ = nil;
4299 @end // implementation SoftwareLayer