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 #include <QuartzCore/QuartzCore.h>
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/debug/crash_logging.h"
13 #include "base/debug/trace_event.h"
14 #include "base/logging.h"
15 #include "base/mac/mac_util.h"
16 #include "base/mac/scoped_cftyperef.h"
17 #import "base/mac/scoped_nsobject.h"
18 #include "base/mac/sdk_forward_declarations.h"
19 #include "base/message_loop.h"
20 #include "base/metrics/histogram.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/sys_string_conversions.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/sys_info.h"
26 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
27 #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
28 #include "content/browser/renderer_host/backing_store_mac.h"
29 #include "content/browser/renderer_host/backing_store_manager.h"
30 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
31 #include "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
32 #include "content/browser/renderer_host/compositing_iosurface_mac.h"
33 #include "content/browser/renderer_host/render_view_host_impl.h"
34 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
35 #import "content/browser/renderer_host/text_input_client_mac.h"
36 #include "content/common/accessibility_messages.h"
37 #include "content/common/edit_command.h"
38 #include "content/common/gpu/gpu_messages.h"
39 #include "content/common/input_messages.h"
40 #include "content/common/view_messages.h"
41 #include "content/port/browser/render_widget_host_view_frame_subscriber.h"
42 #include "content/public/browser/browser_thread.h"
43 #include "content/public/browser/native_web_keyboard_event.h"
44 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
45 #include "content/public/common/content_switches.h"
46 #include "skia/ext/platform_canvas.h"
47 #include "third_party/WebKit/public/web/WebInputEvent.h"
48 #include "third_party/WebKit/public/web/WebScreenInfo.h"
49 #include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
50 #import "third_party/mozilla/ComplexTextInputPanel.h"
51 #include "ui/base/cocoa/animation_utils.h"
52 #import "ui/base/cocoa/fullscreen_window_manager.h"
53 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
54 #include "ui/base/keycodes/keyboard_codes.h"
55 #include "ui/base/layout.h"
56 #include "ui/gfx/display.h"
57 #include "ui/gfx/point.h"
58 #include "ui/gfx/rect_conversions.h"
59 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
60 #include "ui/gfx/screen.h"
61 #include "ui/gfx/size_conversions.h"
62 #include "ui/gl/io_surface_support_mac.h"
63 #include "webkit/plugins/npapi/webplugin.h"
65 using content::BackingStoreMac;
66 using content::BrowserAccessibility;
67 using content::BrowserAccessibilityManager;
68 using content::EditCommand;
69 using content::NativeWebKeyboardEvent;
70 using content::RenderViewHostImpl;
71 using content::RenderWidgetHostImpl;
72 using content::RenderWidgetHostViewMac;
73 using content::RenderWidgetHostViewMacEditCommandHelper;
74 using content::TextInputClientMac;
75 using WebKit::WebInputEvent;
76 using WebKit::WebInputEventFactory;
77 using WebKit::WebMouseEvent;
78 using WebKit::WebMouseWheelEvent;
80 enum CoreAnimationStatus {
81 CORE_ANIMATION_DISABLED,
82 CORE_ANIMATION_ENABLED_LAZY,
83 CORE_ANIMATION_ENABLED_ALWAYS,
86 static CoreAnimationStatus GetCoreAnimationStatus() {
87 if (!CommandLine::ForCurrentProcess()->HasSwitch(
88 switches::kUseCoreAnimation))
89 return CORE_ANIMATION_DISABLED;
90 if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
91 switches::kUseCoreAnimation) == "lazy")
92 return CORE_ANIMATION_ENABLED_LAZY;
93 return CORE_ANIMATION_ENABLED_ALWAYS;
96 // These are not documented, so use only after checking -respondsToSelector:.
97 @interface NSApplication (UndocumentedSpeechMethods)
98 - (void)speakString:(NSString*)string;
99 - (void)stopSpeaking:(id)sender;
103 // Declare things that are part of the 10.7 SDK.
104 #if !defined(MAC_OS_X_VERSION_10_7) || \
105 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
106 @interface NSView (NSOpenGLSurfaceResolutionLionAPI)
107 - (void)setWantsBestResolutionOpenGLSurface:(BOOL)flag;
110 static NSString* const NSWindowDidChangeBackingPropertiesNotification =
111 @"NSWindowDidChangeBackingPropertiesNotification";
112 static NSString* const NSBackingPropertyOldScaleFactorKey =
113 @"NSBackingPropertyOldScaleFactorKey";
114 // Note: Apple's example code (linked from the comment above
115 // -windowDidChangeBackingProperties:) uses
116 // @"NSWindowBackingPropertiesChangeOldBackingScaleFactorKey", but that always
117 // returns an old scale of 0. @"NSBackingPropertyOldScaleFactorKey" seems to
118 // work in practice, and it's what's used in Apple's WebKit port
119 // (WebKit/mac/WebView/WebView.mm).
123 static inline int ToWebKitModifiers(NSUInteger flags) {
125 if (flags & NSControlKeyMask) modifiers |= WebInputEvent::ControlKey;
126 if (flags & NSShiftKeyMask) modifiers |= WebInputEvent::ShiftKey;
127 if (flags & NSAlternateKeyMask) modifiers |= WebInputEvent::AltKey;
128 if (flags & NSCommandKeyMask) modifiers |= WebInputEvent::MetaKey;
132 static float ScaleFactor(NSView* view) {
133 return ui::GetScaleFactorScale(ui::GetScaleFactorForNativeView(view));
137 @interface RenderWidgetHostViewCocoa ()
138 @property(nonatomic, assign) NSRange selectedRange;
139 @property(nonatomic, assign) NSRange markedRange;
140 @property(nonatomic, assign)
141 NSObject<RenderWidgetHostViewMacDelegate>* delegate;
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)updateSoftwareLayerScaleFactor;
155 - (void)checkForPluginImeCancellation;
156 - (void)updateTabBackingStoreScaleFactor;
157 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
158 actualRange:(NSRangePointer)actualRange;
161 // NSEvent subtype for scroll gestures events.
162 static const short kIOHIDEventTypeScroll = 6;
164 // A window subclass that allows the fullscreen window to become main and gain
165 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
166 // handled by the browser.
167 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
170 @implementation PepperFlashFullscreenWindow
172 - (BOOL)canBecomeKeyWindow {
176 - (BOOL)canBecomeMainWindow {
182 @interface RenderWidgetPopupWindow : NSWindow {
183 // The event tap that allows monitoring of all events, to properly close with
184 // a click outside the bounds of the window.
189 @implementation RenderWidgetPopupWindow
191 - (id)initWithContentRect:(NSRect)contentRect
192 styleMask:(NSUInteger)windowStyle
193 backing:(NSBackingStoreType)bufferingType
194 defer:(BOOL)deferCreation {
195 if (self = [super initWithContentRect:contentRect
196 styleMask:windowStyle
197 backing:bufferingType
198 defer:deferCreation]) {
200 [self setBackgroundColor:[NSColor clearColor]];
201 [self startObservingClicks];
207 [self stopObservingClicks];
211 // Gets called when the menubar is clicked.
212 // Needed because the local event monitor doesn't see the click on the menubar.
213 - (void)beganTracking:(NSNotification*)notification {
217 // Install the callback.
218 - (void)startObservingClicks {
219 clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
220 handler:^NSEvent* (NSEvent* event) {
221 if ([event window] == self)
223 NSEventType eventType = [event type];
224 if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
229 NSNotificationCenter* notificationCenter =
230 [NSNotificationCenter defaultCenter];
231 [notificationCenter addObserver:self
232 selector:@selector(beganTracking:)
233 name:NSMenuDidBeginTrackingNotification
234 object:[NSApp mainMenu]];
237 // Remove the callback.
238 - (void)stopObservingClicks {
242 [NSEvent removeMonitor:clickEventTap_];
243 clickEventTap_ = nil;
245 NSNotificationCenter* notificationCenter =
246 [NSNotificationCenter defaultCenter];
247 [notificationCenter removeObserver:self
248 name:NSMenuDidBeginTrackingNotification
249 object:[NSApp mainMenu]];
256 // Maximum number of characters we allow in a tooltip.
257 const size_t kMaxTooltipLength = 1024;
259 // TODO(suzhe): Upstream this function.
260 WebKit::WebColor WebColorFromNSColor(NSColor *color) {
262 [color getRed:&r green:&g blue:&b alpha:&a];
265 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
266 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
267 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
268 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
271 // Extract underline information from an attributed string. Mostly copied from
272 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
273 void ExtractUnderlines(
274 NSAttributedString* string,
275 std::vector<WebKit::WebCompositionUnderline>* underlines) {
276 int length = [[string string] length];
280 NSDictionary* attrs = [string attributesAtIndex:i
281 longestEffectiveRange:&range
282 inRange:NSMakeRange(i, length - i)];
283 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
284 WebKit::WebColor color = SK_ColorBLACK;
285 if (NSColor *colorAttr =
286 [attrs objectForKey:NSUnderlineColorAttributeName]) {
287 color = WebColorFromNSColor(
288 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
290 underlines->push_back(WebKit::WebCompositionUnderline(
291 range.location, NSMaxRange(range), color, [style intValue] > 1));
293 i = range.location + range.length;
297 // EnablePasswordInput() and DisablePasswordInput() are copied from
298 // enableSecureTextInput() and disableSecureTextInput() functions in
299 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
300 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
301 // here, because they are already called in webkit and they are system wide
303 void EnablePasswordInput() {
304 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
305 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
306 sizeof(CFArrayRef), &inputSources);
307 CFRelease(inputSources);
310 void DisablePasswordInput() {
311 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
314 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
315 // left of the primary screen (Carbon coordinates), and stuffs it into a
317 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
318 gfx::Rect new_rect(NSRectToCGRect(rect));
319 if ([[NSScreen screens] count] > 0) {
320 new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height -
321 new_rect.y() - new_rect.height());
326 // Returns the window that visually contains the given view. This is different
327 // from [view window] in the case of tab dragging, where the view's owning
328 // window is a floating panel attached to the actual browser window that the tab
329 // is visually part of.
330 NSWindow* ApparentWindowForView(NSView* view) {
331 // TODO(shess): In case of !window, the view has been removed from
332 // the view hierarchy because the tab isn't main. Could retrieve
333 // the information from the main tab for our window.
334 NSWindow* enclosing_window = [view window];
336 // See if this is a tab drag window. The width check is to distinguish that
337 // case from extension popup windows.
338 NSWindow* ancestor_window = [enclosing_window parentWindow];
339 if (ancestor_window && (NSWidth([enclosing_window frame]) ==
340 NSWidth([ancestor_window frame]))) {
341 enclosing_window = ancestor_window;
344 return enclosing_window;
347 WebKit::WebScreenInfo GetWebScreenInfo(NSView* view) {
348 gfx::Display display =
349 gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
351 NSScreen* screen = [NSScreen deepestScreen];
353 WebKit::WebScreenInfo results;
355 results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
356 results.depth = NSBitsPerPixelFromDepth([screen depth]);
357 results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
358 results.isMonochrome =
359 [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
360 results.rect = display.bounds();
361 results.availableRect = display.work_area();
369 ///////////////////////////////////////////////////////////////////////////////
370 // RenderWidgetHostView, public:
373 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
374 RenderWidgetHost* widget) {
375 return new RenderWidgetHostViewMac(widget);
379 void RenderWidgetHostViewPort::GetDefaultScreenInfo(
380 WebKit::WebScreenInfo* results) {
381 *results = GetWebScreenInfo(NULL);
384 ///////////////////////////////////////////////////////////////////////////////
385 // RenderWidgetHostViewMac, public:
387 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
388 : render_widget_host_(RenderWidgetHostImpl::From(widget)),
389 about_to_validate_and_paint_(false),
390 call_set_needs_display_in_rect_pending_(false),
391 last_frame_was_accelerated_(false),
392 text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
393 can_compose_inline_(true),
394 allow_overlapping_views_(false),
395 use_core_animation_(false),
399 fullscreen_parent_host_view_(NULL),
400 pending_swap_buffers_acks_weak_factory_(this),
401 next_swap_ack_time_(base::Time::Now()) {
402 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
403 // goes away. Since we autorelease it, our caller must put
404 // |GetNativeView()| into the view hierarchy right after calling us.
405 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
406 initWithRenderWidgetHostViewMac:this] autorelease];
408 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_ALWAYS) {
409 EnableCoreAnimation();
412 render_widget_host_->SetView(this);
415 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
416 // This is being called from |cocoa_view_|'s destructor, so invalidate the
420 AckPendingSwapBuffers();
423 // Make sure that the layer doesn't reach into the now-invalid object.
424 DestroyCompositedIOSurfaceAndLayer();
425 software_layer_.reset();
427 // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
428 // RenderWidgetHost does we need to tell it not to hold a stale pointer to
430 if (render_widget_host_)
431 render_widget_host_->SetView(NULL);
434 void RenderWidgetHostViewMac::SetDelegate(
435 NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
436 [cocoa_view_ setDelegate:delegate];
439 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
440 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY) {
442 ScopedCAActionDisabler disabler;
443 [[[cocoa_view_ window] contentView] setWantsLayer:YES];
444 EnableCoreAnimation();
449 if (allow_overlapping_views_ == overlapping)
451 allow_overlapping_views_ = overlapping;
452 [cocoa_view_ setNeedsDisplay:YES];
453 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
456 ///////////////////////////////////////////////////////////////////////////////
457 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
459 void RenderWidgetHostViewMac::EnableCoreAnimation() {
460 if (use_core_animation_)
463 use_core_animation_ = true;
465 software_layer_.reset([[CALayer alloc] init]);
466 if (!software_layer_)
467 LOG(ERROR) << "Failed to create CALayer for software rendering";
468 [software_layer_ setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
469 [software_layer_ setDelegate:cocoa_view_];
470 [software_layer_ setAutoresizingMask:kCALayerWidthSizable |
471 kCALayerHeightSizable];
472 [software_layer_ setContentsGravity:kCAGravityTopLeft];
473 [software_layer_ setFrame:NSRectToCGRect([cocoa_view_ bounds])];
474 [software_layer_ setNeedsDisplay];
475 [cocoa_view_ updateSoftwareLayerScaleFactor];
477 [cocoa_view_ setLayer:software_layer_];
478 [cocoa_view_ setWantsLayer:YES];
480 if (compositing_iosurface_) {
481 if (!CreateCompositedIOSurfaceAndLayer()) {
482 LOG(ERROR) << "Failed to create CALayer for existing IOSurface";
487 bool RenderWidgetHostViewMac::CreateCompositedIOSurfaceAndLayer() {
488 if (compositing_iosurface_layer_ &&
489 [compositing_iosurface_layer_ context] &&
490 compositing_iosurface_) {
494 ScopedCAActionDisabler disabler;
495 if (!compositing_iosurface_layer_) {
496 compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
497 initWithRenderWidgetHostViewMac:this]);
498 if (!compositing_iosurface_layer_) {
499 LOG(ERROR) << "Failed to create CALayer for IOSurface";
502 [cocoa_view_ setLayer:compositing_iosurface_layer_];
504 if (![compositing_iosurface_layer_ ensureContext]) {
505 LOG(ERROR) << "Failed to create context for IOSurface";
508 if (!compositing_iosurface_) {
509 compositing_iosurface_.reset(CompositingIOSurfaceMac::Create(
510 [compositing_iosurface_layer_ context]));
511 if (!compositing_iosurface_) {
512 LOG(ERROR) << "Failed to create CompositingIOSurface";
519 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer() {
520 ScopedCAActionDisabler disabler;
521 compositing_iosurface_.reset();
522 if (compositing_iosurface_layer_) {
524 DCHECK([[cocoa_view_ layer] isEqual:compositing_iosurface_layer_]);
525 [cocoa_view_ setLayer:software_layer_];
527 [compositing_iosurface_layer_ disableCompositing];
528 compositing_iosurface_layer_.reset();
532 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
534 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
535 IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
536 IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
537 IPC_MESSAGE_UNHANDLED(handled = false)
538 IPC_END_MESSAGE_MAP()
542 void RenderWidgetHostViewMac::InitAsChild(
543 gfx::NativeView parent_view) {
546 void RenderWidgetHostViewMac::InitAsPopup(
547 RenderWidgetHostView* parent_host_view,
548 const gfx::Rect& pos) {
549 bool activatable = popup_type_ == WebKit::WebPopupTypeNone;
550 [cocoa_view_ setCloseOnDeactivate:YES];
551 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
553 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
554 if ([[NSScreen screens] count] > 0) {
555 origin_global.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height -
556 pos.height() - origin_global.y;
559 popup_window_.reset([[RenderWidgetPopupWindow alloc]
560 initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
561 pos.width(), pos.height())
562 styleMask:NSBorderlessWindowMask
563 backing:NSBackingStoreBuffered
565 [popup_window_ setLevel:NSPopUpMenuWindowLevel];
566 [popup_window_ setReleasedWhenClosed:NO];
567 [popup_window_ makeKeyAndOrderFront:nil];
568 [[popup_window_ contentView] addSubview:cocoa_view_];
569 [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
570 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
571 [[NSNotificationCenter defaultCenter]
572 addObserver:cocoa_view_
573 selector:@selector(popupWindowWillClose:)
574 name:NSWindowWillCloseNotification
575 object:popup_window_];
578 // This function creates the fullscreen window and hides the dock and menubar if
579 // necessary. Note, this codepath is only used for pepper flash when
580 // pp::FlashFullScreen::SetFullscreen() is called. If
581 // pp::FullScreen::SetFullscreen() is called then the entire browser window
582 // will enter fullscreen instead.
583 void RenderWidgetHostViewMac::InitAsFullscreen(
584 RenderWidgetHostView* reference_host_view) {
585 fullscreen_parent_host_view_ =
586 static_cast<RenderWidgetHostViewMac*>(reference_host_view);
587 NSWindow* parent_window = nil;
588 if (reference_host_view)
589 parent_window = [reference_host_view->GetNativeView() window];
590 NSScreen* screen = [parent_window screen];
592 screen = [NSScreen mainScreen];
594 pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
595 initWithContentRect:[screen frame]
596 styleMask:NSBorderlessWindowMask
597 backing:NSBackingStoreBuffered
599 [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
600 [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
601 [cocoa_view_ setCanBeKeyView:YES];
602 [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
603 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
604 // If the pepper fullscreen window isn't opaque then there are performance
605 // issues when it's on the discrete GPU and the Chrome window is being drawn
606 // to. http://crbug.com/171911
607 [pepper_fullscreen_window_ setOpaque:YES];
609 // Note that this forms a reference cycle between the fullscreen window and
610 // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
611 // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
612 // This cycle is normally broken when -keyEvent: receives an <esc> key, which
613 // explicitly calls Shutdown on the render_widget_host_, which calls
614 // Destroy() on RWHVMac, which drops the reference to
615 // pepper_fullscreen_window_.
616 [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
618 // Note that this keeps another reference to pepper_fullscreen_window_.
619 fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
620 initWithWindow:pepper_fullscreen_window_.get()
621 desiredScreen:screen]);
622 [fullscreen_window_manager_ enterFullscreenMode];
623 [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
626 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
627 // See comment in InitAsFullscreen(): There is a reference cycle between
628 // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
629 // Tests that test pepper fullscreen mode without sending an <esc> event
630 // need to call this method to break the reference cycle.
631 [fullscreen_window_manager_ exitFullscreenMode];
632 fullscreen_window_manager_.reset();
633 [pepper_fullscreen_window_ close];
634 pepper_fullscreen_window_.reset();
637 int RenderWidgetHostViewMac::window_number() const {
638 NSWindow* window = [cocoa_view_ window];
641 return [window windowNumber];
644 float RenderWidgetHostViewMac::scale_factor() const {
645 return ScaleFactor(cocoa_view_);
648 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
649 return render_widget_host_;
652 void RenderWidgetHostViewMac::WasShown() {
656 if (web_contents_switch_paint_time_.is_null())
657 web_contents_switch_paint_time_ = base::TimeTicks::Now();
659 render_widget_host_->WasShown();
661 // We're messing with the window, so do this to ensure no flashes.
662 if (!use_core_animation_)
663 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
665 [compositing_iosurface_layer_ setNeedsDisplay];
668 void RenderWidgetHostViewMac::WasHidden() {
672 // Send ACKs for any pending SwapBuffers (if any) since we won't be displaying
673 // them and the GPU process is waiting.
674 AckPendingSwapBuffers();
676 // If we receive any more paint messages while we are hidden, we want to
677 // ignore them so we don't re-allocate the backing store. We will paint
678 // everything again when we become selected again.
681 // If we have a renderer, then inform it that we are being hidden so it can
682 // reduce its resource utilization.
683 render_widget_host_->WasHidden();
685 // There can be a transparent flash as this view is removed and the next is
686 // added, because of OSX windowing races between displaying the contents of
687 // the NSView and its corresponding OpenGL context.
688 // disableScreenUpdatesUntilFlush prevents the transparent flash by avoiding
689 // screen updates until the next tab draws.
690 if (!use_core_animation_)
691 [[cocoa_view_ window] disableScreenUpdatesUntilFlush];
693 web_contents_switch_paint_time_ = base::TimeTicks();
696 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
697 gfx::Rect rect = GetViewBounds();
702 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
703 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
704 // TODO(thakis): fix, http://crbug.com/73362
708 // During the initial creation of the RenderWidgetHostView in
709 // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
710 // an empty size. In the Windows code flow, it is not ignored because
711 // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
712 // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
713 // flags to keep things sized properly. On the other hand, if the size is not
714 // empty then this is a valid request for a pop-up.
715 if (rect.size().IsEmpty())
718 // Ignore the position of |rect| for non-popup rwhvs. This is because
719 // background tabs do not have a window, but the window is required for the
720 // coordinate conversions. Popups are always for a visible tab.
722 // The position of |rect| is screen coordinate system and we have to
723 // consider Cocoa coordinate system is upside-down and also multi-screen.
724 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
725 NSSize size = NSMakeSize(rect.width(), rect.height());
726 size = [cocoa_view_ convertSize:size toView:nil];
727 if ([[NSScreen screens] count] > 0) {
729 static_cast<NSScreen*>([[NSScreen screens] objectAtIndex:0]);
731 NSHeight([screen frame]) - size.height - origin_global.y;
733 [popup_window_ setFrame:NSMakeRect(origin_global.x, origin_global.y,
734 size.width, size.height)
737 DCHECK([[cocoa_view_ superview] isKindOfClass:[BaseView class]]);
738 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
739 gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
740 rect2.set_width(rect.width());
741 rect2.set_height(rect.height());
742 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
746 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
750 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
751 return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
754 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
756 return static_cast<gfx::NativeViewAccessible>(NULL);
759 void RenderWidgetHostViewMac::MovePluginWindows(
760 const gfx::Vector2d& scroll_offset,
761 const std::vector<webkit::npapi::WebPluginGeometry>& moves) {
762 // Must be overridden, but unused on this platform. Core Animation
763 // plugins are drawn by the GPU process (through the compositor),
764 // and Core Graphics plugins are drawn by the renderer process.
765 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
768 void RenderWidgetHostViewMac::Focus() {
769 [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
772 void RenderWidgetHostViewMac::Blur() {
774 [[cocoa_view_ window] makeFirstResponder:nil];
777 bool RenderWidgetHostViewMac::HasFocus() const {
778 return [[cocoa_view_ window] firstResponder] == cocoa_view_;
781 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
782 return !!render_widget_host_->GetBackingStore(false) ||
783 (compositing_iosurface_ && compositing_iosurface_->HasIOSurface());
786 void RenderWidgetHostViewMac::Show() {
787 [cocoa_view_ setHidden:NO];
792 void RenderWidgetHostViewMac::Hide() {
793 [cocoa_view_ setHidden:YES];
798 bool RenderWidgetHostViewMac::IsShowing() {
799 return ![cocoa_view_ isHidden];
802 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
803 NSRect bounds = [cocoa_view_ bounds];
804 // TODO(shess): In case of !window, the view has been removed from
805 // the view hierarchy because the tab isn't main. Could retrieve
806 // the information from the main tab for our window.
807 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
808 if (!enclosing_window)
809 return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
811 bounds = [cocoa_view_ convertRect:bounds toView:nil];
812 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
813 return FlipNSRectToRectScreen(bounds);
816 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
817 WebCursor web_cursor = cursor;
818 [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
821 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
822 is_loading_ = is_loading;
823 // If we ever decide to show the waiting cursor while the page is loading
824 // like Chrome does on Windows, call |UpdateCursor()| here.
827 void RenderWidgetHostViewMac::TextInputTypeChanged(ui::TextInputType type,
828 bool can_compose_inline) {
829 if (text_input_type_ != type
830 || can_compose_inline_ != can_compose_inline) {
831 text_input_type_ = type;
832 can_compose_inline_ = can_compose_inline;
834 SetTextInputActive(true);
836 // Let AppKit cache the new input context to make IMEs happy.
837 // See http://crbug.com/73039.
838 [NSApp updateWindows];
841 UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
847 void RenderWidgetHostViewMac::ImeCancelComposition() {
848 [cocoa_view_ cancelComposition];
851 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
852 const ui::Range& range,
853 const std::vector<gfx::Rect>& character_bounds) {
854 // The RangeChanged message is only sent with valid values. The current
855 // caret position (start == end) will be sent if there is no IME range.
856 [cocoa_view_ setMarkedRange:range.ToNSRange()];
857 composition_range_ = range;
858 composition_bounds_ = character_bounds;
861 void RenderWidgetHostViewMac::DidUpdateBackingStore(
862 const gfx::Rect& scroll_rect,
863 const gfx::Vector2d& scroll_delta,
864 const std::vector<gfx::Rect>& copy_rects,
865 const ui::LatencyInfo& latency_info) {
868 software_latency_info_.MergeWith(latency_info);
871 std::vector<gfx::Rect> rects(copy_rects);
873 // Because the findbar might be open, we cannot use scrollRect:by: here. For
874 // now, simply mark all of scroll rect as dirty.
875 if (!scroll_rect.IsEmpty())
876 rects.push_back(scroll_rect);
878 for (size_t i = 0; i < rects.size(); ++i) {
879 NSRect ns_rect = [cocoa_view_ flipRectToNSRect:rects[i]];
881 if (about_to_validate_and_paint_) {
882 // As much as we'd like to use -setNeedsDisplayInRect: here, we can't.
883 // We're in the middle of executing a -drawRect:, and as soon as it
884 // returns Cocoa will clear its record of what needs display. We
885 // instead use |performSelector:| to call |setNeedsDisplayInRect:|
886 // after returning to the main loop, at which point |drawRect:| is no
887 // longer on the stack.
888 DCHECK([NSThread isMainThread]);
889 if (!call_set_needs_display_in_rect_pending_) {
890 [cocoa_view_ performSelector:@selector(callSetNeedsDisplayInRect)
893 call_set_needs_display_in_rect_pending_ = true;
894 invalid_rect_ = ns_rect;
896 // The old invalid rect is probably invalid now, since the view has
897 // most likely been resized, but there's no harm in dirtying the
898 // union. In the limit, this becomes equivalent to dirtying the
900 invalid_rect_ = NSUnionRect(invalid_rect_, ns_rect);
903 [cocoa_view_ setNeedsDisplayInRect:ns_rect];
907 if (!about_to_validate_and_paint_)
908 [cocoa_view_ displayIfNeeded];
912 void RenderWidgetHostViewMac::RenderViewGone(base::TerminationStatus status,
917 void RenderWidgetHostViewMac::Destroy() {
918 AckPendingSwapBuffers();
920 [[NSNotificationCenter defaultCenter]
921 removeObserver:cocoa_view_
922 name:NSWindowWillCloseNotification
923 object:popup_window_];
925 // We've been told to destroy.
926 [cocoa_view_ retain];
927 [cocoa_view_ removeFromSuperview];
928 [cocoa_view_ autorelease];
930 [popup_window_ close];
931 popup_window_.autorelease();
933 [fullscreen_window_manager_ exitFullscreenMode];
934 fullscreen_window_manager_.reset();
935 [pepper_fullscreen_window_ close];
937 // This can be called as part of processing the window's responder
938 // chain, for instance |-performKeyEquivalent:|. In that case the
939 // object needs to survive until the stack unwinds.
940 pepper_fullscreen_window_.autorelease();
942 // We get this call just before |render_widget_host_| deletes
943 // itself. But we are owned by |cocoa_view_|, which may be retained
944 // by some other code. Examples are WebContentsViewMac's
945 // |latent_focus_view_| and TabWindowController's
946 // |cachedContentView_|.
947 render_widget_host_ = NULL;
950 // Called from the renderer to tell us what the tooltip text should be. It
951 // calls us frequently so we need to cache the value to prevent doing a lot
953 void RenderWidgetHostViewMac::SetTooltipText(const string16& tooltip_text) {
954 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
955 tooltip_text_ = tooltip_text;
957 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
958 // Windows; we're just trying to be polite. Don't persist the trimmed
959 // string, as then the comparison above will always fail and we'll try to
960 // set it again every single time the mouse moves.
961 string16 display_text = tooltip_text_;
962 if (tooltip_text_.length() > kMaxTooltipLength)
963 display_text = tooltip_text_.substr(0, kMaxTooltipLength);
965 NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
966 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
970 bool RenderWidgetHostViewMac::SupportsSpeech() const {
971 return [NSApp respondsToSelector:@selector(speakString:)] &&
972 [NSApp respondsToSelector:@selector(stopSpeaking:)];
975 void RenderWidgetHostViewMac::SpeakSelection() {
976 if ([NSApp respondsToSelector:@selector(speakString:)])
977 [NSApp speakString:base::SysUTF8ToNSString(selected_text_)];
980 bool RenderWidgetHostViewMac::IsSpeaking() const {
981 return [NSApp respondsToSelector:@selector(isSpeaking)] &&
985 void RenderWidgetHostViewMac::StopSpeaking() {
986 if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
987 [NSApp stopSpeaking:cocoa_view_];
991 // RenderWidgetHostViewCocoa uses the stored selection text,
992 // which implements NSServicesRequests protocol.
994 void RenderWidgetHostViewMac::SelectionChanged(const string16& text,
996 const ui::Range& range) {
997 if (range.is_empty() || text.empty()) {
998 selected_text_.clear();
1000 size_t pos = range.GetMin() - offset;
1001 size_t n = range.length();
1003 DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1004 if (pos >= text.length()) {
1005 DCHECK(false) << "The text can not cover range.";
1008 selected_text_ = UTF16ToUTF8(text.substr(pos, n));
1011 [cocoa_view_ setSelectedRange:range.ToNSRange()];
1012 // Updates markedRange when there is no marked text so that retrieving
1013 // markedRange immediately after calling setMarkdText: returns the current
1015 if (![cocoa_view_ hasMarkedText]) {
1016 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1019 RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1022 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1023 const ViewHostMsg_SelectionBounds_Params& params) {
1024 if (params.anchor_rect == params.focus_rect)
1025 caret_rect_ = params.anchor_rect;
1028 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1031 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1032 RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1034 // Create a fake mouse event to inform the render widget that the mouse
1036 NSWindow* window = [cocoa_view_ window];
1037 // TODO(asvitkine): If the location outside of the event stream doesn't
1038 // correspond to the current event (due to delayed event processing), then
1039 // this may result in a cursor flicker if there are later mouse move events
1040 // in the pipeline. Find a way to use the mouse location from the event that
1041 // dismissed the context menu.
1042 NSPoint location = [window mouseLocationOutsideOfEventStream];
1043 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1047 windowNumber:window_number()
1052 WebMouseEvent web_event =
1053 WebInputEventFactory::mouseEvent(event, cocoa_view_);
1055 web_event.type = WebInputEvent::MouseLeave;
1056 ForwardMouseEvent(web_event);
1059 bool RenderWidgetHostViewMac::IsPopup() const {
1060 return popup_type_ != WebKit::WebPopupTypeNone;
1063 BackingStore* RenderWidgetHostViewMac::AllocBackingStore(
1064 const gfx::Size& size) {
1065 float scale = ScaleFactor(cocoa_view_);
1066 return new BackingStoreMac(render_widget_host_, size, scale);
1069 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1070 const gfx::Rect& src_subrect,
1071 const gfx::Size& dst_size,
1072 const base::Callback<void(bool, const SkBitmap&)>& callback) {
1073 base::ScopedClosureRunner scoped_callback_runner(
1074 base::Bind(callback, false, SkBitmap()));
1075 if (!compositing_iosurface_ ||
1076 !compositing_iosurface_->HasIOSurface())
1079 float scale = ScaleFactor(cocoa_view_);
1080 gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1081 gfx::ScaleSize(dst_size, scale));
1083 scoped_callback_runner.Release();
1085 compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1090 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1091 const gfx::Rect& src_subrect,
1092 const scoped_refptr<media::VideoFrame>& target,
1093 const base::Callback<void(bool)>& callback) {
1094 base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1095 if (!render_widget_host_->is_accelerated_compositing_active() ||
1096 !compositing_iosurface_ ||
1097 !compositing_iosurface_->HasIOSurface())
1105 if (target->format() != media::VideoFrame::YV12 &&
1106 target->format() != media::VideoFrame::I420) {
1111 if (src_subrect.IsEmpty())
1114 scoped_callback_runner.Release();
1115 compositing_iosurface_->CopyToVideoFrame(
1116 GetScaledOpenGLPixelRect(src_subrect),
1121 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1122 return (!render_widget_host_->GetBackingStore(false) &&
1123 render_widget_host_->is_accelerated_compositing_active() &&
1124 compositing_iosurface_ &&
1125 compositing_iosurface_->HasIOSurface());
1128 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1132 void RenderWidgetHostViewMac::BeginFrameSubscription(
1133 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1134 frame_subscriber_ = subscriber.Pass();
1137 void RenderWidgetHostViewMac::EndFrameSubscription() {
1138 frame_subscriber_.reset();
1141 // Sets whether or not to accept first responder status.
1142 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1143 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1146 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1147 if (render_widget_host_)
1148 render_widget_host_->ForwardMouseEvent(event);
1150 if (event.type == WebInputEvent::MouseLeave) {
1151 [cocoa_view_ setToolTipAtMousePoint:nil];
1152 tooltip_text_.clear();
1156 void RenderWidgetHostViewMac::KillSelf() {
1157 if (!weak_factory_.HasWeakPtrs()) {
1158 [cocoa_view_ setHidden:YES];
1159 base::MessageLoop::current()->PostTask(FROM_HERE,
1160 base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1161 weak_factory_.GetWeakPtr()));
1165 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1166 const NativeWebKeyboardEvent& event) {
1167 // Check WebInputEvent type since multiple types of events can be sent into
1168 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1169 // necessary to avoid double processing.
1170 // Also check the native type, since NSFlagsChanged is considered a key event
1171 // for WebKit purposes, but isn't considered a key event by the OS.
1172 if (event.type == WebInputEvent::RawKeyDown &&
1173 [event.os_event type] == NSKeyDown)
1174 return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1178 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1179 const string16& text, int plugin_id) {
1180 if (render_widget_host_) {
1181 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1182 render_widget_host_->GetRoutingID(), text, plugin_id));
1186 bool RenderWidgetHostViewMac::CompositorSwapBuffers(
1187 uint64 surface_handle,
1188 const gfx::Size& size,
1189 float surface_scale_factor,
1190 const ui::LatencyInfo& latency_info) {
1194 NSWindow* window = [cocoa_view_ window];
1195 if (window_number() <= 0) {
1196 // There is no window to present so capturing during present won't work.
1197 // We check if frame subscriber wants this frame and capture manually.
1198 if (compositing_iosurface_ && frame_subscriber_) {
1199 const base::Time present_time = base::Time::Now();
1200 scoped_refptr<media::VideoFrame> frame;
1201 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1202 if (frame_subscriber_->ShouldCaptureFrame(present_time,
1203 &frame, &callback)) {
1204 compositing_iosurface_->SetIOSurface(
1205 surface_handle, size, surface_scale_factor, latency_info);
1206 compositing_iosurface_->CopyToVideoFrame(
1207 gfx::Rect(size), frame,
1208 base::Bind(callback, present_time));
1213 // TODO(shess) If the view does not have a window, or the window
1214 // does not have backing, the IOSurface will log "invalid drawable"
1215 // in -setView:. It is not clear how this code is reached with such
1216 // a case, so record some info into breakpad (some subset of
1217 // browsers are likely to crash later for unrelated reasons).
1218 // http://crbug.com/148882
1219 const char* const kCrashKey = "rwhvm_window";
1221 base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1224 base::StringPrintf("window %s delegate %s controller %s",
1225 object_getClassName(window),
1226 object_getClassName([window delegate]),
1227 object_getClassName([window windowController]));
1228 base::debug::SetCrashKeyValue(kCrashKey, value);
1234 bool should_post_notification = false;
1236 if (use_core_animation_) {
1237 if (!CreateCompositedIOSurfaceAndLayer()) {
1238 LOG(ERROR) << "Failed to create CompositingIOSurface or its layer";
1242 if (!compositing_iosurface_) {
1243 compositing_iosurface_.reset(
1244 CompositingIOSurfaceMac::Create(window_number()));
1246 if (!compositing_iosurface_) {
1247 LOG(ERROR) << "Failed to create CompositingIOSurfaceMac";
1251 should_post_notification = true;
1253 if (!compositing_iosurface_->SetIOSurface(
1254 surface_handle, size, surface_scale_factor, latency_info)) {
1255 LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1259 GotAcceleratedFrame();
1261 gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1262 if (window_size.IsEmpty()) {
1263 // setNeedsDisplay will never display and we'll never ack if the window is
1264 // empty, so ack now and don't bother calling setNeedsDisplay below.
1268 // No need to draw the surface if we are inside a drawRect. It will be done
1270 if (!about_to_validate_and_paint_) {
1271 if (use_core_animation_) {
1272 DCHECK(compositing_iosurface_layer_);
1273 [compositing_iosurface_layer_ setNeedsDisplay];
1275 if (!compositing_iosurface_->DrawIOSurface(this)) {
1276 [cocoa_view_ setNeedsDisplay:YES];
1277 GotAcceleratedCompositingError();
1283 if (should_post_notification && [[cocoa_view_ delegate]
1284 respondsToSelector:@selector(compositingIOSurfaceCreated)]) {
1285 [[cocoa_view_ delegate] compositingIOSurfaceCreated];
1291 void RenderWidgetHostViewMac::AckPendingSwapBuffers() {
1292 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::AckPendingSwapBuffers");
1294 // Cancel any outstanding delayed calls to this function.
1295 pending_swap_buffers_acks_weak_factory_.InvalidateWeakPtrs();
1297 while (!pending_swap_buffers_acks_.empty()) {
1298 if (pending_swap_buffers_acks_.front().first != 0) {
1299 AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
1300 ack_params.sync_point = 0;
1301 if (compositing_iosurface_)
1302 ack_params.renderer_id = compositing_iosurface_->GetRendererID();
1303 RenderWidgetHostImpl::AcknowledgeBufferPresent(
1304 pending_swap_buffers_acks_.front().first,
1305 pending_swap_buffers_acks_.front().second,
1307 if (render_widget_host_) {
1308 render_widget_host_->AcknowledgeSwapBuffersToRenderer();
1311 pending_swap_buffers_acks_.erase(pending_swap_buffers_acks_.begin());
1315 void RenderWidgetHostViewMac::ThrottledAckPendingSwapBuffers() {
1316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1318 // Send VSync parameters to the renderer's compositor thread.
1319 base::TimeTicks vsync_timebase;
1320 base::TimeDelta vsync_interval;
1321 GetVSyncParameters(&vsync_timebase, &vsync_interval);
1322 if (render_widget_host_ && compositing_iosurface_)
1323 render_widget_host_->UpdateVSyncParameters(vsync_timebase, vsync_interval);
1325 // If the render widget host is responsible for throttling swaps to vsync rate
1326 // then don't ack the swapbuffers until a full vsync has passed since the last
1328 bool throttle_swap_ack =
1329 render_widget_host_ &&
1330 !render_widget_host_->is_threaded_compositing_enabled() &&
1331 compositing_iosurface_ &&
1332 !compositing_iosurface_->is_vsync_disabled();
1333 base::Time now = base::Time::Now();
1334 if (throttle_swap_ack && next_swap_ack_time_ > now) {
1335 base::TimeDelta next_swap_ack_delay = next_swap_ack_time_ - now;
1336 next_swap_ack_time_ += vsync_interval;
1337 base::MessageLoop::current()->PostDelayedTask(
1339 base::Bind(&RenderWidgetHostViewMac::AckPendingSwapBuffers,
1340 pending_swap_buffers_acks_weak_factory_.GetWeakPtr()),
1341 next_swap_ack_delay);
1343 next_swap_ack_time_ = now + vsync_interval;
1344 AckPendingSwapBuffers();
1348 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1349 AckPendingSwapBuffers();
1350 DestroyCompositedIOSurfaceAndLayer();
1351 // The existing GL contexts may be in a bad state, so don't re-use any of the
1352 // existing ones anymore, rather, allocate new ones.
1353 CompositingIOSurfaceContext::MarkExistingContextsAsNotShareable();
1354 // Request that a new frame be generated.
1355 if (render_widget_host_)
1356 render_widget_host_->ScheduleComposite();
1357 // TODO(ccameron): It may be a good idea to request that the renderer recreate
1358 // its GL context as well, and fall back to software if this happens
1362 void RenderWidgetHostViewMac::GetVSyncParameters(
1363 base::TimeTicks* timebase, base::TimeDelta* interval) {
1364 if (compositing_iosurface_) {
1365 uint32 numerator = 0;
1366 uint32 denominator = 0;
1367 compositing_iosurface_->GetVSyncParameters(
1368 timebase, &numerator, &denominator);
1369 if (numerator > 0 && denominator > 0) {
1370 int64 interval_micros =
1371 1000000 * static_cast<int64>(numerator) / denominator;
1372 *interval = base::TimeDelta::FromMicroseconds(interval_micros);
1377 // Pass reasonable default values if unable to get the actual ones
1378 // (e.g. CVDisplayLink failed to return them because the display is
1380 static const int64 kOneOverSixtyMicroseconds = 16669;
1381 *timebase = base::TimeTicks::Now(),
1382 *interval = base::TimeDelta::FromMicroseconds(kOneOverSixtyMicroseconds);
1385 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1386 const std::vector<gfx::Rect>& bounds,
1387 const ui::Range& range,
1388 size_t* line_break_point) {
1389 DCHECK(line_break_point);
1390 if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1393 // We can't check line breaking completely from only rectangle array. Thus we
1394 // assume the line breaking as the next character's y offset is larger than
1395 // a threshold. Currently the threshold is determined as minimum y offset plus
1396 // 75% of maximum height.
1397 // TODO(nona): Check the threshold is reliable or not.
1398 // TODO(nona): Bidi support.
1399 const size_t loop_end_idx = std::min(bounds.size(), range.end());
1401 int min_y_offset = kint32max;
1402 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1403 max_height = std::max(max_height, bounds[idx].height());
1404 min_y_offset = std::min(min_y_offset, bounds[idx].y());
1406 int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1407 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1408 if (bounds[idx].y() > line_break_threshold) {
1409 *line_break_point = idx;
1416 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1417 const ui::Range& range,
1418 ui::Range* actual_range) {
1419 DCHECK(actual_range);
1420 DCHECK(!composition_bounds_.empty());
1421 DCHECK(range.start() <= composition_bounds_.size());
1422 DCHECK(range.end() <= composition_bounds_.size());
1424 if (range.is_empty()) {
1425 *actual_range = range;
1426 if (range.start() == composition_bounds_.size()) {
1427 return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1428 composition_bounds_[range.start() - 1].y(),
1430 composition_bounds_[range.start() - 1].height());
1432 return gfx::Rect(composition_bounds_[range.start()].x(),
1433 composition_bounds_[range.start()].y(),
1435 composition_bounds_[range.start()].height());
1440 if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1441 end_idx = range.end();
1443 *actual_range = ui::Range(range.start(), end_idx);
1444 gfx::Rect rect = composition_bounds_[range.start()];
1445 for (size_t i = range.start() + 1; i < end_idx; ++i) {
1446 rect.Union(composition_bounds_[i]);
1451 ui::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1452 const ui::Range& request_range) {
1453 if (composition_range_.is_empty())
1454 return ui::Range::InvalidRange();
1456 if (request_range.is_reversed())
1457 return ui::Range::InvalidRange();
1459 if (request_range.start() < composition_range_.start() ||
1460 request_range.start() > composition_range_.end() ||
1461 request_range.end() > composition_range_.end()) {
1462 return ui::Range::InvalidRange();
1466 request_range.start() - composition_range_.start(),
1467 request_range.end() - composition_range_.start());
1470 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1473 NSRange* actual_range) {
1475 // This exists to make IMEs more responsive, see http://crbug.com/115920
1476 TRACE_EVENT0("browser",
1477 "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1479 // If requested range is same as caret location, we can just return it.
1480 if (selection_range_.is_empty() && ui::Range(range) == selection_range_) {
1482 *actual_range = range;
1483 *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1487 const ui::Range request_range_in_composition =
1488 ConvertCharacterRangeToCompositionRange(ui::Range(range));
1489 if (request_range_in_composition == ui::Range::InvalidRange())
1492 // If firstRectForCharacterRange in WebFrame is failed in renderer,
1493 // ImeCompositionRangeChanged will be sent with empty vector.
1494 if (composition_bounds_.empty())
1496 DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1498 ui::Range ui_actual_range;
1499 *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1500 request_range_in_composition,
1501 &ui_actual_range).ToCGRect());
1503 *actual_range = ui::Range(
1504 composition_range_.start() + ui_actual_range.start(),
1505 composition_range_.start() + ui_actual_range.end()).ToNSRange();
1510 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1511 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1513 TRACE_EVENT0("browser",
1514 "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1515 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1517 pending_swap_buffers_acks_.push_back(std::make_pair(params.route_id,
1520 if (CompositorSwapBuffers(params.surface_handle,
1522 params.scale_factor,
1523 params.latency_info)) {
1524 ThrottledAckPendingSwapBuffers();
1526 GotAcceleratedCompositingError();
1530 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1531 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1533 TRACE_EVENT0("browser",
1534 "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1535 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1537 pending_swap_buffers_acks_.push_back(std::make_pair(params.route_id,
1540 if (CompositorSwapBuffers(params.surface_handle,
1541 params.surface_size,
1542 params.surface_scale_factor,
1543 params.latency_info)) {
1544 ThrottledAckPendingSwapBuffers();
1546 GotAcceleratedCompositingError();
1550 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1551 if (compositing_iosurface_)
1552 compositing_iosurface_->UnrefIOSurface();
1555 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1556 DestroyCompositedIOSurfaceAndLayer();
1559 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1560 const gfx::Size& desired_size) {
1561 return last_frame_was_accelerated_ &&
1562 compositing_iosurface_ &&
1563 compositing_iosurface_->HasIOSurface() &&
1564 (desired_size.IsEmpty() ||
1565 compositing_iosurface_->dip_io_surface_size() == desired_size);
1568 void RenderWidgetHostViewMac::AboutToWaitForBackingStoreMsg() {
1569 AckPendingSwapBuffers();
1572 void RenderWidgetHostViewMac::OnAcceleratedCompositingStateChange() {
1575 void RenderWidgetHostViewMac::GetScreenInfo(WebKit::WebScreenInfo* results) {
1576 *results = GetWebScreenInfo(GetNativeView());
1579 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1580 // TODO(shess): In case of !window, the view has been removed from
1581 // the view hierarchy because the tab isn't main. Could retrieve
1582 // the information from the main tab for our window.
1583 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1584 if (!enclosing_window)
1587 NSRect bounds = [enclosing_window frame];
1588 return FlipNSRectToRectScreen(bounds);
1591 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1592 // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1593 // completely on Mac OS.
1594 return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
1597 void RenderWidgetHostViewMac::SetHasHorizontalScrollbar(
1598 bool has_horizontal_scrollbar) {
1599 [cocoa_view_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
1602 void RenderWidgetHostViewMac::SetScrollOffsetPinning(
1603 bool is_pinned_to_left, bool is_pinned_to_right) {
1604 [cocoa_view_ scrollOffsetPinnedToLeft:is_pinned_to_left
1605 toRight:is_pinned_to_right];
1608 bool RenderWidgetHostViewMac::LockMouse() {
1612 mouse_locked_ = true;
1614 // Lock position of mouse cursor and hide it.
1615 CGAssociateMouseAndMouseCursorPosition(NO);
1618 // Clear the tooltip window.
1619 SetTooltipText(string16());
1624 void RenderWidgetHostViewMac::UnlockMouse() {
1627 mouse_locked_ = false;
1629 // Unlock position of mouse cursor and unhide it.
1630 CGAssociateMouseAndMouseCursorPosition(YES);
1633 if (render_widget_host_)
1634 render_widget_host_->LostMouseLock();
1637 void RenderWidgetHostViewMac::UnhandledWheelEvent(
1638 const WebKit::WebMouseWheelEvent& event) {
1639 // Only record a wheel event as unhandled if JavaScript handlers got a chance
1640 // to see it (no-op wheel events are ignored by the event dispatcher)
1641 if (event.deltaX || event.deltaY)
1642 [cocoa_view_ gotUnhandledWheelEvent];
1645 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
1646 if (render_widget_host_)
1647 return render_widget_host_->Send(message);
1653 void RenderWidgetHostViewMac::ShutdownHost() {
1654 weak_factory_.InvalidateWeakPtrs();
1655 render_widget_host_->Shutdown();
1656 // Do not touch any members at this point, |this| has been deleted.
1659 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
1660 // Update the scale factor of the layer to match the scale factor of the
1662 [compositing_iosurface_layer_ updateScaleFactor];
1664 if (!last_frame_was_accelerated_) {
1665 last_frame_was_accelerated_ = true;
1667 if (!use_core_animation_) {
1668 // Need to wipe the software view with transparency to expose the GL
1669 // underlay. Invalidate the whole window to do that.
1670 [cocoa_view_ setNeedsDisplay:YES];
1673 // Delete software backingstore.
1674 BackingStoreManager::RemoveBackingStore(render_widget_host_);
1678 void RenderWidgetHostViewMac::GotSoftwareFrame() {
1679 if (last_frame_was_accelerated_) {
1680 last_frame_was_accelerated_ = false;
1682 AckPendingSwapBuffers();
1684 // Forget IOSurface since we are drawing a software frame now.
1685 if (use_core_animation_) {
1686 DestroyCompositedIOSurfaceAndLayer();
1689 if (compositing_iosurface_ &&
1690 compositing_iosurface_->HasIOSurface()) {
1691 compositing_iosurface_->ClearDrawable();
1697 void RenderWidgetHostViewMac::SetActive(bool active) {
1698 if (render_widget_host_) {
1699 render_widget_host_->SetActive(active);
1702 render_widget_host_->Focus();
1704 render_widget_host_->Blur();
1708 SetTextInputActive(active);
1710 [cocoa_view_ setPluginImeActive:NO];
1715 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
1716 if (render_widget_host_) {
1717 render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
1718 render_widget_host_->GetRoutingID(), visible));
1722 void RenderWidgetHostViewMac::WindowFrameChanged() {
1723 if (render_widget_host_) {
1724 render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
1725 render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
1730 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
1731 // Brings up either Dictionary.app or a light-weight dictionary panel,
1732 // depending on system settings.
1733 NSRange selection_range = [cocoa_view_ selectedRange];
1734 NSAttributedString* attr_string =
1735 [cocoa_view_ attributedSubstringForProposedRange:selection_range
1738 // The PDF plugin does not support getting the attributed string. Until it
1739 // does, use NSPerformService(), which opens Dictionary.app.
1740 // http://crbug.com/152438
1741 // TODO(asvitkine): This should be removed after the above support is added.
1743 if (selected_text_.empty())
1745 NSString* text = base::SysUTF8ToNSString(selected_text_);
1746 NSPasteboard* pasteboard = [NSPasteboard pasteboardWithUniqueName];
1747 NSArray* types = [NSArray arrayWithObject:NSStringPboardType];
1748 [pasteboard declareTypes:types owner:nil];
1749 if ([pasteboard setString:text forType:NSStringPboardType])
1750 NSPerformService(@"Look Up in Dictionary", pasteboard);
1754 NSRect rect = [cocoa_view_ firstViewRectForCharacterRange:selection_range
1757 // Set |rect.origin| to the text baseline based on |attr_string|'s font,
1758 // since -baselineDeltaForCharacterAtIndex: is currently not implemented.
1759 NSDictionary* attrs = [attr_string attributesAtIndex:0 effectiveRange:nil];
1760 NSFont* font = [attrs objectForKey:NSFontAttributeName];
1761 rect.origin.y += NSHeight(rect) - [font ascender];
1762 [cocoa_view_ showDefinitionForAttributedString:attr_string
1763 atPoint:rect.origin];
1766 void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) {
1767 RenderWidgetHostViewBase::SetBackground(background);
1768 if (render_widget_host_)
1769 render_widget_host_->Send(new ViewMsg_SetBackground(
1770 render_widget_host_->GetRoutingID(), background));
1773 void RenderWidgetHostViewMac::OnAccessibilityNotifications(
1774 const std::vector<AccessibilityHostMsg_NotificationParams>& params) {
1775 if (!GetBrowserAccessibilityManager()) {
1776 SetBrowserAccessibilityManager(
1777 new BrowserAccessibilityManagerMac(
1779 BrowserAccessibilityManagerMac::GetEmptyDocument(),
1782 GetBrowserAccessibilityManager()->OnAccessibilityNotifications(params);
1785 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
1787 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1788 EnablePasswordInput();
1790 DisablePasswordInput();
1792 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
1793 DisablePasswordInput();
1797 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
1799 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
1802 void RenderWidgetHostViewMac::OnStartPluginIme() {
1803 [cocoa_view_ setPluginImeActive:YES];
1806 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
1807 const gfx::Rect& rect) {
1808 gfx::Rect src_gl_subrect = rect;
1809 src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
1811 return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
1815 void RenderWidgetHostViewMac::FrameSwapped() {
1816 software_latency_info_.swap_timestamp = base::TimeTicks::HighResNow();
1817 render_widget_host_->FrameSwapped(software_latency_info_);
1818 software_latency_info_.Clear();
1821 } // namespace content
1823 // RenderWidgetHostViewCocoa ---------------------------------------------------
1825 @implementation RenderWidgetHostViewCocoa
1827 @synthesize selectedRange = selectedRange_;
1828 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
1829 @synthesize markedRange = markedRange_;
1830 @synthesize delegate = delegate_;
1832 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
1833 self = [super initWithFrame:NSZeroRect];
1835 editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
1836 editCommand_helper_->AddEditingSelectorsToClass([self class]);
1838 renderWidgetHostView_.reset(r);
1839 canBeKeyView_ = YES;
1840 focusedPluginIdentifier_ = -1;
1841 deviceScaleFactor_ = ScaleFactor(self);
1844 if ([self respondsToSelector:
1845 @selector(setWantsBestResolutionOpenGLSurface:)]) {
1846 [self setWantsBestResolutionOpenGLSurface:YES];
1848 handlingGlobalFrameDidChange_ = NO;
1849 [[NSNotificationCenter defaultCenter]
1851 selector:@selector(globalFrameDidChange:)
1852 name:NSViewGlobalFrameDidChangeNotification
1859 if (delegate_ && [delegate_ respondsToSelector:@selector(viewGone:)])
1860 [delegate_ viewGone:self];
1861 [[NSNotificationCenter defaultCenter] removeObserver:self];
1866 - (void)resetCursorRects {
1867 if (currentCursor_) {
1868 [self addCursorRect:[self visibleRect] cursor:currentCursor_];
1869 [currentCursor_ setOnMouseEntered:YES];
1873 - (void)gotUnhandledWheelEvent {
1875 [delegate_ respondsToSelector:@selector(gotUnhandledWheelEvent)]) {
1876 [delegate_ gotUnhandledWheelEvent];
1880 - (void)scrollOffsetPinnedToLeft:(BOOL)left toRight:(BOOL)right {
1881 if (delegate_ && [delegate_ respondsToSelector:
1882 @selector(scrollOffsetPinnedToLeft:toRight:)]) {
1883 [delegate_ scrollOffsetPinnedToLeft:left toRight:right];
1887 - (void)setHasHorizontalScrollbar:(BOOL)has_horizontal_scrollbar {
1889 [delegate_ respondsToSelector:@selector(setHasHorizontalScrollbar:)]) {
1890 [delegate_ setHasHorizontalScrollbar:has_horizontal_scrollbar];
1894 - (BOOL)respondsToSelector:(SEL)selector {
1895 // Trickiness: this doesn't mean "does this object's superclass respond to
1896 // this selector" but rather "does the -respondsToSelector impl from the
1897 // superclass say that this class responds to the selector".
1898 if ([super respondsToSelector:selector])
1902 return [delegate_ respondsToSelector:selector];
1907 - (id)forwardingTargetForSelector:(SEL)selector {
1908 if ([delegate_ respondsToSelector:selector])
1911 return [super forwardingTargetForSelector:selector];
1914 - (void)setCanBeKeyView:(BOOL)can {
1915 canBeKeyView_ = can;
1918 - (BOOL)acceptsMouseEventsWhenInactive {
1919 // Some types of windows (balloons, always-on-top panels) want to accept mouse
1920 // clicks w/o the first click being treated as 'activation'. Same applies to
1921 // mouse move events.
1922 return [[self window] level] > NSNormalWindowLevel;
1925 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
1926 return [self acceptsMouseEventsWhenInactive];
1929 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
1930 takesFocusOnlyOnMouseDown_ = b;
1933 - (void)setCloseOnDeactivate:(BOOL)b {
1934 closeOnDeactivate_ = b;
1937 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
1938 NSWindow* window = [self window];
1939 // If this is a background window, don't handle mouse movement events. This
1940 // is the expected behavior on the Mac as evidenced by other applications.
1941 if ([theEvent type] == NSMouseMoved &&
1942 ![self acceptsMouseEventsWhenInactive] &&
1943 ![window isKeyWindow]) {
1947 // Use hitTest to check whether the mouse is over a nonWebContentView - in
1948 // which case the mouse event should not be handled by the render host.
1949 const SEL nonWebContentViewSelector = @selector(nonWebContentView);
1950 NSView* contentView = [window contentView];
1951 NSView* view = [contentView hitTest:[theEvent locationInWindow]];
1952 // Traverse the superview hierarchy as the hitTest will return the frontmost
1953 // view, such as an NSTextView, while nonWebContentView may be specified by
1956 if ([view respondsToSelector:nonWebContentViewSelector] &&
1957 [view performSelector:nonWebContentViewSelector]) {
1958 // The cursor is over a nonWebContentView - ignore this mouse event.
1961 if ([view isKindOfClass:[self class]] && ![view isEqual:self]) {
1962 // The cursor is over an overlapping render widget. This check is done by
1963 // both views so the one that's returned by -hitTest: will end up
1964 // processing the event.
1967 view = [view superview];
1972 - (void)mouseEvent:(NSEvent*)theEvent {
1973 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
1974 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
1975 BOOL handled = [delegate_ handleEvent:theEvent];
1980 if ([self shouldIgnoreMouseEvent:theEvent]) {
1981 // If this is the first such event, send a mouse exit to the host view.
1982 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
1983 WebMouseEvent exitEvent =
1984 WebInputEventFactory::mouseEvent(theEvent, self);
1985 exitEvent.type = WebInputEvent::MouseLeave;
1986 exitEvent.button = WebMouseEvent::ButtonNone;
1987 renderWidgetHostView_->ForwardMouseEvent(exitEvent);
1989 mouseEventWasIgnored_ = YES;
1993 if (mouseEventWasIgnored_) {
1994 // If this is the first mouse event after a previous event that was ignored
1995 // due to the hitTest, send a mouse enter event to the host view.
1996 if (renderWidgetHostView_->render_widget_host_) {
1997 WebMouseEvent enterEvent =
1998 WebInputEventFactory::mouseEvent(theEvent, self);
1999 enterEvent.type = WebInputEvent::MouseMove;
2000 enterEvent.button = WebMouseEvent::ButtonNone;
2001 renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2004 mouseEventWasIgnored_ = NO;
2006 // TODO(rohitrao): Probably need to handle other mouse down events here.
2007 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2008 if (renderWidgetHostView_->render_widget_host_)
2009 renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2011 // Manually take focus after the click but before forwarding it to the
2013 [[self window] makeFirstResponder:self];
2016 // Don't cancel child popups; killing them on a mouse click would prevent the
2017 // user from positioning the insertion point in the text field spawning the
2018 // popup. A click outside the text field would cause the text field to drop
2019 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2020 // the popup anyway, so we're OK.
2022 NSEventType type = [theEvent type];
2023 if (type == NSLeftMouseDown)
2024 hasOpenMouseDown_ = YES;
2025 else if (type == NSLeftMouseUp)
2026 hasOpenMouseDown_ = NO;
2028 // TODO(suzhe): We should send mouse events to the input method first if it
2029 // wants to handle them. But it won't work without implementing method
2030 // - (NSUInteger)characterIndexForPoint:.
2031 // See: http://code.google.com/p/chromium/issues/detail?id=47141
2032 // Instead of sending mouse events to the input method first, we now just
2033 // simply confirm all ongoing composition here.
2034 if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2035 type == NSOtherMouseDown) {
2036 [self confirmComposition];
2039 const WebMouseEvent event =
2040 WebInputEventFactory::mouseEvent(theEvent, self);
2041 renderWidgetHostView_->ForwardMouseEvent(event);
2044 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2045 // |performKeyEquivalent:| is sent to all views of a window, not only down the
2046 // responder chain (cf. "Handling Key Equivalents" in
2047 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2048 // ). We only want to handle key equivalents if we're first responder.
2049 if ([[self window] firstResponder] != self)
2052 // If we return |NO| from this function, cocoa will send the key event to
2053 // the menu and only if the menu does not process the event to |keyDown:|. We
2054 // want to send the event to a renderer _before_ sending it to the menu, so
2055 // we need to return |YES| for all events that might be swallowed by the menu.
2056 // We do not return |YES| for every keypress because we don't get |keyDown:|
2057 // events for keys that we handle this way.
2058 NSUInteger modifierFlags = [theEvent modifierFlags];
2059 if ((modifierFlags & NSCommandKeyMask) == 0) {
2060 // Make sure the menu does not contain key equivalents that don't
2062 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2066 // Command key combinations are sent via performKeyEquivalent rather than
2067 // keyDown:. We just forward this on and if WebCore doesn't want to handle
2068 // it, we let the WebContentsView figure out how to reinject it.
2069 [self keyEvent:theEvent wasKeyEquivalent:YES];
2073 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2074 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2075 // returned NO. If this function returns |YES|, Cocoa sends the event to
2076 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2077 // to us instead of doing key view loop control, ctrl-left/right get handled
2079 // (However, there are still some keys that Cocoa swallows, e.g. the key
2080 // equivalent that Cocoa uses for toggling the input language. In this case,
2081 // that's actually a good thing, though -- see http://crbug.com/26115 .)
2085 - (void)keyEvent:(NSEvent*)theEvent {
2086 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2087 BOOL handled = [delegate_ handleEvent:theEvent];
2092 [self keyEvent:theEvent wasKeyEquivalent:NO];
2095 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2096 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2097 DCHECK([theEvent type] != NSKeyDown ||
2098 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2100 if ([theEvent type] == NSFlagsChanged) {
2101 // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2102 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2103 int keyCode = [theEvent keyCode];
2104 if (!keyCode || keyCode == 10 || keyCode == 63)
2108 // Don't cancel child popups; the key events are probably what's triggering
2109 // the popup in the first place.
2111 RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2114 NativeWebKeyboardEvent event(theEvent);
2116 // Force fullscreen windows to close on Escape so they won't keep the keyboard
2117 // grabbed or be stuck onscreen if the renderer is hanging.
2118 if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2119 event.windowsKeyCode == ui::VKEY_ESCAPE &&
2120 renderWidgetHostView_->pepper_fullscreen_window()) {
2121 RenderWidgetHostViewMac* parent =
2122 renderWidgetHostView_->fullscreen_parent_host_view();
2124 parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2125 widgetHost->Shutdown();
2129 // Suppress the escape key up event if necessary.
2130 if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2131 if (event.type == NativeWebKeyboardEvent::KeyUp)
2132 suppressNextEscapeKeyUp_ = NO;
2136 // We only handle key down events and just simply forward other events.
2137 if ([theEvent type] != NSKeyDown) {
2138 widgetHost->ForwardKeyboardEvent(event);
2140 // Possibly autohide the cursor.
2141 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2142 [NSCursor setHiddenUntilMouseMoves:YES];
2147 base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2149 // Records the current marked text state, so that we can know if the marked
2150 // text was deleted or not after handling the key down event.
2151 BOOL oldHasMarkedText = hasMarkedText_;
2153 // This method should not be called recursively.
2154 DCHECK(!handlingKeyDown_);
2156 // Tells insertText: and doCommandBySelector: that we are handling a key
2158 handlingKeyDown_ = YES;
2160 // These variables might be set when handling the keyboard event.
2161 // Clear them here so that we can know whether they have changed afterwards.
2162 textToBeInserted_.clear();
2163 markedText_.clear();
2164 underlines_.clear();
2165 unmarkTextCalled_ = NO;
2166 hasEditCommands_ = NO;
2167 editCommands_.clear();
2169 // Before doing anything with a key down, check to see if plugin IME has been
2170 // cancelled, since the plugin host needs to be informed of that before
2171 // receiving the keydown.
2172 if ([theEvent type] == NSKeyDown)
2173 [self checkForPluginImeCancellation];
2175 // Sends key down events to input method first, then we can decide what should
2176 // be done according to input method's feedback.
2177 // If a plugin is active, bypass this step since events are forwarded directly
2178 // to the plugin IME.
2179 if (focusedPluginIdentifier_ == -1)
2180 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2182 handlingKeyDown_ = NO;
2184 // Indicates if we should send the key event and corresponding editor commands
2185 // after processing the input method result.
2186 BOOL delayEventUntilAfterImeCompostion = NO;
2188 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2189 // while an input method is composing or inserting a text.
2190 // Gmail checks this code in its onkeydown handler to stop auto-completing
2191 // e-mail addresses while composing a CJK text.
2192 // If the text to be inserted has only one character, then we don't need this
2193 // trick, because we'll send the text as a key press event instead.
2194 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2195 NativeWebKeyboardEvent fakeEvent = event;
2196 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
2197 fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2198 fakeEvent.skip_in_browser = true;
2199 widgetHost->ForwardKeyboardEvent(fakeEvent);
2200 // If this key event was handled by the input method, but
2201 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2202 // enqueued edit commands, then in order to let webkit handle them
2203 // correctly, we need to send the real key event and corresponding edit
2204 // commands after processing the input method result.
2205 // We shouldn't do this if a new marked text was set by the input method,
2206 // otherwise the new marked text might be cancelled by webkit.
2207 if (hasEditCommands_ && !hasMarkedText_)
2208 delayEventUntilAfterImeCompostion = YES;
2210 if (!editCommands_.empty()) {
2211 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2212 widgetHost->GetRoutingID(), editCommands_));
2214 widgetHost->ForwardKeyboardEvent(event);
2217 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2218 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2219 // be set to NULL. So we check it here and return immediately if it's NULL.
2220 if (!renderWidgetHostView_->render_widget_host_)
2223 // Then send keypress and/or composition related events.
2224 // If there was a marked text or the text to be inserted is longer than 1
2225 // character, then we send the text by calling ConfirmComposition().
2226 // Otherwise, if the text to be inserted only contains 1 character, then we
2227 // can just send a keypress event which is fabricated by changing the type of
2228 // the keydown event, so that we can retain all necessary informations, such
2229 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2230 // prevent the browser from handling it again.
2231 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2232 // handle BMP characters here, as we can always insert non-BMP characters as
2234 BOOL textInserted = NO;
2235 if (textToBeInserted_.length() >
2236 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2237 widgetHost->ImeConfirmComposition(textToBeInserted_);
2241 // Updates or cancels the composition. If some text has been inserted, then
2242 // we don't need to cancel the composition explicitly.
2243 if (hasMarkedText_ && markedText_.length()) {
2244 // Sends the updated marked text to the renderer so it can update the
2245 // composition node in WebKit.
2246 // When marked text is available, |selectedRange_| will be the range being
2247 // selected inside the marked text.
2248 widgetHost->ImeSetComposition(markedText_, underlines_,
2249 selectedRange_.location,
2250 NSMaxRange(selectedRange_));
2251 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2252 if (unmarkTextCalled_)
2253 widgetHost->ImeConfirmComposition();
2255 widgetHost->ImeCancelComposition();
2258 // If the key event was handled by the input method but it also generated some
2259 // edit commands, then we need to send the real key event and corresponding
2260 // edit commands here. This usually occurs when the input method wants to
2261 // finish current composition session but still wants the application to
2262 // handle the key event. See http://crbug.com/48161 for reference.
2263 if (delayEventUntilAfterImeCompostion) {
2264 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2265 // with windowsKeyCode == 0xE5 has already been sent to webkit.
2266 // So before sending the real key down event, we need to send a fake key up
2267 // event to balance it.
2268 NativeWebKeyboardEvent fakeEvent = event;
2269 fakeEvent.type = WebKit::WebInputEvent::KeyUp;
2270 fakeEvent.skip_in_browser = true;
2271 widgetHost->ForwardKeyboardEvent(fakeEvent);
2272 // Not checking |renderWidgetHostView_->render_widget_host_| here because
2273 // a key event with |skip_in_browser| == true won't be handled by browser,
2274 // thus it won't destroy the widget.
2276 if (!editCommands_.empty()) {
2277 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2278 widgetHost->GetRoutingID(), editCommands_));
2280 widgetHost->ForwardKeyboardEvent(event);
2282 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2283 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2284 // be set to NULL. So we check it here and return immediately if it's NULL.
2285 if (!renderWidgetHostView_->render_widget_host_)
2289 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2290 // Only send a corresponding key press event if there is no marked text.
2291 if (!hasMarkedText_) {
2292 if (!textInserted && textToBeInserted_.length() == 1) {
2293 // If a single character was inserted, then we just send it as a keypress
2295 event.type = WebKit::WebInputEvent::Char;
2296 event.text[0] = textToBeInserted_[0];
2298 event.skip_in_browser = true;
2299 widgetHost->ForwardKeyboardEvent(event);
2300 } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2301 [[theEvent characters] length] > 0 &&
2302 (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2303 (hasEditCommands_ && editCommands_.empty()))) {
2304 // We don't get insertText: calls if ctrl or cmd is down, or the key event
2305 // generates an insert command. So synthesize a keypress event for these
2306 // cases, unless the key event generated any other command.
2307 event.type = WebKit::WebInputEvent::Char;
2308 event.skip_in_browser = true;
2309 widgetHost->ForwardKeyboardEvent(event);
2313 // Possibly autohide the cursor.
2314 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2315 [NSCursor setHiddenUntilMouseMoves:YES];
2318 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2319 DCHECK(base::mac::IsOSLionOrLater());
2321 if ([event phase] != NSEventPhaseEnded &&
2322 [event phase] != NSEventPhaseCancelled) {
2326 if (renderWidgetHostView_->render_widget_host_) {
2327 const WebMouseWheelEvent& webEvent =
2328 WebInputEventFactory::mouseWheelEvent(event, self);
2329 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2332 if (endWheelMonitor_) {
2333 [NSEvent removeMonitor:endWheelMonitor_];
2334 endWheelMonitor_ = nil;
2338 - (void)scrollWheel:(NSEvent*)event {
2339 if (delegate_ && [delegate_ respondsToSelector:@selector(handleEvent:)]) {
2340 BOOL handled = [delegate_ handleEvent:event];
2345 // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
2346 // the event is received even when the mouse cursor is no longer over the view
2347 // when the scrolling ends (e.g. if the tab was switched). This is necessary
2348 // for ending rubber-banding in such cases.
2349 if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
2350 !endWheelMonitor_) {
2352 [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
2353 handler:^(NSEvent* blockEvent) {
2354 [self shortCircuitScrollWheelEvent:blockEvent];
2359 if (renderWidgetHostView_->render_widget_host_) {
2360 const WebMouseWheelEvent& webEvent =
2361 WebInputEventFactory::mouseWheelEvent(event, self);
2362 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2366 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
2367 NSWindow* oldWindow = [self window];
2369 // We're messing with the window, so do this to ensure no flashes. This one
2370 // prevents a flash when the current tab is closed.
2371 if (!renderWidgetHostView_->use_core_animation_)
2372 [oldWindow disableScreenUpdatesUntilFlush];
2374 // If the new window for this view is using CoreAnimation then enable
2375 // CoreAnimation on this view.
2376 if (GetCoreAnimationStatus() == CORE_ANIMATION_ENABLED_LAZY &&
2377 [[newWindow contentView] wantsLayer]) {
2378 renderWidgetHostView_->EnableCoreAnimation();
2381 NSNotificationCenter* notificationCenter =
2382 [NSNotificationCenter defaultCenter];
2386 name:NSWindowDidChangeBackingPropertiesNotification
2390 name:NSWindowDidMoveNotification
2394 name:NSWindowDidEndLiveResizeNotification
2400 selector:@selector(windowDidChangeBackingProperties:)
2401 name:NSWindowDidChangeBackingPropertiesNotification
2405 selector:@selector(windowChangedGlobalFrame:)
2406 name:NSWindowDidMoveNotification
2410 selector:@selector(windowChangedGlobalFrame:)
2411 name:NSWindowDidEndLiveResizeNotification
2416 - (void)updateTabBackingStoreScaleFactor {
2417 if (!renderWidgetHostView_->render_widget_host_)
2420 float scaleFactor = ScaleFactor(self);
2421 if (scaleFactor == deviceScaleFactor_)
2423 deviceScaleFactor_ = scaleFactor;
2425 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
2426 renderWidgetHostView_->render_widget_host_->GetBackingStore(false));
2427 if (backingStore) // NULL in hardware path.
2428 backingStore->ScaleFactorChanged(scaleFactor);
2430 [self updateSoftwareLayerScaleFactor];
2431 renderWidgetHostView_->render_widget_host_->NotifyScreenInfoChanged();
2434 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
2435 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
2436 NSWindow* window = (NSWindow*)[notification object];
2438 CGFloat newBackingScaleFactor = [window backingScaleFactor];
2439 CGFloat oldBackingScaleFactor = [base::mac::ObjCCast<NSNumber>(
2440 [[notification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey])
2442 if (newBackingScaleFactor != oldBackingScaleFactor) {
2443 // Background tabs check if their scale factor changed when they are added
2446 // Allocating a CGLayerRef with the current scale factor immediately from
2447 // this handler doesn't work. Schedule the backing store update on the
2448 // next runloop cycle, then things are read for CGLayerRef allocations to
2450 [self performSelector:@selector(updateTabBackingStoreScaleFactor)
2456 - (void)globalFrameDidChange:(NSNotification*)notification {
2457 if (handlingGlobalFrameDidChange_)
2460 handlingGlobalFrameDidChange_ = YES;
2461 if (renderWidgetHostView_->compositing_iosurface_)
2462 renderWidgetHostView_->compositing_iosurface_->GlobalFrameDidChange();
2463 handlingGlobalFrameDidChange_ = NO;
2466 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
2467 renderWidgetHostView_->UpdateScreenInfo(
2468 renderWidgetHostView_->GetNativeView());
2471 - (void)setFrameSize:(NSSize)newSize {
2472 // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
2473 // -setFrame: isn't neccessary.
2474 [super setFrameSize:newSize];
2475 if (renderWidgetHostView_->render_widget_host_) {
2476 renderWidgetHostView_->render_widget_host_->SendScreenRects();
2477 renderWidgetHostView_->render_widget_host_->WasResized();
2481 - (void)callSetNeedsDisplayInRect {
2482 DCHECK([NSThread isMainThread]);
2483 DCHECK(renderWidgetHostView_->call_set_needs_display_in_rect_pending_);
2484 [self setNeedsDisplayInRect:renderWidgetHostView_->invalid_rect_];
2485 renderWidgetHostView_->call_set_needs_display_in_rect_pending_ = false;
2486 renderWidgetHostView_->invalid_rect_ = NSZeroRect;
2488 if (renderWidgetHostView_->compositing_iosurface_layer_)
2489 [renderWidgetHostView_->compositing_iosurface_layer_ setNeedsDisplay];
2492 // Fills with white the parts of the area to the right and bottom for |rect|
2493 // that intersect |damagedRect|.
2494 - (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect
2495 dirtyRect:(gfx::Rect)damagedRect
2496 inContext:(CGContextRef)context {
2497 if (damagedRect.right() > rect.right()) {
2498 int x = std::max(rect.right(), damagedRect.x());
2499 int y = std::min(rect.bottom(), damagedRect.bottom());
2500 int width = damagedRect.right() - x;
2501 int height = damagedRect.y() - y;
2503 // Extra fun to get around the fact that gfx::Rects can't have
2514 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
2515 CGContextSetFillColorWithColor(context,
2516 CGColorGetConstantColor(kCGColorWhite));
2517 CGContextFillRect(context, NSRectToCGRect(r));
2519 if (damagedRect.bottom() > rect.bottom()) {
2520 int x = damagedRect.x();
2521 int y = damagedRect.bottom();
2522 int width = damagedRect.right() - x;
2523 int height = std::max(rect.bottom(), damagedRect.y()) - y;
2525 // Extra fun to get around the fact that gfx::Rects can't have
2536 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)];
2537 CGContextSetFillColorWithColor(context,
2538 CGColorGetConstantColor(kCGColorWhite));
2539 CGContextFillRect(context, NSRectToCGRect(r));
2543 - (void)drawRect:(NSRect)dirtyRect {
2544 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::drawRect");
2545 DCHECK(!renderWidgetHostView_->use_core_animation_);
2547 if (!renderWidgetHostView_->render_widget_host_) {
2548 // TODO(shess): Consider using something more noticable?
2549 [[NSColor whiteColor] set];
2550 NSRectFill(dirtyRect);
2554 DCHECK(!renderWidgetHostView_->about_to_validate_and_paint_);
2556 // GetBackingStore works for both software and accelerated frames. If a
2557 // SwapBuffers occurs while GetBackingStore is blocking, we will continue to
2558 // blit the IOSurface below.
2559 renderWidgetHostView_->about_to_validate_and_paint_ = true;
2560 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
2561 renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
2562 renderWidgetHostView_->about_to_validate_and_paint_ = false;
2564 const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]);
2566 if (renderWidgetHostView_->last_frame_was_accelerated_ &&
2567 renderWidgetHostView_->compositing_iosurface_) {
2568 if (renderWidgetHostView_->allow_overlapping_views_) {
2569 // If overlapping views need to be allowed, punch a hole in the window
2570 // to expose the GL underlay.
2571 TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
2572 "h", damagedRect.height());
2573 // NSRectFill is extremely slow (15ms for a window on a fast MacPro), so
2574 // this is only done when it's a real invalidation from window damage (not
2575 // when a BuffersSwapped was received). Note that even a 1x1 NSRectFill
2576 // can take many milliseconds sometimes (!) so this is skipped completely
2577 // for drawRects that are triggered by BuffersSwapped messages.
2578 [[NSColor clearColor] set];
2579 NSRectFill(dirtyRect);
2582 if (renderWidgetHostView_->compositing_iosurface_->DrawIOSurface(
2583 renderWidgetHostView_.get())) {
2586 // On error, fall back to software and fall through to the non-accelerated
2588 renderWidgetHostView_->GotAcceleratedCompositingError();
2591 CGContextRef context = static_cast<CGContextRef>(
2592 [[NSGraphicsContext currentContext] graphicsPort]);
2593 [self drawBackingStore:backingStore
2594 dirtyRect:NSRectToCGRect(dirtyRect)
2598 - (void)drawBackingStore:(BackingStoreMac*)backingStore
2599 dirtyRect:(CGRect)dirtyRect
2600 inContext:(CGContextRef)context {
2602 // Note: All coordinates are in view units, not pixels.
2603 gfx::Rect bitmapRect(0, 0,
2604 backingStore->size().width(),
2605 backingStore->size().height());
2607 // Specify the proper y offset to ensure that the view is rooted to the
2608 // upper left corner. This can be negative, if the window was resized
2609 // smaller and the renderer hasn't yet repainted.
2610 int yOffset = NSHeight([self bounds]) - backingStore->size().height();
2612 NSRect nsDirtyRect = NSRectFromCGRect(dirtyRect);
2613 const gfx::Rect damagedRect([self flipNSRectToRect:nsDirtyRect]);
2615 gfx::Rect paintRect = gfx::IntersectRects(bitmapRect, damagedRect);
2616 if (!paintRect.IsEmpty()) {
2617 // if we have a CGLayer, draw that into the window
2618 if (backingStore->cg_layer()) {
2619 // TODO: add clipping to dirtyRect if it improves drawing performance.
2620 CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset),
2621 backingStore->cg_layer());
2623 // if we haven't created a layer yet, draw the cached bitmap into
2624 // the window. The CGLayer will be created the next time the renderer
2626 base::ScopedCFTypeRef<CGImageRef> image(
2627 CGBitmapContextCreateImage(backingStore->cg_bitmap()));
2628 CGRect imageRect = bitmapRect.ToCGRect();
2629 imageRect.origin.y = yOffset;
2630 CGContextDrawImage(context, imageRect, image);
2634 renderWidgetHostView_->FrameSwapped();
2636 // Fill the remaining portion of the damagedRect with white
2637 [self fillBottomRightRemainderOfRect:bitmapRect
2638 dirtyRect:damagedRect
2641 if (!renderWidgetHostView_->whiteout_start_time_.is_null()) {
2642 base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
2643 renderWidgetHostView_->whiteout_start_time_;
2644 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
2646 // Reset the start time to 0 so that we start recording again the next
2647 // time the backing store is NULL...
2648 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks();
2650 if (!renderWidgetHostView_->web_contents_switch_paint_time_.is_null()) {
2651 base::TimeDelta web_contents_switch_paint_duration =
2652 base::TimeTicks::Now() -
2653 renderWidgetHostView_->web_contents_switch_paint_time_;
2654 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
2655 web_contents_switch_paint_duration);
2656 // Reset contents_switch_paint_time_ to 0 so future tab selections are
2658 renderWidgetHostView_->web_contents_switch_paint_time_ =
2662 CGContextSetFillColorWithColor(context,
2663 CGColorGetConstantColor(kCGColorWhite));
2664 CGContextFillRect(context, dirtyRect);
2665 if (renderWidgetHostView_->whiteout_start_time_.is_null())
2666 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now();
2670 - (BOOL)canBecomeKeyView {
2671 if (!renderWidgetHostView_->render_widget_host_)
2674 return canBeKeyView_;
2677 - (BOOL)acceptsFirstResponder {
2678 if (!renderWidgetHostView_->render_widget_host_)
2681 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
2684 - (BOOL)becomeFirstResponder {
2685 if (!renderWidgetHostView_->render_widget_host_)
2688 renderWidgetHostView_->render_widget_host_->Focus();
2689 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
2690 renderWidgetHostView_->SetTextInputActive(true);
2692 // Cancel any onging composition text which was left before we lost focus.
2693 // TODO(suzhe): We should do it in -resignFirstResponder: method, but
2694 // somehow that method won't be called when switching among different tabs.
2695 // See http://crbug.com/47209
2696 [self cancelComposition];
2698 NSNumber* direction = [NSNumber numberWithUnsignedInteger:
2699 [[self window] keyViewSelectionDirection]];
2700 NSDictionary* userInfo =
2701 [NSDictionary dictionaryWithObject:direction
2702 forKey:kSelectionDirection];
2703 [[NSNotificationCenter defaultCenter]
2704 postNotificationName:kViewDidBecomeFirstResponder
2711 - (BOOL)resignFirstResponder {
2712 renderWidgetHostView_->SetTextInputActive(false);
2713 if (!renderWidgetHostView_->render_widget_host_)
2716 if (closeOnDeactivate_)
2717 renderWidgetHostView_->KillSelf();
2719 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
2720 renderWidgetHostView_->render_widget_host_->Blur();
2722 // We should cancel any onging composition whenever RWH's Blur() method gets
2723 // called, because in this case, webkit will confirm the ongoing composition
2725 [self cancelComposition];
2730 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
2731 if (delegate_ && [delegate_ respondsToSelector:
2732 @selector(validateUserInterfaceItem:isValidItem:)]) {
2734 BOOL known = [delegate_ validateUserInterfaceItem:item
2735 isValidItem:&valid];
2740 SEL action = [item action];
2742 if (action == @selector(stopSpeaking:)) {
2743 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
2744 renderWidgetHostView_->IsSpeaking();
2746 if (action == @selector(startSpeaking:)) {
2747 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
2748 renderWidgetHostView_->SupportsSpeech();
2751 // For now, these actions are always enabled for render view,
2752 // this is sub-optimal.
2753 // TODO(suzhe): Plumb the "can*" methods up from WebCore.
2754 if (action == @selector(undo:) ||
2755 action == @selector(redo:) ||
2756 action == @selector(cut:) ||
2757 action == @selector(copy:) ||
2758 action == @selector(copyToFindPboard:) ||
2759 action == @selector(paste:) ||
2760 action == @selector(pasteAndMatchStyle:)) {
2761 return renderWidgetHostView_->render_widget_host_->IsRenderView();
2764 return editCommand_helper_->IsMenuItemEnabled(action, self);
2767 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
2768 return renderWidgetHostView_.get();
2771 // Determine whether we should autohide the cursor (i.e., hide it until mouse
2772 // move) for the given event. Customize here to be more selective about which
2773 // key presses to autohide on.
2774 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
2775 return ([event type] == NSKeyDown) ? YES : NO;
2778 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
2779 index:(NSUInteger)index
2780 maxCount:(NSUInteger)maxCount {
2781 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
2782 NSUInteger totalLength = [fullArray count];
2783 if (index >= totalLength)
2785 NSUInteger length = MIN(totalLength - index, maxCount);
2786 return [fullArray subarrayWithRange:NSMakeRange(index, length)];
2789 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
2790 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
2791 return [fullArray count];
2794 - (id)accessibilityAttributeValue:(NSString *)attribute {
2795 BrowserAccessibilityManager* manager =
2796 renderWidgetHostView_->GetBrowserAccessibilityManager();
2798 // Contents specifies document view of RenderWidgetHostViewCocoa provided by
2799 // BrowserAccessibilityManager. Children includes all subviews in addition to
2800 // contents. Currently we do not have subviews besides the document view.
2801 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
2802 [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
2804 return [NSArray arrayWithObjects:manager->
2805 GetRoot()->ToBrowserAccessibilityCocoa(), nil];
2806 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
2807 return NSAccessibilityScrollAreaRole;
2809 id ret = [super accessibilityAttributeValue:attribute];
2813 - (NSArray*)accessibilityAttributeNames {
2814 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
2815 [ret addObject:NSAccessibilityContentsAttribute];
2816 [ret addObjectsFromArray:[super accessibilityAttributeNames]];
2820 - (id)accessibilityHitTest:(NSPoint)point {
2821 if (!renderWidgetHostView_->GetBrowserAccessibilityManager())
2823 NSPoint pointInWindow = [[self window] convertScreenToBase:point];
2824 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
2825 localPoint.y = NSHeight([self bounds]) - localPoint.y;
2826 BrowserAccessibilityCocoa* root = renderWidgetHostView_->
2827 GetBrowserAccessibilityManager()->
2828 GetRoot()->ToBrowserAccessibilityCocoa();
2829 id obj = [root accessibilityHitTest:localPoint];
2833 - (BOOL)accessibilityIsIgnored {
2834 return !renderWidgetHostView_->GetBrowserAccessibilityManager();
2837 - (NSUInteger)accessibilityGetIndexOf:(id)child {
2838 BrowserAccessibilityManager* manager =
2839 renderWidgetHostView_->GetBrowserAccessibilityManager();
2840 // Only child is root.
2842 manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
2849 - (id)accessibilityFocusedUIElement {
2850 BrowserAccessibilityManager* manager =
2851 renderWidgetHostView_->GetBrowserAccessibilityManager();
2853 BrowserAccessibility* focused_item = manager->GetFocus(NULL);
2854 DCHECK(focused_item);
2856 BrowserAccessibilityCocoa* focused_item_cocoa =
2857 focused_item->ToBrowserAccessibilityCocoa();
2858 DCHECK(focused_item_cocoa);
2859 if (focused_item_cocoa)
2860 return focused_item_cocoa;
2863 return [super accessibilityFocusedUIElement];
2866 - (void)doDefaultAction:(int32)accessibilityObjectId {
2867 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
2868 rwh->Send(new AccessibilityMsg_DoDefaultAction(
2869 rwh->GetRoutingID(), accessibilityObjectId));
2872 // VoiceOver uses this method to move the caret to the beginning of the next
2873 // word in a text field.
2874 - (void)accessibilitySetTextSelection:(int32)accId
2875 startOffset:(int32)startOffset
2876 endOffset:(int32)endOffset {
2877 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
2878 rwh->AccessibilitySetTextSelection(accId, startOffset, endOffset);
2881 // Convert a web accessibility's location in web coordinates into a cocoa
2882 // screen coordinate.
2883 - (NSPoint)accessibilityPointInScreen:
2884 (BrowserAccessibilityCocoa*)accessibility {
2885 NSPoint origin = [accessibility origin];
2886 NSSize size = [[accessibility size] sizeValue];
2887 origin.y = NSHeight([self bounds]) - origin.y;
2888 NSPoint originInWindow = [self convertPoint:origin toView:nil];
2889 NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow];
2890 originInScreen.y = originInScreen.y - size.height;
2891 return originInScreen;
2894 - (void)setAccessibilityFocus:(BOOL)focus
2895 accessibilityId:(int32)accessibilityObjectId {
2897 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
2898 rwh->Send(new AccessibilityMsg_SetFocus(
2899 rwh->GetRoutingID(), accessibilityObjectId));
2901 // Immediately set the focused item even though we have not officially set
2902 // focus on it as VoiceOver expects to get the focused item after this
2904 BrowserAccessibilityManager* manager =
2905 renderWidgetHostView_->GetBrowserAccessibilityManager();
2906 manager->SetFocus(manager->GetFromRendererID(accessibilityObjectId), false);
2910 - (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility {
2911 // Performs a right click copying WebKit's
2912 // accessibilityPerformShowMenuAction.
2913 NSPoint location = [self accessibilityPointInScreen:accessibility];
2914 NSSize size = [[accessibility size] sizeValue];
2915 location = [[self window] convertScreenToBase:location];
2916 location.x += size.width/2;
2917 location.y += size.height/2;
2919 NSEvent* fakeRightClick = [NSEvent
2920 mouseEventWithType:NSRightMouseDown
2924 windowNumber:[[self window] windowNumber]
2925 context:[NSGraphicsContext currentContext]
2930 [self mouseEvent:fakeRightClick];
2933 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
2934 // with minor modifications for code style and commenting.
2936 // The 'public' interface is -setToolTipAtMousePoint:. This differs from
2937 // -setToolTip: in that the updated tooltip takes effect immediately,
2938 // without the user's having to move the mouse out of and back into the view.
2940 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
2941 // the view, which in turn requires overriding some internal tracking-rect
2942 // methods (to keep track of its owner & userdata, which need to be filled out
2943 // in the fake events.) --snej 7/6/09
2947 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
2948 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
2950 * Redistribution and use in source and binary forms, with or without
2951 * modification, are permitted provided that the following conditions
2954 * 1. Redistributions of source code must retain the above copyright
2955 * notice, this list of conditions and the following disclaimer.
2956 * 2. Redistributions in binary form must reproduce the above copyright
2957 * notice, this list of conditions and the following disclaimer in the
2958 * documentation and/or other materials provided with the distribution.
2959 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
2960 * its contributors may be used to endorse or promote products derived
2961 * from this software without specific prior written permission.
2963 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
2964 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2965 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2966 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
2967 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2968 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2969 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
2970 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2971 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2972 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2975 // Any non-zero value will do, but using something recognizable might help us
2977 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
2979 // Override of a public NSView method, replacing the inherited functionality.
2980 // See above for rationale.
2981 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
2983 userData:(void *)data
2984 assumeInside:(BOOL)assumeInside {
2985 DCHECK(trackingRectOwner_ == nil);
2986 trackingRectOwner_ = owner;
2987 trackingRectUserData_ = data;
2988 return kTrackingRectTag;
2991 // Override of (apparently) a private NSView method(!) See above for rationale.
2992 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
2994 userData:(void *)data
2995 assumeInside:(BOOL)assumeInside
2996 useTrackingNum:(int)tag {
2997 DCHECK(tag == 0 || tag == kTrackingRectTag);
2998 DCHECK(trackingRectOwner_ == nil);
2999 trackingRectOwner_ = owner;
3000 trackingRectUserData_ = data;
3001 return kTrackingRectTag;
3004 // Override of (apparently) a private NSView method(!) See above for rationale.
3005 - (void)_addTrackingRects:(NSRect *)rects
3007 userDataList:(void **)userDataList
3008 assumeInsideList:(BOOL *)assumeInsideList
3009 trackingNums:(NSTrackingRectTag *)trackingNums
3012 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3013 DCHECK(trackingRectOwner_ == nil);
3014 trackingRectOwner_ = owner;
3015 trackingRectUserData_ = userDataList[0];
3016 trackingNums[0] = kTrackingRectTag;
3019 // Override of a public NSView method, replacing the inherited functionality.
3020 // See above for rationale.
3021 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3025 if (tag == kTrackingRectTag) {
3026 trackingRectOwner_ = nil;
3030 if (tag == lastToolTipTag_) {
3031 [super removeTrackingRect:tag];
3032 lastToolTipTag_ = 0;
3036 // If any other tracking rect is being removed, we don't know how it was
3037 // created and it's possible there's a leak involved (see Radar 3500217).
3041 // Override of (apparently) a private NSView method(!)
3042 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3043 for (int i = 0; i < count; ++i) {
3047 DCHECK(tag == kTrackingRectTag);
3048 trackingRectOwner_ = nil;
3052 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3053 - (void)_sendToolTipMouseExited {
3054 // Nothing matters except window, trackingNumber, and userData.
3055 int windowNumber = [[self window] windowNumber];
3056 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3057 location:NSMakePoint(0, 0)
3060 windowNumber:windowNumber
3063 trackingNumber:kTrackingRectTag
3064 userData:trackingRectUserData_];
3065 [trackingRectOwner_ mouseExited:fakeEvent];
3068 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3069 - (void)_sendToolTipMouseEntered {
3070 // Nothing matters except window, trackingNumber, and userData.
3071 int windowNumber = [[self window] windowNumber];
3072 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3073 location:NSMakePoint(0, 0)
3076 windowNumber:windowNumber
3079 trackingNumber:kTrackingRectTag
3080 userData:trackingRectUserData_];
3081 [trackingRectOwner_ mouseEntered:fakeEvent];
3084 // Sets the view's current tooltip, to be displayed at the current mouse
3085 // location. (This does not make the tooltip appear -- as usual, it only
3086 // appears after a delay.) Pass null to remove the tooltip.
3087 - (void)setToolTipAtMousePoint:(NSString *)string {
3088 NSString *toolTip = [string length] == 0 ? nil : string;
3089 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3090 (!toolTip && !toolTip_)) {
3095 [self _sendToolTipMouseExited];
3098 toolTip_.reset([toolTip copy]);
3101 // See radar 3500217 for why we remove all tooltips
3102 // rather than just the single one we created.
3103 [self removeAllToolTips];
3104 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3105 lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3108 [self _sendToolTipMouseEntered];
3112 // NSView calls this to get the text when displaying the tooltip.
3113 - (NSString *)view:(NSView *)view
3114 stringForToolTip:(NSToolTipTag)tag
3115 point:(NSPoint)point
3116 userData:(void *)data {
3117 return [[toolTip_ copy] autorelease];
3120 // Below is our NSTextInputClient implementation.
3122 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3123 // functions to process this event.
3125 // [WebHTMLView keyDown] ->
3126 // EventHandler::keyEvent() ->
3128 // [WebEditorClient handleKeyboardEvent] ->
3129 // [WebHTMLView _interceptEditingKeyEvent] ->
3130 // [NSResponder interpretKeyEvents] ->
3131 // [WebHTMLView insertText] ->
3132 // Editor::insertText()
3134 // Unfortunately, it is hard for Chromium to use this implementation because
3135 // it causes key-typing jank.
3136 // RenderWidgetHostViewMac is running in a browser process. On the other
3137 // hand, Editor and EventHandler are running in a renderer process.
3138 // So, if we used this implementation, a NSKeyDown event is dispatched to
3139 // the following functions of Chromium.
3141 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3142 // |Sync IPC (KeyDown)| (*1) ->
3143 // EventHandler::keyEvent() (renderer) ->
3145 // EditorClientImpl::handleKeyboardEvent() (renderer) ->
3146 // |Sync IPC| (*2) ->
3147 // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3148 // [self interpretKeyEvents] ->
3149 // [RenderWidgetHostViewMac insertText] (browser) ->
3151 // Editor::insertText() (renderer)
3153 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3154 // result of EventHandler::keyEvent().
3155 // (*2) we need to wait until this call finishes since WebEditorClient uses
3156 // the result of [WebHTMLView _interceptEditingKeyEvent].
3158 // This needs many sync IPC messages sent between a browser and a renderer for
3159 // each key event, which would probably result in key-typing jank.
3160 // To avoid this problem, this implementation processes key events (and input
3161 // method events) totally in a browser process and sends asynchronous input
3162 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3163 // renderer process.
3165 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3166 // |Async IPC (RawKeyDown)| ->
3167 // [self interpretKeyEvents] ->
3168 // [RenderWidgetHostViewMac insertText] (browser) ->
3169 // |Async IPC (Char)| ->
3170 // Editor::insertText() (renderer)
3172 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3173 // make any key-typing jank. --hbono 7/23/09
3176 extern NSString *NSTextInputReplacementRangeAttributeName;
3179 - (NSArray *)validAttributesForMarkedText {
3180 // This code is just copied from WebKit except renaming variables.
3181 if (!validAttributesForMarkedText_) {
3182 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3183 NSUnderlineStyleAttributeName,
3184 NSUnderlineColorAttributeName,
3185 NSMarkedClauseSegmentAttributeName,
3186 NSTextInputReplacementRangeAttributeName,
3189 return validAttributesForMarkedText_.get();
3192 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3193 DCHECK([self window]);
3194 // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3195 // coordinates (upper left origin). Scroll offsets will be taken care of in
3197 thePoint = [[self window] convertScreenToBase:thePoint];
3198 thePoint = [self convertPoint:thePoint fromView:nil];
3199 thePoint.y = NSHeight([self frame]) - thePoint.y;
3202 TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3203 renderWidgetHostView_->render_widget_host_,
3204 gfx::Point(thePoint.x, thePoint.y));
3208 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3209 actualRange:(NSRangePointer)actualRange {
3211 if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3215 rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3216 renderWidgetHostView_->render_widget_host_, theRange);
3218 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3220 *actualRange = theRange;
3223 // The returned rectangle is in WebKit coordinates (upper left origin), so
3224 // flip the coordinate system.
3225 NSRect viewFrame = [self frame];
3226 rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3230 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3231 actualRange:(NSRangePointer)actualRange {
3232 NSRect rect = [self firstViewRectForCharacterRange:theRange
3233 actualRange:actualRange];
3235 // Convert into screen coordinates for return.
3236 rect = [self convertRect:rect toView:nil];
3237 rect.origin = [[self window] convertBaseToScreen:rect.origin];
3241 - (NSRange)markedRange {
3242 // An input method calls this method to check if an application really has
3243 // a text being composed when hasMarkedText call returns true.
3244 // Returns the range saved in the setMarkedText method so the input method
3245 // calls the setMarkedText method and we can update the composition node
3246 // there. (When this method returns an empty range, the input method doesn't
3247 // call the setMarkedText method.)
3248 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3251 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3252 actualRange:(NSRangePointer)actualRange {
3253 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3255 *actualRange = range;
3256 NSAttributedString* str =
3257 TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3258 renderWidgetHostView_->render_widget_host_, range);
3262 - (NSInteger)conversationIdentifier {
3263 return reinterpret_cast<NSInteger>(self);
3266 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3267 // nil when the caret is in non-editable content or password box to avoid
3268 // making input methods do their work.
3269 - (NSTextInputContext *)inputContext {
3270 if (focusedPluginIdentifier_ != -1)
3271 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3273 switch(renderWidgetHostView_->text_input_type_) {
3274 case ui::TEXT_INPUT_TYPE_NONE:
3275 case ui::TEXT_INPUT_TYPE_PASSWORD:
3278 return [super inputContext];
3282 - (BOOL)hasMarkedText {
3283 // An input method calls this function to figure out whether or not an
3284 // application is really composing a text. If it is composing, it calls
3285 // the markedRange method, and maybe calls the setMarkedText method.
3286 // It seems an input method usually calls this function when it is about to
3287 // cancel an ongoing composition. If an application has a non-empty marked
3288 // range, it calls the setMarkedText method to delete the range.
3289 return hasMarkedText_;
3292 - (void)unmarkText {
3293 // Delete the composition node of the renderer and finish an ongoing
3295 // It seems an input method calls the setMarkedText method and set an empty
3296 // text when it cancels an ongoing composition, i.e. I have never seen an
3297 // input method calls this method.
3298 hasMarkedText_ = NO;
3299 markedText_.clear();
3300 underlines_.clear();
3302 // If we are handling a key down event, then ConfirmComposition() will be
3303 // called in keyEvent: method.
3304 if (!handlingKeyDown_)
3305 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition();
3307 unmarkTextCalled_ = YES;
3310 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3311 replacementRange:(NSRange)replacementRange {
3312 // An input method updates the composition string.
3313 // We send the given text and range to the renderer so it can update the
3314 // composition node of WebKit.
3315 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3316 // the full web content.
3317 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3318 NSString* im_text = isAttributedString ? [string string] : string;
3319 int length = [im_text length];
3321 // |markedRange_| will get set on a callback from ImeSetComposition().
3322 selectedRange_ = newSelRange;
3323 markedText_ = base::SysNSStringToUTF16(im_text);
3324 hasMarkedText_ = (length > 0);
3326 underlines_.clear();
3327 if (isAttributedString) {
3328 ExtractUnderlines(string, &underlines_);
3330 // Use a thin black underline by default.
3331 underlines_.push_back(
3332 WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false));
3335 // If we are handling a key down event, then SetComposition() will be
3336 // called in keyEvent: method.
3337 // Input methods of Mac use setMarkedText calls with an empty text to cancel
3338 // an ongoing composition. So, we should check whether or not the given text
3339 // is empty to update the input method state. (Our input method backend can
3340 // automatically cancels an ongoing composition when we send an empty text.
3341 // So, it is OK to send an empty text to the renderer.)
3342 if (!handlingKeyDown_) {
3343 renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3344 markedText_, underlines_,
3345 newSelRange.location, NSMaxRange(newSelRange));
3349 - (void)doCommandBySelector:(SEL)selector {
3350 // An input method calls this function to dispatch an editing command to be
3351 // handled by this view.
3352 if (selector == @selector(noop:))
3355 std::string command(
3356 [RenderWidgetHostViewMacEditCommandHelper::
3357 CommandNameForSelector(selector) UTF8String]);
3359 // If this method is called when handling a key down event, then we need to
3360 // handle the command in the key event handler. Otherwise we can just handle
3362 if (handlingKeyDown_) {
3363 hasEditCommands_ = YES;
3364 // We ignore commands that insert characters, because this was causing
3365 // strange behavior (e.g. tab always inserted a tab rather than moving to
3366 // the next field on the page).
3367 if (!StartsWithASCII(command, "insert", false))
3368 editCommands_.push_back(EditCommand(command, ""));
3370 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3371 rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
3376 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
3377 // An input method has characters to be inserted.
3378 // Same as Linux, Mac calls this method not only:
3379 // * when an input method finishs composing text, but also;
3380 // * when we type an ASCII character (without using input methods).
3381 // When we aren't using input methods, we should send the given character as
3382 // a Char event so it is dispatched to an onkeypress() event handler of
3384 // On the other hand, when we are using input methods, we should send the
3385 // given characters as an input method event and prevent the characters from
3386 // being dispatched to onkeypress() event handlers.
3387 // Text inserting might be initiated by other source instead of keyboard
3388 // events, such as the Characters dialog. In this case the text should be
3389 // sent as an input method event as well.
3390 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3391 // the full web content.
3392 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3393 NSString* im_text = isAttributedString ? [string string] : string;
3394 if (handlingKeyDown_) {
3395 textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
3397 ui::Range replacement_range(replacementRange);
3398 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3399 base::SysNSStringToUTF16(im_text), replacement_range);
3402 // Inserting text will delete all marked text automatically.
3403 hasMarkedText_ = NO;
3406 - (void)insertText:(id)string {
3407 [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
3410 - (void)viewDidMoveToWindow {
3412 [self updateTabBackingStoreScaleFactor];
3414 if (canBeKeyView_) {
3415 NSWindow* newWindow = [self window];
3416 // Pointer comparison only, since we don't know if lastWindow_ is still
3419 // If we move into a new window, refresh the frame information. We
3420 // don't need to do it if it was the same window as it used to be in,
3421 // since that case is covered by WasShown(). We only want to do this for
3422 // real browser views, not popups.
3423 if (newWindow != lastWindow_) {
3424 lastWindow_ = newWindow;
3425 renderWidgetHostView_->WindowFrameChanged();
3430 // If we switch windows (or are removed from the view hierarchy), cancel any
3431 // open mouse-downs.
3432 if (hasOpenMouseDown_) {
3433 WebMouseEvent event;
3434 event.type = WebInputEvent::MouseUp;
3435 event.button = WebMouseEvent::ButtonLeft;
3436 renderWidgetHostView_->ForwardMouseEvent(event);
3438 hasOpenMouseDown_ = NO;
3441 // Resize the view's layers to match the new window size.
3442 ScopedCAActionDisabler disabler;
3443 [renderWidgetHostView_->software_layer_
3444 setFrame:NSRectToCGRect([self bounds])];
3445 [renderWidgetHostView_->compositing_iosurface_layer_
3446 setFrame:NSRectToCGRect([self bounds])];
3449 - (void)undo:(id)sender {
3450 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3451 static_cast<RenderViewHostImpl*>(
3452 renderWidgetHostView_->render_widget_host_)->Undo();
3456 - (void)redo:(id)sender {
3457 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3458 static_cast<RenderViewHostImpl*>(
3459 renderWidgetHostView_->render_widget_host_)->Redo();
3463 - (void)cut:(id)sender {
3464 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3465 static_cast<RenderViewHostImpl*>(
3466 renderWidgetHostView_->render_widget_host_)->Cut();
3470 - (void)copy:(id)sender {
3471 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3472 static_cast<RenderViewHostImpl*>(
3473 renderWidgetHostView_->render_widget_host_)->Copy();
3477 - (void)copyToFindPboard:(id)sender {
3478 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3479 static_cast<RenderViewHostImpl*>(
3480 renderWidgetHostView_->render_widget_host_)->CopyToFindPboard();
3484 - (void)paste:(id)sender {
3485 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3486 static_cast<RenderViewHostImpl*>(
3487 renderWidgetHostView_->render_widget_host_)->Paste();
3491 - (void)pasteAndMatchStyle:(id)sender {
3492 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3493 static_cast<RenderViewHostImpl*>(
3494 renderWidgetHostView_->render_widget_host_)->PasteAndMatchStyle();
3498 - (void)selectAll:(id)sender {
3499 // editCommand_helper_ adds implementations for most NSResponder methods
3500 // dynamically. But the renderer side only sends selection results back to
3501 // the browser if they were triggered by a keyboard event or went through
3502 // one of the Select methods on RWH. Since selectAll: is called from the
3503 // menu handler, neither is true.
3504 // Explicitly call SelectAll() here to make sure the renderer returns
3505 // selection results.
3506 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) {
3507 static_cast<RenderViewHostImpl*>(
3508 renderWidgetHostView_->render_widget_host_)->SelectAll();
3512 - (void)startSpeaking:(id)sender {
3513 renderWidgetHostView_->SpeakSelection();
3516 - (void)stopSpeaking:(id)sender {
3517 renderWidgetHostView_->StopSpeaking();
3520 - (void)cancelComposition {
3521 if (!hasMarkedText_)
3524 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
3525 // doesn't call any NSTextInput functions, such as setMarkedText or
3526 // insertText. So, we need to send an IPC message to a renderer so it can
3527 // delete the composition node.
3528 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3529 [currentInputManager markedTextAbandoned:self];
3531 hasMarkedText_ = NO;
3532 // Should not call [self unmarkText] here, because it'll send unnecessary
3533 // cancel composition IPC message to the renderer.
3536 - (void)confirmComposition {
3537 if (!hasMarkedText_)
3540 if (renderWidgetHostView_->render_widget_host_)
3541 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition();
3543 [self cancelComposition];
3546 - (void)setPluginImeActive:(BOOL)active {
3547 if (active == pluginImeActive_)
3550 pluginImeActive_ = active;
3552 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
3553 renderWidgetHostView_->PluginImeCompositionCompleted(
3554 string16(), focusedPluginIdentifier_);
3558 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
3560 focusedPluginIdentifier_ = pluginId;
3561 else if (focusedPluginIdentifier_ == pluginId)
3562 focusedPluginIdentifier_ = -1;
3564 // Whenever plugin focus changes, plugin IME resets.
3565 [self setPluginImeActive:NO];
3568 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
3569 if (!pluginImeActive_)
3572 ComplexTextInputPanel* inputPanel =
3573 [ComplexTextInputPanel sharedComplexTextInputPanel];
3574 NSString* composited_string = nil;
3575 BOOL handled = [inputPanel interpretKeyEvent:event
3576 string:&composited_string];
3577 if (composited_string) {
3578 renderWidgetHostView_->PluginImeCompositionCompleted(
3579 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
3580 pluginImeActive_ = NO;
3585 - (void)checkForPluginImeCancellation {
3586 if (pluginImeActive_ &&
3587 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
3588 renderWidgetHostView_->PluginImeCompositionCompleted(
3589 string16(), focusedPluginIdentifier_);
3590 pluginImeActive_ = NO;
3594 // Overriding a NSResponder method to support application services.
3596 - (id)validRequestorForSendType:(NSString*)sendType
3597 returnType:(NSString*)returnType {
3599 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
3600 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
3601 BOOL hasText = !renderWidgetHostView_->selected_text().empty();
3603 renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
3605 if (sendTypeIsString && hasText && !returnType) {
3607 } else if (!sendType && returnTypeIsString && takesText) {
3609 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
3612 requestor = [super validRequestorForSendType:sendType
3613 returnType:returnType];
3618 - (void)viewWillStartLiveResize {
3619 [super viewWillStartLiveResize];
3620 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3622 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
3625 - (void)viewDidEndLiveResize {
3626 [super viewDidEndLiveResize];
3627 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
3629 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
3632 - (void)updateCursor:(NSCursor*)cursor {
3633 if (currentCursor_ == cursor)
3636 currentCursor_.reset([cursor retain]);
3637 [[self window] invalidateCursorRectsForView:self];
3640 - (void)popupWindowWillClose:(NSNotification *)notification {
3641 renderWidgetHostView_->KillSelf();
3644 - (void)updateSoftwareLayerScaleFactor {
3645 if (![renderWidgetHostView_->software_layer_
3646 respondsToSelector:@selector(setContentsScale:)])
3649 ScopedCAActionDisabler disabler;
3650 [renderWidgetHostView_->software_layer_ setContentsScale:deviceScaleFactor_];
3653 // Delegate methods for the software CALayer
3654 - (void)drawLayer:(CALayer*)layer
3655 inContext:(CGContextRef)context {
3656 DCHECK(renderWidgetHostView_->use_core_animation_);
3657 DCHECK([layer isEqual:renderWidgetHostView_->software_layer_]);
3659 CGRect clipRect = CGContextGetClipBoundingBox(context);
3661 if (!renderWidgetHostView_->render_widget_host_ ||
3662 renderWidgetHostView_->is_hidden()) {
3663 CGContextSetFillColorWithColor(context,
3664 CGColorGetConstantColor(kCGColorWhite));
3665 CGContextFillRect(context, clipRect);
3669 renderWidgetHostView_->about_to_validate_and_paint_ = true;
3670 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>(
3671 renderWidgetHostView_->render_widget_host_->GetBackingStore(true));
3672 renderWidgetHostView_->about_to_validate_and_paint_ = false;
3674 [self drawBackingStore:backingStore
3679 - (void)setNeedsDisplay:(BOOL)flag {
3680 [renderWidgetHostView_->software_layer_ setNeedsDisplay];
3681 [super setNeedsDisplay:flag];
3684 - (void)setNeedsDisplayInRect:(NSRect)rect {
3685 [renderWidgetHostView_->software_layer_
3686 setNeedsDisplayInRect:NSRectToCGRect(rect)];
3687 [super setNeedsDisplayInRect:rect];
3693 // Supporting application services
3695 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
3697 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
3698 types:(NSArray*)types {
3699 const std::string& str = renderWidgetHostView_->selected_text();
3700 if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
3702 base::scoped_nsobject<NSString> text(
3703 [[NSString alloc] initWithUTF8String:str.c_str()]);
3704 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
3705 [pboard declareTypes:toDeclare owner:nil];
3706 return [pboard setString:text forType:NSStringPboardType];
3709 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
3710 NSString *string = [pboard stringForType:NSStringPboardType];
3711 if (!string) return NO;
3713 // If the user is currently using an IME, confirm the IME input,
3714 // and then insert the text from the service, the same as TextEdit and Safari.
3715 [self confirmComposition];
3716 [self insertText:string];
3721 if (renderWidgetHostView_->use_core_animation_)
3723 return [super isOpaque];