1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
7 #import <objc/runtime.h>
9 #include <QuartzCore/QuartzCore.h>
11 #include "base/basictypes.h"
12 #include "base/bind.h"
13 #include "base/callback_helpers.h"
14 #include "base/command_line.h"
15 #include "base/debug/crash_logging.h"
16 #include "base/debug/trace_event.h"
17 #include "base/logging.h"
18 #include "base/mac/mac_util.h"
19 #include "base/mac/scoped_cftyperef.h"
20 #import "base/mac/scoped_nsobject.h"
21 #include "base/mac/sdk_forward_declarations.h"
22 #include "base/message_loop/message_loop.h"
23 #include "base/metrics/histogram.h"
24 #include "base/strings/string_util.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/strings/sys_string_conversions.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/sys_info.h"
29 #import "content/browser/accessibility/browser_accessibility_cocoa.h"
30 #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
31 #import "content/browser/cocoa/system_hotkey_helper_mac.h"
32 #import "content/browser/cocoa/system_hotkey_map.h"
33 #include "content/browser/compositor/resize_lock.h"
34 #include "content/browser/frame_host/frame_tree.h"
35 #include "content/browser/frame_host/frame_tree_node.h"
36 #include "content/browser/frame_host/render_frame_host_impl.h"
37 #include "content/browser/gpu/compositor_util.h"
38 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
39 #include "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
40 #include "content/browser/renderer_host/compositing_iosurface_mac.h"
41 #include "content/browser/renderer_host/render_widget_helper.h"
42 #include "content/browser/renderer_host/render_view_host_impl.h"
43 #import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h"
44 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
45 #import "content/browser/renderer_host/software_layer_mac.h"
46 #import "content/browser/renderer_host/text_input_client_mac.h"
47 #include "content/common/accessibility_messages.h"
48 #include "content/common/edit_command.h"
49 #include "content/common/gpu/gpu_messages.h"
50 #include "content/common/gpu/surface_handle_types_mac.h"
51 #include "content/common/input_messages.h"
52 #include "content/common/view_messages.h"
53 #include "content/common/webplugin_geometry.h"
54 #include "content/public/browser/browser_thread.h"
55 #include "content/public/browser/native_web_keyboard_event.h"
56 #include "content/public/browser/notification_service.h"
57 #include "content/public/browser/notification_types.h"
58 #include "content/public/browser/render_widget_host_view_frame_subscriber.h"
59 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
60 #include "content/public/browser/user_metrics.h"
61 #include "content/public/browser/web_contents.h"
62 #include "skia/ext/platform_canvas.h"
63 #include "third_party/WebKit/public/platform/WebScreenInfo.h"
64 #include "third_party/WebKit/public/web/WebInputEvent.h"
65 #include "third_party/WebKit/public/web/mac/WebInputEventFactory.h"
66 #import "third_party/mozilla/ComplexTextInputPanel.h"
67 #include "ui/base/cocoa/animation_utils.h"
68 #import "ui/base/cocoa/fullscreen_window_manager.h"
69 #import "ui/base/cocoa/underlay_opengl_hosting_window.h"
70 #include "ui/events/keycodes/keyboard_codes.h"
71 #include "ui/base/layout.h"
72 #include "ui/compositor/compositor.h"
73 #include "ui/compositor/layer.h"
74 #include "ui/gfx/display.h"
75 #include "ui/gfx/frame_time.h"
76 #include "ui/gfx/point.h"
77 #include "ui/gfx/rect_conversions.h"
78 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
79 #include "ui/gfx/screen.h"
80 #include "ui/gfx/size_conversions.h"
81 #include "ui/gl/gl_switches.h"
83 using content::BrowserAccessibility;
84 using content::BrowserAccessibilityManager;
85 using content::EditCommand;
86 using content::FrameTreeNode;
87 using content::NativeWebKeyboardEvent;
88 using content::RenderFrameHost;
89 using content::RenderViewHost;
90 using content::RenderViewHostImpl;
91 using content::RenderWidgetHostImpl;
92 using content::RenderWidgetHostViewMac;
93 using content::RenderWidgetHostViewMacEditCommandHelper;
94 using content::TextInputClientMac;
95 using content::WebContents;
96 using blink::WebInputEvent;
97 using blink::WebInputEventFactory;
98 using blink::WebMouseEvent;
99 using blink::WebMouseWheelEvent;
100 using blink::WebGestureEvent;
104 // Whether a keyboard event has been reserved by OSX.
105 BOOL EventIsReservedBySystem(NSEvent* event) {
106 content::SystemHotkeyHelperMac* helper =
107 content::SystemHotkeyHelperMac::GetInstance();
108 return helper->map()->IsEventReserved(event);
113 // These are not documented, so use only after checking -respondsToSelector:.
114 @interface NSApplication (UndocumentedSpeechMethods)
115 - (void)speakString:(NSString*)string;
116 - (void)stopSpeaking:(id)sender;
120 // Declare things that are part of the 10.7 SDK.
121 #if !defined(MAC_OS_X_VERSION_10_7) || \
122 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
124 static NSString* const NSWindowDidChangeBackingPropertiesNotification =
125 @"NSWindowDidChangeBackingPropertiesNotification";
129 // This method will return YES for OS X versions 10.7.3 and later, and NO
131 // Used to prevent a crash when building with the 10.7 SDK and accessing the
132 // notification below. See: http://crbug.com/260595.
133 static BOOL SupportsBackingPropertiesChangedNotification() {
134 // windowDidChangeBackingProperties: method has been added to the
135 // NSWindowDelegate protocol in 10.7.3, at the same time as the
136 // NSWindowDidChangeBackingPropertiesNotification notification was added.
137 // If the protocol contains this method description, the notification should
138 // be supported as well.
139 Protocol* windowDelegateProtocol = NSProtocolFromString(@"NSWindowDelegate");
140 struct objc_method_description methodDescription =
141 protocol_getMethodDescription(
142 windowDelegateProtocol,
143 @selector(windowDidChangeBackingProperties:),
147 // If the protocol does not contain the method, the returned method
148 // description is {NULL, NULL}
149 return methodDescription.name != NULL || methodDescription.types != NULL;
153 @interface RenderWidgetHostViewCocoa ()
154 @property(nonatomic, assign) NSRange selectedRange;
155 @property(nonatomic, assign) NSRange markedRange;
157 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event;
158 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r;
159 - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event
160 consumed:(BOOL)consumed;
162 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv;
163 - (void)windowDidChangeBackingProperties:(NSNotification*)notification;
164 - (void)windowChangedGlobalFrame:(NSNotification*)notification;
165 - (void)checkForPluginImeCancellation;
166 - (void)updateScreenProperties;
167 - (void)setResponderDelegate:
168 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate;
171 // A window subclass that allows the fullscreen window to become main and gain
172 // keyboard focus. This is only used for pepper flash. Normal fullscreen is
173 // handled by the browser.
174 @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow
177 @implementation PepperFlashFullscreenWindow
179 - (BOOL)canBecomeKeyWindow {
183 - (BOOL)canBecomeMainWindow {
189 @interface RenderWidgetPopupWindow : NSWindow {
190 // The event tap that allows monitoring of all events, to properly close with
191 // a click outside the bounds of the window.
196 @implementation RenderWidgetPopupWindow
198 - (id)initWithContentRect:(NSRect)contentRect
199 styleMask:(NSUInteger)windowStyle
200 backing:(NSBackingStoreType)bufferingType
201 defer:(BOOL)deferCreation {
202 if (self = [super initWithContentRect:contentRect
203 styleMask:windowStyle
204 backing:bufferingType
205 defer:deferCreation]) {
207 [self setBackgroundColor:[NSColor clearColor]];
208 [self startObservingClicks];
214 [self stopObservingClicks];
218 // Gets called when the menubar is clicked.
219 // Needed because the local event monitor doesn't see the click on the menubar.
220 - (void)beganTracking:(NSNotification*)notification {
224 // Install the callback.
225 - (void)startObservingClicks {
226 clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask
227 handler:^NSEvent* (NSEvent* event) {
228 if ([event window] == self)
230 NSEventType eventType = [event type];
231 if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown)
236 NSNotificationCenter* notificationCenter =
237 [NSNotificationCenter defaultCenter];
238 [notificationCenter addObserver:self
239 selector:@selector(beganTracking:)
240 name:NSMenuDidBeginTrackingNotification
241 object:[NSApp mainMenu]];
244 // Remove the callback.
245 - (void)stopObservingClicks {
249 [NSEvent removeMonitor:clickEventTap_];
250 clickEventTap_ = nil;
252 NSNotificationCenter* notificationCenter =
253 [NSNotificationCenter defaultCenter];
254 [notificationCenter removeObserver:self
255 name:NSMenuDidBeginTrackingNotification
256 object:[NSApp mainMenu]];
263 // Maximum number of characters we allow in a tooltip.
264 const size_t kMaxTooltipLength = 1024;
266 // TODO(suzhe): Upstream this function.
267 blink::WebColor WebColorFromNSColor(NSColor *color) {
269 [color getRed:&r green:&g blue:&b alpha:&a];
272 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
273 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
274 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
275 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
278 // Extract underline information from an attributed string. Mostly copied from
279 // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
280 void ExtractUnderlines(
281 NSAttributedString* string,
282 std::vector<blink::WebCompositionUnderline>* underlines) {
283 int length = [[string string] length];
287 NSDictionary* attrs = [string attributesAtIndex:i
288 longestEffectiveRange:&range
289 inRange:NSMakeRange(i, length - i)];
290 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
291 blink::WebColor color = SK_ColorBLACK;
292 if (NSColor *colorAttr =
293 [attrs objectForKey:NSUnderlineColorAttributeName]) {
294 color = WebColorFromNSColor(
295 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
297 underlines->push_back(
298 blink::WebCompositionUnderline(range.location,
301 [style intValue] > 1,
302 SK_ColorTRANSPARENT));
304 i = range.location + range.length;
308 // EnablePasswordInput() and DisablePasswordInput() are copied from
309 // enableSecureTextInput() and disableSecureTextInput() functions in
310 // third_party/WebKit/WebCore/platform/SecureTextInput.cpp
311 // But we don't call EnableSecureEventInput() and DisableSecureEventInput()
312 // here, because they are already called in webkit and they are system wide
314 void EnablePasswordInput() {
315 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
316 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag,
317 sizeof(CFArrayRef), &inputSources);
318 CFRelease(inputSources);
321 void DisablePasswordInput() {
322 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
325 // Calls to [NSScreen screens], required by FlipYFromRectToScreen and
326 // FlipNSRectToRectScreen, can take several milliseconds. Only re-compute this
327 // value when screen info changes.
328 // TODO(ccameron): An observer on every RWHVCocoa will set this to false
329 // on NSApplicationDidChangeScreenParametersNotification. Only one observer
331 bool g_screen_info_up_to_date = false;
333 float FlipYFromRectToScreen(float y, float rect_height) {
334 TRACE_EVENT0("browser", "FlipYFromRectToScreen");
335 static CGFloat screen_zero_height = 0;
336 if (!g_screen_info_up_to_date) {
337 if ([[NSScreen screens] count] > 0) {
339 [[[NSScreen screens] objectAtIndex:0] frame].size.height;
340 g_screen_info_up_to_date = true;
345 return screen_zero_height - y - rect_height;
348 // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
349 // left of the primary screen (Carbon coordinates), and stuffs it into a
351 gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
352 gfx::Rect new_rect(NSRectToCGRect(rect));
353 new_rect.set_y(FlipYFromRectToScreen(new_rect.y(), new_rect.height()));
357 // Returns the window that visually contains the given view. This is different
358 // from [view window] in the case of tab dragging, where the view's owning
359 // window is a floating panel attached to the actual browser window that the tab
360 // is visually part of.
361 NSWindow* ApparentWindowForView(NSView* view) {
362 // TODO(shess): In case of !window, the view has been removed from
363 // the view hierarchy because the tab isn't main. Could retrieve
364 // the information from the main tab for our window.
365 NSWindow* enclosing_window = [view window];
367 // See if this is a tab drag window. The width check is to distinguish that
368 // case from extension popup windows.
369 NSWindow* ancestor_window = [enclosing_window parentWindow];
370 if (ancestor_window && (NSWidth([enclosing_window frame]) ==
371 NSWidth([ancestor_window frame]))) {
372 enclosing_window = ancestor_window;
375 return enclosing_window;
378 blink::WebScreenInfo GetWebScreenInfo(NSView* view) {
379 gfx::Display display =
380 gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(view);
382 NSScreen* screen = [NSScreen deepestScreen];
384 blink::WebScreenInfo results;
386 results.deviceScaleFactor = static_cast<int>(display.device_scale_factor());
387 results.depth = NSBitsPerPixelFromDepth([screen depth]);
388 results.depthPerComponent = NSBitsPerSampleFromDepth([screen depth]);
389 results.isMonochrome =
390 [[screen colorSpace] colorSpaceModel] == NSGrayColorSpaceModel;
391 results.rect = display.bounds();
392 results.availableRect = display.work_area();
393 results.orientationAngle = display.RotationAsDegree();
394 results.orientationType =
395 content::RenderWidgetHostViewBase::GetOrientationTypeForDesktop(display);
400 void RemoveLayerFromSuperlayer(
401 base::scoped_nsobject<CompositingIOSurfaceLayer> layer) {
402 // Disable the fade-out animation as the layer is removed.
403 ScopedCAActionDisabler disabler;
404 [layer removeFromSuperlayer];
411 ////////////////////////////////////////////////////////////////////////////////
412 // DelegatedFrameHost, public:
414 ui::Compositor* RenderWidgetHostViewMac::GetCompositor() const {
415 if (browser_compositor_view_)
416 return browser_compositor_view_->GetCompositor();
420 ui::Layer* RenderWidgetHostViewMac::GetLayer() {
421 return root_layer_.get();
424 RenderWidgetHostImpl* RenderWidgetHostViewMac::GetHost() {
425 return render_widget_host_;
428 void RenderWidgetHostViewMac::SchedulePaintInRect(
429 const gfx::Rect& damage_rect_in_dip) {
431 GetLayer()->SchedulePaint(damage_rect_in_dip);
434 bool RenderWidgetHostViewMac::IsVisible() {
435 return !render_widget_host_->is_hidden();
438 gfx::Size RenderWidgetHostViewMac::DesiredFrameSize() {
439 return GetViewBounds().size();
442 float RenderWidgetHostViewMac::CurrentDeviceScaleFactor() {
443 return ViewScaleFactor();
446 gfx::Size RenderWidgetHostViewMac::ConvertViewSizeToPixel(
447 const gfx::Size& size) {
448 return gfx::ToEnclosingRect(gfx::ScaleRect(gfx::Rect(size),
449 ViewScaleFactor())).size();
452 scoped_ptr<ResizeLock> RenderWidgetHostViewMac::CreateResizeLock(
453 bool defer_compositor_lock) {
455 ResizeLock* lock = NULL;
456 return scoped_ptr<ResizeLock>(lock);
459 DelegatedFrameHost* RenderWidgetHostViewMac::GetDelegatedFrameHost() const {
460 return delegated_frame_host_.get();
463 ////////////////////////////////////////////////////////////////////////////////
464 // BrowserCompositorViewMacClient, public:
466 bool RenderWidgetHostViewMac::BrowserCompositorViewShouldAckImmediately()
468 // The logic for delegated and non-delegated rendering is the same.
469 return AcceleratedLayerShouldAckImmediately();
472 void RenderWidgetHostViewMac::BrowserCompositorViewFrameSwapped(
473 const std::vector<ui::LatencyInfo>& all_latency_info) {
474 if (!render_widget_host_)
476 for (auto latency_info : all_latency_info) {
477 latency_info.AddLatencyNumber(
478 ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
479 render_widget_host_->FrameSwapped(latency_info);
483 NSView* RenderWidgetHostViewMac::BrowserCompositorSuperview() {
487 ui::Layer* RenderWidgetHostViewMac::BrowserCompositorRootLayer() {
488 return root_layer_.get();
491 ///////////////////////////////////////////////////////////////////////////////
492 // RenderWidgetHostViewBase, public:
495 void RenderWidgetHostViewBase::GetDefaultScreenInfo(
496 blink::WebScreenInfo* results) {
497 *results = GetWebScreenInfo(NULL);
500 ///////////////////////////////////////////////////////////////////////////////
501 // RenderWidgetHostViewMac, public:
503 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
504 : render_widget_host_(RenderWidgetHostImpl::From(widget)),
505 text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
506 can_compose_inline_(true),
507 browser_compositor_view_placeholder_(
508 new BrowserCompositorViewPlaceholderMac),
509 backing_store_scale_factor_(1),
511 allow_pause_for_resize_or_repaint_(true),
513 fullscreen_parent_host_view_(NULL),
514 software_frame_weak_ptr_factory_(this) {
515 software_frame_manager_.reset(new SoftwareFrameManager(
516 software_frame_weak_ptr_factory_.GetWeakPtr()));
517 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_|
518 // goes away. Since we autorelease it, our caller must put
519 // |GetNativeView()| into the view hierarchy right after calling us.
520 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc]
521 initWithRenderWidgetHostViewMac:this] autorelease];
523 // Make this view host a solid white layer when there is no content ready to
525 background_layer_.reset([[CALayer alloc] init]);
527 setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
528 [cocoa_view_ setLayer:background_layer_];
529 [cocoa_view_ setWantsLayer:YES];
531 if (!IsDelegatedRendererEnabled()) {
532 // Add a flipped transparent layer as a child, so that we don't need to
533 // fiddle with the position of sub-layers -- they will always be at the
535 flipped_layer_.reset([[CALayer alloc] init]);
536 [flipped_layer_ setGeometryFlipped:YES];
538 setAutoresizingMask:kCALayerWidthSizable|kCALayerHeightSizable];
539 [background_layer_ addSublayer:flipped_layer_];
542 if (IsDelegatedRendererEnabled()) {
543 root_layer_.reset(new ui::Layer(ui::LAYER_TEXTURED));
544 delegated_frame_host_.reset(new DelegatedFrameHost(this));
547 gfx::Screen::GetScreenFor(cocoa_view_)->AddObserver(this);
549 render_widget_host_->SetView(this);
552 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
553 gfx::Screen::GetScreenFor(cocoa_view_)->RemoveObserver(this);
555 // This is being called from |cocoa_view_|'s destructor, so invalidate the
561 // Ensure that the browser compositor is destroyed in a safe order.
562 ShutdownBrowserCompositor();
564 // Make sure that the layer doesn't reach into the now-invalid object.
565 DestroyCompositedIOSurfaceAndLayer();
566 DestroySoftwareLayer();
568 // We are owned by RenderWidgetHostViewCocoa, so if we go away before the
569 // RenderWidgetHost does we need to tell it not to hold a stale pointer to
571 if (render_widget_host_)
572 render_widget_host_->SetView(NULL);
575 void RenderWidgetHostViewMac::SetDelegate(
576 NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
577 [cocoa_view_ setResponderDelegate:delegate];
580 void RenderWidgetHostViewMac::SetAllowOverlappingViews(bool overlapping) {
581 // TODO(ccameron): Remove callers of this function.
584 void RenderWidgetHostViewMac::SetAllowPauseForResizeOrRepaint(bool allow) {
585 allow_pause_for_resize_or_repaint_ = allow;
588 ///////////////////////////////////////////////////////////////////////////////
589 // RenderWidgetHostViewMac, RenderWidgetHostView implementation:
591 bool RenderWidgetHostViewMac::EnsureCompositedIOSurface() {
592 // If the context or the IOSurface's context has had an error, re-build
593 // everything from scratch.
594 if (compositing_iosurface_context_ &&
595 compositing_iosurface_context_->HasBeenPoisoned()) {
596 LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
597 << "context was poisoned";
600 if (compositing_iosurface_ &&
601 compositing_iosurface_->HasBeenPoisoned()) {
602 LOG(ERROR) << "Failing EnsureCompositedIOSurface because "
603 << "surface was poisoned";
607 int current_window_number =
608 CompositingIOSurfaceContext::kOffscreenContextWindowNumber;
609 bool new_surface_needed = !compositing_iosurface_;
610 bool new_context_needed =
611 !compositing_iosurface_context_ ||
612 (compositing_iosurface_context_ &&
613 compositing_iosurface_context_->window_number() !=
614 current_window_number);
616 if (!new_surface_needed && !new_context_needed)
619 // Create the GL context and shaders.
620 if (new_context_needed) {
621 scoped_refptr<CompositingIOSurfaceContext> new_context =
622 CompositingIOSurfaceContext::Get(current_window_number);
623 // Un-bind the GL context from this view before binding the new GL
624 // context. Having two GL contexts bound to a view will result in
625 // crashes and corruption.
626 // http://crbug.com/230883
628 LOG(ERROR) << "Failed to create CompositingIOSurfaceContext";
631 compositing_iosurface_context_ = new_context;
634 // Create the IOSurface texture.
635 if (new_surface_needed) {
636 compositing_iosurface_ = CompositingIOSurfaceMac::Create();
637 if (!compositing_iosurface_) {
638 LOG(ERROR) << "Failed to create CompositingIOSurface";
646 void RenderWidgetHostViewMac::EnsureBrowserCompositorView() {
647 if (!delegated_frame_host_)
649 if (browser_compositor_view_)
652 TRACE_EVENT0("browser",
653 "RenderWidgetHostViewMac::EnsureBrowserCompositorView");
655 browser_compositor_view_.reset(new BrowserCompositorViewMac(this));
656 delegated_frame_host_->AddedToWindow();
657 delegated_frame_host_->WasShown(ui::LatencyInfo());
660 void RenderWidgetHostViewMac::DestroyBrowserCompositorView() {
661 TRACE_EVENT0("browser",
662 "RenderWidgetHostViewMac::DestroyBrowserCompositorView");
663 if (!browser_compositor_view_)
666 // Marking the DelegatedFrameHost as removed from the window hierarchy is
667 // necessary to remove all connections to its old ui::Compositor.
668 delegated_frame_host_->WasHidden();
669 delegated_frame_host_->RemovingFromWindow();
670 browser_compositor_view_.reset();
673 void RenderWidgetHostViewMac::EnsureSoftwareLayer() {
674 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::EnsureSoftwareLayer");
678 software_layer_.reset([[SoftwareLayer alloc] init]);
679 DCHECK(software_layer_);
681 // Disable the fade-in animation as the layer is added.
682 ScopedCAActionDisabler disabler;
683 [flipped_layer_ addSublayer:software_layer_];
686 void RenderWidgetHostViewMac::DestroySoftwareLayer() {
687 if (!software_layer_)
690 // Disable the fade-out animation as the layer is removed.
691 ScopedCAActionDisabler disabler;
692 [software_layer_ removeFromSuperlayer];
693 software_layer_.reset();
696 void RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer() {
697 TRACE_EVENT0("browser",
698 "RenderWidgetHostViewMac::EnsureCompositedIOSurfaceLayer");
699 DCHECK(compositing_iosurface_context_);
700 if (compositing_iosurface_layer_)
703 compositing_iosurface_layer_.reset([[CompositingIOSurfaceLayer alloc]
704 initWithIOSurface:compositing_iosurface_
705 withScaleFactor:compositing_iosurface_->scale_factor()
707 DCHECK(compositing_iosurface_layer_);
709 // Disable the fade-in animation as the layer is added.
710 ScopedCAActionDisabler disabler;
711 [flipped_layer_ addSublayer:compositing_iosurface_layer_];
714 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceLayer(
715 DestroyCompositedIOSurfaceLayerBehavior destroy_layer_behavior) {
716 if (!compositing_iosurface_layer_)
719 if (destroy_layer_behavior == kRemoveLayerFromHierarchy) {
720 // Disable the fade-out animation as the layer is removed.
721 ScopedCAActionDisabler disabler;
722 [compositing_iosurface_layer_ removeFromSuperlayer];
724 [compositing_iosurface_layer_ resetClient];
725 compositing_iosurface_layer_.reset();
728 void RenderWidgetHostViewMac::DestroyCompositedIOSurfaceAndLayer() {
729 // Any pending frames will not be displayed, so ack them now.
730 SendPendingSwapAck();
732 DestroyCompositedIOSurfaceLayer(kRemoveLayerFromHierarchy);
733 compositing_iosurface_ = NULL;
734 compositing_iosurface_context_ = NULL;
737 bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
739 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
740 IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, OnPluginFocusChanged)
741 IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, OnStartPluginIme)
742 IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedTextCompleted,
743 OnGetRenderedTextCompleted)
744 IPC_MESSAGE_UNHANDLED(handled = false)
745 IPC_END_MESSAGE_MAP()
749 void RenderWidgetHostViewMac::InitAsChild(
750 gfx::NativeView parent_view) {
753 void RenderWidgetHostViewMac::InitAsPopup(
754 RenderWidgetHostView* parent_host_view,
755 const gfx::Rect& pos) {
756 bool activatable = popup_type_ == blink::WebPopupTypeNone;
757 [cocoa_view_ setCloseOnDeactivate:YES];
758 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
760 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
761 origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height());
763 popup_window_.reset([[RenderWidgetPopupWindow alloc]
764 initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
765 pos.width(), pos.height())
766 styleMask:NSBorderlessWindowMask
767 backing:NSBackingStoreBuffered
769 [popup_window_ setLevel:NSPopUpMenuWindowLevel];
770 [popup_window_ setReleasedWhenClosed:NO];
771 [popup_window_ makeKeyAndOrderFront:nil];
772 [[popup_window_ contentView] addSubview:cocoa_view_];
773 [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]];
774 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
775 [[NSNotificationCenter defaultCenter]
776 addObserver:cocoa_view_
777 selector:@selector(popupWindowWillClose:)
778 name:NSWindowWillCloseNotification
779 object:popup_window_];
782 // This function creates the fullscreen window and hides the dock and menubar if
783 // necessary. Note, this codepath is only used for pepper flash when
784 // pp::FlashFullScreen::SetFullscreen() is called. If
785 // pp::FullScreen::SetFullscreen() is called then the entire browser window
786 // will enter fullscreen instead.
787 void RenderWidgetHostViewMac::InitAsFullscreen(
788 RenderWidgetHostView* reference_host_view) {
789 fullscreen_parent_host_view_ =
790 static_cast<RenderWidgetHostViewMac*>(reference_host_view);
791 NSWindow* parent_window = nil;
792 if (reference_host_view)
793 parent_window = [reference_host_view->GetNativeView() window];
794 NSScreen* screen = [parent_window screen];
796 screen = [NSScreen mainScreen];
798 pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc]
799 initWithContentRect:[screen frame]
800 styleMask:NSBorderlessWindowMask
801 backing:NSBackingStoreBuffered
803 [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel];
804 [pepper_fullscreen_window_ setReleasedWhenClosed:NO];
805 [cocoa_view_ setCanBeKeyView:YES];
806 [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]];
807 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
808 // If the pepper fullscreen window isn't opaque then there are performance
809 // issues when it's on the discrete GPU and the Chrome window is being drawn
810 // to. http://crbug.com/171911
811 [pepper_fullscreen_window_ setOpaque:YES];
813 // Note that this forms a reference cycle between the fullscreen window and
814 // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_,
815 // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable.
816 // This cycle is normally broken when -keyEvent: receives an <esc> key, which
817 // explicitly calls Shutdown on the render_widget_host_, which calls
818 // Destroy() on RWHVMac, which drops the reference to
819 // pepper_fullscreen_window_.
820 [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_];
822 // Note that this keeps another reference to pepper_fullscreen_window_.
823 fullscreen_window_manager_.reset([[FullscreenWindowManager alloc]
824 initWithWindow:pepper_fullscreen_window_.get()
825 desiredScreen:screen]);
826 [fullscreen_window_manager_ enterFullscreenMode];
827 [pepper_fullscreen_window_ makeKeyAndOrderFront:nil];
830 void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() {
831 // See comment in InitAsFullscreen(): There is a reference cycle between
832 // rwhvmac and fullscreen window, which is usually broken by hitting <esc>.
833 // Tests that test pepper fullscreen mode without sending an <esc> event
834 // need to call this method to break the reference cycle.
835 [fullscreen_window_manager_ exitFullscreenMode];
836 fullscreen_window_manager_.reset();
837 [pepper_fullscreen_window_ close];
838 pepper_fullscreen_window_.reset();
841 int RenderWidgetHostViewMac::window_number() const {
842 NSWindow* window = [cocoa_view_ window];
845 return [window windowNumber];
848 float RenderWidgetHostViewMac::ViewScaleFactor() const {
849 return ui::GetScaleFactorForNativeView(cocoa_view_);
852 void RenderWidgetHostViewMac::UpdateDisplayLink() {
853 static bool is_vsync_disabled =
854 base::CommandLine::ForCurrentProcess()->HasSwitch(
855 switches::kDisableGpuVsync);
856 if (is_vsync_disabled)
859 NSScreen* screen = [[cocoa_view_ window] screen];
860 NSDictionary* screen_description = [screen deviceDescription];
861 NSNumber* screen_number = [screen_description objectForKey:@"NSScreenNumber"];
862 CGDirectDisplayID display_id = [screen_number unsignedIntValue];
864 display_link_ = DisplayLinkMac::GetForDisplay(display_id);
865 if (!display_link_) {
866 // Note that on some headless systems, the display link will fail to be
867 // created, so this should not be a fatal error.
868 LOG(ERROR) << "Failed to create display link.";
872 void RenderWidgetHostViewMac::SendVSyncParametersToRenderer() {
873 if (!render_widget_host_ || !display_link_)
876 if (!display_link_->GetVSyncParameters(&vsync_timebase_, &vsync_interval_)) {
877 vsync_timebase_ = base::TimeTicks();
878 vsync_interval_ = base::TimeDelta();
882 render_widget_host_->UpdateVSyncParameters(vsync_timebase_, vsync_interval_);
885 void RenderWidgetHostViewMac::SpeakText(const std::string& text) {
886 [NSApp speakString:base::SysUTF8ToNSString(text)];
889 void RenderWidgetHostViewMac::UpdateBackingStoreScaleFactor() {
890 if (!render_widget_host_)
893 float new_scale_factor = ui::GetScaleFactorForNativeView(cocoa_view_);
894 if (new_scale_factor == backing_store_scale_factor_)
896 backing_store_scale_factor_ = new_scale_factor;
898 render_widget_host_->NotifyScreenInfoChanged();
901 RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const {
902 return render_widget_host_;
905 void RenderWidgetHostViewMac::WasShown() {
906 if (!render_widget_host_->is_hidden())
909 ui::LatencyInfo renderer_latency_info;
910 if ((compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) ||
911 software_frame_manager_->HasCurrentFrame() ||
912 (delegated_frame_host_ && delegated_frame_host_->HasSavedFrame())) {
913 ui::LatencyInfo browser_latency_info;
914 browser_latency_info.AddLatencyNumber(
915 ui::TAB_SHOW_COMPONENT,
916 render_widget_host_->GetLatencyComponentId(),
918 pending_latency_info_.push_back(browser_latency_info);
920 renderer_latency_info.AddLatencyNumber(
921 ui::TAB_SHOW_COMPONENT,
922 render_widget_host_->GetLatencyComponentId(),
926 render_widget_host_->WasShown(renderer_latency_info);
927 software_frame_manager_->SetVisibility(true);
929 // If there is not a frame being currently drawn, kick one, so that the below
930 // pause will have a frame to wait on.
931 if (IsDelegatedRendererEnabled())
932 render_widget_host_->ScheduleComposite();
934 // Call setNeedsDisplay before pausing for new frames to come in -- if any
935 // do, and are drawn, then the needsDisplay bit will be cleared.
936 [compositing_iosurface_layer_ setNeedsDisplay];
937 PauseForPendingResizeOrRepaintsAndDraw();
940 void RenderWidgetHostViewMac::WasHidden() {
941 if (render_widget_host_->is_hidden())
944 // Any pending frames will not be displayed until this is shown again. Ack
946 SendPendingSwapAck();
948 DestroyBrowserCompositorView();
950 // If we have a renderer, then inform it that we are being hidden so it can
951 // reduce its resource utilization.
952 render_widget_host_->WasHidden();
953 software_frame_manager_->SetVisibility(false);
956 void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
957 gfx::Rect rect = GetViewBounds();
962 void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
963 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
964 // TODO(thakis): fix, http://crbug.com/73362
965 if (render_widget_host_->is_hidden())
968 // During the initial creation of the RenderWidgetHostView in
969 // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
970 // an empty size. In the Windows code flow, it is not ignored because
971 // subsequent sizing calls from the OS flow through TCVW::WasSized which calls
972 // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
973 // flags to keep things sized properly. On the other hand, if the size is not
974 // empty then this is a valid request for a pop-up.
975 if (rect.size().IsEmpty())
978 // Ignore the position of |rect| for non-popup rwhvs. This is because
979 // background tabs do not have a window, but the window is required for the
980 // coordinate conversions. Popups are always for a visible tab.
982 // Note: If |cocoa_view_| has been removed from the view hierarchy, it's still
983 // valid for resizing to be requested (e.g., during tab capture, to size the
984 // view to screen-capture resolution). In this case, simply treat the view as
985 // relative to the screen.
986 BOOL isRelativeToScreen = IsPopup() ||
987 ![[cocoa_view_ superview] isKindOfClass:[BaseView class]];
988 if (isRelativeToScreen) {
989 // The position of |rect| is screen coordinate system and we have to
990 // consider Cocoa coordinate system is upside-down and also multi-screen.
991 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
992 NSSize size = NSMakeSize(rect.width(), rect.height());
993 size = [cocoa_view_ convertSize:size toView:nil];
994 origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height);
995 NSRect frame = NSMakeRect(origin_global.x, origin_global.y,
996 size.width, size.height);
998 [popup_window_ setFrame:frame display:YES];
1000 [cocoa_view_ setFrame:frame];
1002 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
1003 gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
1004 rect2.set_width(rect.width());
1005 rect2.set_height(rect.height());
1006 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
1010 gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const {
1014 gfx::NativeViewId RenderWidgetHostViewMac::GetNativeViewId() const {
1015 return reinterpret_cast<gfx::NativeViewId>(GetNativeView());
1018 gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
1020 return static_cast<gfx::NativeViewAccessible>(NULL);
1023 void RenderWidgetHostViewMac::MovePluginWindows(
1024 const std::vector<WebPluginGeometry>& moves) {
1025 // Must be overridden, but unused on this platform. Core Animation
1026 // plugins are drawn by the GPU process (through the compositor),
1027 // and Core Graphics plugins are drawn by the renderer process.
1028 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1031 void RenderWidgetHostViewMac::Focus() {
1032 [[cocoa_view_ window] makeFirstResponder:cocoa_view_];
1035 void RenderWidgetHostViewMac::Blur() {
1037 [[cocoa_view_ window] makeFirstResponder:nil];
1040 bool RenderWidgetHostViewMac::HasFocus() const {
1041 return [[cocoa_view_ window] firstResponder] == cocoa_view_;
1044 bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const {
1045 if (delegated_frame_host_)
1046 return delegated_frame_host_->CanCopyToBitmap();
1048 return software_frame_manager_->HasCurrentFrame() ||
1049 (compositing_iosurface_ && compositing_iosurface_->HasIOSurface());
1052 void RenderWidgetHostViewMac::Show() {
1053 [cocoa_view_ setHidden:NO];
1058 void RenderWidgetHostViewMac::Hide() {
1059 [cocoa_view_ setHidden:YES];
1064 bool RenderWidgetHostViewMac::IsShowing() {
1065 return ![cocoa_view_ isHidden];
1068 gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const {
1069 NSRect bounds = [cocoa_view_ bounds];
1070 // TODO(shess): In case of !window, the view has been removed from
1071 // the view hierarchy because the tab isn't main. Could retrieve
1072 // the information from the main tab for our window.
1073 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1074 if (!enclosing_window)
1075 return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds)));
1077 bounds = [cocoa_view_ convertRect:bounds toView:nil];
1078 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin];
1079 return FlipNSRectToRectScreen(bounds);
1082 void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
1083 WebCursor web_cursor = cursor;
1084 [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
1087 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
1088 is_loading_ = is_loading;
1089 // If we ever decide to show the waiting cursor while the page is loading
1090 // like Chrome does on Windows, call |UpdateCursor()| here.
1093 void RenderWidgetHostViewMac::TextInputStateChanged(
1094 const ViewHostMsg_TextInputState_Params& params) {
1095 if (text_input_type_ != params.type ||
1096 can_compose_inline_ != params.can_compose_inline) {
1097 text_input_type_ = params.type;
1098 can_compose_inline_ = params.can_compose_inline;
1100 SetTextInputActive(true);
1102 // Let AppKit cache the new input context to make IMEs happy.
1103 // See http://crbug.com/73039.
1104 [NSApp updateWindows];
1107 UseInputWindow(TSMGetActiveDocument(), !can_compose_inline_);
1113 void RenderWidgetHostViewMac::ImeCancelComposition() {
1114 [cocoa_view_ cancelComposition];
1117 void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
1118 const gfx::Range& range,
1119 const std::vector<gfx::Rect>& character_bounds) {
1120 // The RangeChanged message is only sent with valid values. The current
1121 // caret position (start == end) will be sent if there is no IME range.
1122 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1123 composition_range_ = range;
1124 composition_bounds_ = character_bounds;
1127 void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status,
1132 void RenderWidgetHostViewMac::Destroy() {
1133 [[NSNotificationCenter defaultCenter]
1134 removeObserver:cocoa_view_
1135 name:NSWindowWillCloseNotification
1136 object:popup_window_];
1138 // We've been told to destroy.
1139 [cocoa_view_ retain];
1140 [cocoa_view_ removeFromSuperview];
1141 [cocoa_view_ autorelease];
1143 [popup_window_ close];
1144 popup_window_.autorelease();
1146 [fullscreen_window_manager_ exitFullscreenMode];
1147 fullscreen_window_manager_.reset();
1148 [pepper_fullscreen_window_ close];
1150 // This can be called as part of processing the window's responder
1151 // chain, for instance |-performKeyEquivalent:|. In that case the
1152 // object needs to survive until the stack unwinds.
1153 pepper_fullscreen_window_.autorelease();
1155 // Delete the delegated frame state, which will reach back into
1156 // render_widget_host_.
1157 ShutdownBrowserCompositor();
1159 // We get this call just before |render_widget_host_| deletes
1160 // itself. But we are owned by |cocoa_view_|, which may be retained
1161 // by some other code. Examples are WebContentsViewMac's
1162 // |latent_focus_view_| and TabWindowController's
1163 // |cachedContentView_|.
1164 render_widget_host_ = NULL;
1167 // Called from the renderer to tell us what the tooltip text should be. It
1168 // calls us frequently so we need to cache the value to prevent doing a lot
1170 void RenderWidgetHostViewMac::SetTooltipText(
1171 const base::string16& tooltip_text) {
1172 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) {
1173 tooltip_text_ = tooltip_text;
1175 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
1176 // Windows; we're just trying to be polite. Don't persist the trimmed
1177 // string, as then the comparison above will always fail and we'll try to
1178 // set it again every single time the mouse moves.
1179 base::string16 display_text = tooltip_text_;
1180 if (tooltip_text_.length() > kMaxTooltipLength)
1181 display_text = tooltip_text_.substr(0, kMaxTooltipLength);
1183 NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
1184 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
1188 bool RenderWidgetHostViewMac::SupportsSpeech() const {
1189 return [NSApp respondsToSelector:@selector(speakString:)] &&
1190 [NSApp respondsToSelector:@selector(stopSpeaking:)];
1193 void RenderWidgetHostViewMac::SpeakSelection() {
1194 if (![NSApp respondsToSelector:@selector(speakString:)])
1197 if (selected_text_.empty() && render_widget_host_) {
1198 // If there's no selection, speak all text. Send an asynchronous IPC
1199 // request for fetching all the text for a webcontent.
1200 // ViewMsg_GetRenderedTextCompleted is sent back to IPC Message receiver.
1201 render_widget_host_->Send(new ViewMsg_GetRenderedText(
1202 render_widget_host_->GetRoutingID()));
1206 SpeakText(selected_text_);
1209 bool RenderWidgetHostViewMac::IsSpeaking() const {
1210 return [NSApp respondsToSelector:@selector(isSpeaking)] &&
1214 void RenderWidgetHostViewMac::StopSpeaking() {
1215 if ([NSApp respondsToSelector:@selector(stopSpeaking:)])
1216 [NSApp stopSpeaking:cocoa_view_];
1220 // RenderWidgetHostViewCocoa uses the stored selection text,
1221 // which implements NSServicesRequests protocol.
1223 void RenderWidgetHostViewMac::SelectionChanged(const base::string16& text,
1225 const gfx::Range& range) {
1226 if (range.is_empty() || text.empty()) {
1227 selected_text_.clear();
1229 size_t pos = range.GetMin() - offset;
1230 size_t n = range.length();
1232 DCHECK(pos + n <= text.length()) << "The text can not fully cover range.";
1233 if (pos >= text.length()) {
1234 DCHECK(false) << "The text can not cover range.";
1237 selected_text_ = base::UTF16ToUTF8(text.substr(pos, n));
1240 [cocoa_view_ setSelectedRange:range.ToNSRange()];
1241 // Updates markedRange when there is no marked text so that retrieving
1242 // markedRange immediately after calling setMarkdText: returns the current
1244 if (![cocoa_view_ hasMarkedText]) {
1245 [cocoa_view_ setMarkedRange:range.ToNSRange()];
1248 RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
1251 void RenderWidgetHostViewMac::SelectionBoundsChanged(
1252 const ViewHostMsg_SelectionBounds_Params& params) {
1253 if (params.anchor_rect == params.focus_rect)
1254 caret_rect_ = params.anchor_rect;
1257 void RenderWidgetHostViewMac::ScrollOffsetChanged() {
1260 void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
1261 RenderWidgetHostViewBase::SetShowingContextMenu(showing);
1263 // Create a fake mouse event to inform the render widget that the mouse
1265 NSWindow* window = [cocoa_view_ window];
1266 // TODO(asvitkine): If the location outside of the event stream doesn't
1267 // correspond to the current event (due to delayed event processing), then
1268 // this may result in a cursor flicker if there are later mouse move events
1269 // in the pipeline. Find a way to use the mouse location from the event that
1270 // dismissed the context menu.
1271 NSPoint location = [window mouseLocationOutsideOfEventStream];
1272 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
1276 windowNumber:window_number()
1281 WebMouseEvent web_event =
1282 WebInputEventFactory::mouseEvent(event, cocoa_view_);
1284 web_event.type = WebInputEvent::MouseLeave;
1285 ForwardMouseEvent(web_event);
1288 bool RenderWidgetHostViewMac::IsPopup() const {
1289 return popup_type_ != blink::WebPopupTypeNone;
1292 void RenderWidgetHostViewMac::CopyFromCompositingSurface(
1293 const gfx::Rect& src_subrect,
1294 const gfx::Size& dst_size,
1295 const base::Callback<void(bool, const SkBitmap&)>& callback,
1296 const SkColorType color_type) {
1297 if (delegated_frame_host_) {
1298 delegated_frame_host_->CopyFromCompositingSurface(
1299 src_subrect, dst_size, callback, color_type);
1303 if (color_type != kN32_SkColorType) {
1305 callback.Run(false, SkBitmap());
1307 base::ScopedClosureRunner scoped_callback_runner(
1308 base::Bind(callback, false, SkBitmap()));
1309 float scale = ui::GetScaleFactorForNativeView(cocoa_view_);
1310 gfx::Size dst_pixel_size = gfx::ToFlooredSize(
1311 gfx::ScaleSize(dst_size, scale));
1312 if (compositing_iosurface_ && compositing_iosurface_->HasIOSurface()) {
1313 ignore_result(scoped_callback_runner.Release());
1314 compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
1317 } else if (software_frame_manager_->HasCurrentFrame()) {
1318 gfx::Rect src_pixel_rect = gfx::ToEnclosingRect(gfx::ScaleRect(
1320 software_frame_manager_->GetCurrentFrameDeviceScaleFactor()));
1321 SkBitmap source_bitmap;
1322 SkImageInfo source_info = SkImageInfo::MakeN32(
1323 software_frame_manager_->GetCurrentFrameSizeInPixels().width(),
1324 software_frame_manager_->GetCurrentFrameSizeInPixels().height(),
1325 kOpaque_SkAlphaType);
1326 source_bitmap.installPixels(
1328 software_frame_manager_->GetCurrentFramePixels(),
1329 source_info.minRowBytes());
1331 SkBitmap target_bitmap;
1332 if (!target_bitmap.allocN32Pixels(
1333 dst_pixel_size.width(), dst_pixel_size.height(), true))
1336 SkCanvas target_canvas(target_bitmap);
1337 SkRect src_pixel_skrect = SkRect::MakeXYWH(
1338 src_pixel_rect.x(), src_pixel_rect.y(),
1339 src_pixel_rect.width(), src_pixel_rect.height());
1340 target_canvas.drawBitmapRectToRect(
1343 SkRect::MakeXYWH(0, 0, dst_pixel_size.width(), dst_pixel_size.height()),
1345 SkCanvas::kNone_DrawBitmapRectFlag);
1347 ignore_result(scoped_callback_runner.Release());
1348 callback.Run(true, target_bitmap);
1350 callback.Run(false, SkBitmap());
1354 void RenderWidgetHostViewMac::CopyFromCompositingSurfaceToVideoFrame(
1355 const gfx::Rect& src_subrect,
1356 const scoped_refptr<media::VideoFrame>& target,
1357 const base::Callback<void(bool)>& callback) {
1358 if (delegated_frame_host_) {
1359 delegated_frame_host_->CopyFromCompositingSurfaceToVideoFrame(
1360 src_subrect, target, callback);
1364 base::ScopedClosureRunner scoped_callback_runner(base::Bind(callback, false));
1365 if (!compositing_iosurface_ || !compositing_iosurface_->HasIOSurface())
1368 if (!target.get()) {
1373 if (target->format() != media::VideoFrame::YV12 &&
1374 target->format() != media::VideoFrame::I420) {
1379 if (src_subrect.IsEmpty())
1382 ignore_result(scoped_callback_runner.Release());
1383 compositing_iosurface_->CopyToVideoFrame(
1384 GetScaledOpenGLPixelRect(src_subrect),
1389 bool RenderWidgetHostViewMac::CanCopyToVideoFrame() const {
1390 if (delegated_frame_host_)
1391 return delegated_frame_host_->CanCopyToVideoFrame();
1393 return (!software_frame_manager_->HasCurrentFrame() &&
1394 compositing_iosurface_ &&
1395 compositing_iosurface_->HasIOSurface());
1398 bool RenderWidgetHostViewMac::CanSubscribeFrame() const {
1399 if (delegated_frame_host_)
1400 return delegated_frame_host_->CanSubscribeFrame();
1402 return !software_frame_manager_->HasCurrentFrame();
1405 void RenderWidgetHostViewMac::BeginFrameSubscription(
1406 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
1407 if (delegated_frame_host_) {
1408 delegated_frame_host_->BeginFrameSubscription(subscriber.Pass());
1411 frame_subscriber_ = subscriber.Pass();
1414 void RenderWidgetHostViewMac::EndFrameSubscription() {
1415 if (delegated_frame_host_) {
1416 delegated_frame_host_->EndFrameSubscription();
1420 frame_subscriber_.reset();
1423 // Sets whether or not to accept first responder status.
1424 void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) {
1425 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag];
1428 void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
1429 if (render_widget_host_)
1430 render_widget_host_->ForwardMouseEvent(event);
1432 if (event.type == WebInputEvent::MouseLeave) {
1433 [cocoa_view_ setToolTipAtMousePoint:nil];
1434 tooltip_text_.clear();
1438 void RenderWidgetHostViewMac::KillSelf() {
1439 if (!weak_factory_.HasWeakPtrs()) {
1440 [cocoa_view_ setHidden:YES];
1441 base::MessageLoop::current()->PostTask(FROM_HERE,
1442 base::Bind(&RenderWidgetHostViewMac::ShutdownHost,
1443 weak_factory_.GetWeakPtr()));
1447 bool RenderWidgetHostViewMac::PostProcessEventForPluginIme(
1448 const NativeWebKeyboardEvent& event) {
1449 // Check WebInputEvent type since multiple types of events can be sent into
1450 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is
1451 // necessary to avoid double processing.
1452 // Also check the native type, since NSFlagsChanged is considered a key event
1453 // for WebKit purposes, but isn't considered a key event by the OS.
1454 if (event.type == WebInputEvent::RawKeyDown &&
1455 [event.os_event type] == NSKeyDown)
1456 return [cocoa_view_ postProcessEventForPluginIme:event.os_event];
1460 void RenderWidgetHostViewMac::PluginImeCompositionCompleted(
1461 const base::string16& text, int plugin_id) {
1462 if (render_widget_host_) {
1463 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted(
1464 render_widget_host_->GetRoutingID(), text, plugin_id));
1468 void RenderWidgetHostViewMac::CompositorSwapBuffers(
1469 IOSurfaceID surface_handle,
1470 const gfx::Rect& damage_rect,
1471 const gfx::Size& size,
1472 float surface_scale_factor,
1473 const std::vector<ui::LatencyInfo>& latency_info) {
1474 // Ensure that the frame be acked unless it is explicitly passed to a
1475 // display function.
1476 base::ScopedClosureRunner scoped_ack(
1477 base::Bind(&RenderWidgetHostViewMac::SendPendingSwapAck,
1478 weak_factory_.GetWeakPtr()));
1480 if (render_widget_host_->is_hidden())
1483 // Ensure that if this function exits before the frame is set up (but not
1484 // necessarily drawn) then it is treated as an error.
1485 base::ScopedClosureRunner scoped_error(
1486 base::Bind(&RenderWidgetHostViewMac::GotAcceleratedCompositingError,
1487 weak_factory_.GetWeakPtr()));
1489 AddPendingLatencyInfo(latency_info);
1491 // If compositing_iosurface_ exists and has been poisoned, destroy it
1492 // and allow EnsureCompositedIOSurface to recreate it below. Keep a
1493 // reference to the destroyed layer around until after the below call
1494 // to LayoutLayers, to avoid flickers.
1495 base::ScopedClosureRunner scoped_layer_remover;
1496 if (compositing_iosurface_context_ &&
1497 compositing_iosurface_context_->HasBeenPoisoned()) {
1498 scoped_layer_remover.Reset(
1499 base::Bind(RemoveLayerFromSuperlayer, compositing_iosurface_layer_));
1500 DestroyCompositedIOSurfaceLayer(kLeaveLayerInHierarchy);
1501 DestroyCompositedIOSurfaceAndLayer();
1504 // Ensure compositing_iosurface_ and compositing_iosurface_context_ be
1506 if (!EnsureCompositedIOSurface()) {
1507 LOG(ERROR) << "Failed EnsureCompositingIOSurface";
1511 // Make the context current and update the IOSurface with the handle
1512 // passed in by the swap command.
1514 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1515 compositing_iosurface_context_->cgl_context());
1516 if (!compositing_iosurface_->SetIOSurfaceWithContextCurrent(
1517 compositing_iosurface_context_, surface_handle, size,
1518 surface_scale_factor)) {
1519 LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
1524 // Grab video frames now that the IOSurface has been set up. Note that this
1525 // will be done in an offscreen context, so it is necessary to re-set the
1526 // current context afterward.
1527 bool frame_was_captured = false;
1528 if (frame_subscriber_) {
1529 const base::TimeTicks now = gfx::FrameTime::Now();
1530 base::TimeTicks present_time;
1531 if (vsync_timebase_.is_null() || vsync_interval_ <= base::TimeDelta()) {
1534 const int64 intervals_elapsed = (now - vsync_timebase_) / vsync_interval_;
1535 present_time = vsync_timebase_ +
1536 (intervals_elapsed + 1) * vsync_interval_;
1539 scoped_refptr<media::VideoFrame> frame;
1540 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
1541 if (frame_subscriber_->ShouldCaptureFrame(
1542 damage_rect, present_time, &frame, &callback)) {
1543 // Flush the context that updated the IOSurface, to ensure that the
1544 // context that does the copy picks up the correct version.
1546 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
1547 compositing_iosurface_context_->cgl_context());
1550 compositing_iosurface_->CopyToVideoFrame(
1551 gfx::Rect(size), frame,
1552 base::Bind(callback, present_time));
1553 frame_was_captured = true;
1557 // At this point the surface, its context, and its layer have been set up, so
1558 // don't generate an error (one may be generated when drawing).
1559 ignore_result(scoped_error.Release());
1561 GotAcceleratedFrame();
1563 gfx::Size window_size(NSSizeToCGSize([cocoa_view_ frame].size));
1564 if (window_size.IsEmpty()) {
1565 // setNeedsDisplay will never display and we'll never ack if the window is
1566 // empty, so ack now and don't bother calling setNeedsDisplay below.
1569 if (window_number() <= 0) {
1570 // It's normal for a backgrounded tab that is being captured to have no
1571 // window but not be hidden. Immediately ack the frame, and don't try to
1573 if (frame_was_captured)
1576 // If this frame was not captured, there is likely some sort of bug. Ack
1577 // the frame and hope for the best. Because the IOSurface and layer are
1578 // populated, it will likely be displayed when the view is added to a
1579 // window's hierarchy.
1581 // TODO(shess) If the view does not have a window, or the window
1582 // does not have backing, the IOSurface will log "invalid drawable"
1583 // in -setView:. It is not clear how this code is reached with such
1584 // a case, so record some info into breakpad (some subset of
1585 // browsers are likely to crash later for unrelated reasons).
1586 // http://crbug.com/148882
1587 const char* const kCrashKey = "rwhvm_window";
1588 NSWindow* window = [cocoa_view_ window];
1590 base::debug::SetCrashKeyValue(kCrashKey, "Missing window");
1593 base::StringPrintf("window %s delegate %s controller %s",
1594 object_getClassName(window),
1595 object_getClassName([window delegate]),
1596 object_getClassName([window windowController]));
1597 base::debug::SetCrashKeyValue(kCrashKey, value);
1602 // If we reach here, then the frame will be displayed by a future draw
1603 // call, so don't make the callback.
1604 ignore_result(scoped_ack.Release());
1605 DCHECK(compositing_iosurface_layer_);
1606 [compositing_iosurface_layer_ gotNewFrame];
1608 // Try to finish previous copy requests after draw to get better pipelining.
1609 if (compositing_iosurface_)
1610 compositing_iosurface_->CheckIfAllCopiesAreFinished(false);
1612 // The IOSurface's size may have changed, so re-layout the layers to take
1613 // this into account. This may force an immediate draw.
1617 void RenderWidgetHostViewMac::GotAcceleratedCompositingError() {
1618 LOG(ERROR) << "Encountered accelerated compositing error";
1619 base::MessageLoop::current()->PostTask(
1621 base::Bind(&RenderWidgetHostViewMac::DestroyCompositingStateOnError,
1622 weak_factory_.GetWeakPtr()));
1625 void RenderWidgetHostViewMac::DestroyCompositingStateOnError() {
1626 // This should be called with a clean stack. Make sure that no context is
1628 DCHECK(!CGLGetCurrentContext());
1630 // The existing GL contexts may be in a bad state, so don't re-use any of the
1631 // existing ones anymore, rather, allocate new ones.
1632 if (compositing_iosurface_context_)
1633 compositing_iosurface_context_->PoisonContextAndSharegroup();
1635 DestroyCompositedIOSurfaceAndLayer();
1637 // Request that a new frame be generated and dirty the view.
1638 if (render_widget_host_)
1639 render_widget_host_->ScheduleComposite();
1640 [cocoa_view_ setNeedsDisplay:YES];
1642 // TODO(ccameron): It may be a good idea to request that the renderer recreate
1643 // its GL context as well, and fall back to software if this happens
1647 bool RenderWidgetHostViewMac::GetLineBreakIndex(
1648 const std::vector<gfx::Rect>& bounds,
1649 const gfx::Range& range,
1650 size_t* line_break_point) {
1651 DCHECK(line_break_point);
1652 if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
1655 // We can't check line breaking completely from only rectangle array. Thus we
1656 // assume the line breaking as the next character's y offset is larger than
1657 // a threshold. Currently the threshold is determined as minimum y offset plus
1658 // 75% of maximum height.
1659 // TODO(nona): Check the threshold is reliable or not.
1660 // TODO(nona): Bidi support.
1661 const size_t loop_end_idx = std::min(bounds.size(), range.end());
1663 int min_y_offset = kint32max;
1664 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1665 max_height = std::max(max_height, bounds[idx].height());
1666 min_y_offset = std::min(min_y_offset, bounds[idx].y());
1668 int line_break_threshold = min_y_offset + (max_height * 3 / 4);
1669 for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
1670 if (bounds[idx].y() > line_break_threshold) {
1671 *line_break_point = idx;
1678 gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
1679 const gfx::Range& range,
1680 gfx::Range* actual_range) {
1681 DCHECK(actual_range);
1682 DCHECK(!composition_bounds_.empty());
1683 DCHECK(range.start() <= composition_bounds_.size());
1684 DCHECK(range.end() <= composition_bounds_.size());
1686 if (range.is_empty()) {
1687 *actual_range = range;
1688 if (range.start() == composition_bounds_.size()) {
1689 return gfx::Rect(composition_bounds_[range.start() - 1].right(),
1690 composition_bounds_[range.start() - 1].y(),
1692 composition_bounds_[range.start() - 1].height());
1694 return gfx::Rect(composition_bounds_[range.start()].x(),
1695 composition_bounds_[range.start()].y(),
1697 composition_bounds_[range.start()].height());
1702 if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
1703 end_idx = range.end();
1705 *actual_range = gfx::Range(range.start(), end_idx);
1706 gfx::Rect rect = composition_bounds_[range.start()];
1707 for (size_t i = range.start() + 1; i < end_idx; ++i) {
1708 rect.Union(composition_bounds_[i]);
1713 gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
1714 const gfx::Range& request_range) {
1715 if (composition_range_.is_empty())
1716 return gfx::Range::InvalidRange();
1718 if (request_range.is_reversed())
1719 return gfx::Range::InvalidRange();
1721 if (request_range.start() < composition_range_.start() ||
1722 request_range.start() > composition_range_.end() ||
1723 request_range.end() > composition_range_.end()) {
1724 return gfx::Range::InvalidRange();
1728 request_range.start() - composition_range_.start(),
1729 request_range.end() - composition_range_.start());
1732 WebContents* RenderWidgetHostViewMac::GetWebContents() {
1733 if (!render_widget_host_->IsRenderView())
1736 return WebContents::FromRenderViewHost(
1737 RenderViewHost::From(render_widget_host_));
1740 bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
1743 NSRange* actual_range) {
1745 // This exists to make IMEs more responsive, see http://crbug.com/115920
1746 TRACE_EVENT0("browser",
1747 "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
1749 // If requested range is same as caret location, we can just return it.
1750 if (selection_range_.is_empty() && gfx::Range(range) == selection_range_) {
1752 *actual_range = range;
1753 *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
1757 const gfx::Range request_range_in_composition =
1758 ConvertCharacterRangeToCompositionRange(gfx::Range(range));
1759 if (request_range_in_composition == gfx::Range::InvalidRange())
1762 // If firstRectForCharacterRange in WebFrame is failed in renderer,
1763 // ImeCompositionRangeChanged will be sent with empty vector.
1764 if (composition_bounds_.empty())
1766 DCHECK_EQ(composition_bounds_.size(), composition_range_.length());
1768 gfx::Range ui_actual_range;
1769 *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
1770 request_range_in_composition,
1771 &ui_actual_range).ToCGRect());
1773 *actual_range = gfx::Range(
1774 composition_range_.start() + ui_actual_range.start(),
1775 composition_range_.start() + ui_actual_range.end()).ToNSRange();
1780 void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
1781 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
1783 TRACE_EVENT0("browser",
1784 "RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped");
1785 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1787 AddPendingSwapAck(params.route_id,
1789 compositing_iosurface_ ?
1790 compositing_iosurface_->GetRendererID() : 0);
1792 switch (GetSurfaceHandleType(params.surface_handle)) {
1793 case kSurfaceHandleTypeIOSurface: {
1794 IOSurfaceID io_surface_id = IOSurfaceIDFromSurfaceHandle(
1795 params.surface_handle);
1797 CompositorSwapBuffers(io_surface_id,
1800 params.scale_factor,
1801 params.latency_info);
1803 case kSurfaceHandleTypeCAContext: {
1804 // Disable the fade-out animation as the layer is added.
1805 ScopedCAActionDisabler disabler;
1807 CAContextID context_id = CAContextIDFromSurfaceHandle(
1808 params.surface_handle);
1810 // If if the layer has changed put the new layer in the hierarchy and
1811 // take the old one out.
1812 if ([remote_layer_host_ contextId] != context_id) {
1813 [remote_layer_host_ removeFromSuperlayer];
1815 remote_layer_host_.reset([[CALayerHost alloc] init]);
1816 [remote_layer_host_ setContextId:context_id];
1818 setAutoresizingMask:kCALayerMaxXMargin|kCALayerMaxYMargin];
1819 [flipped_layer_ addSublayer:remote_layer_host_];
1822 // Ack the frame immediately. Any GPU back pressure will be applied by
1823 // the remote layer from within the GPU process.
1824 SendPendingSwapAck();
1827 LOG(ERROR) << "Invalid surface handle type.";
1832 void RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer(
1833 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params,
1835 TRACE_EVENT0("browser",
1836 "RenderWidgetHostViewMac::AcceleratedSurfacePostSubBuffer");
1837 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1839 AddPendingSwapAck(params.route_id,
1841 compositing_iosurface_ ?
1842 compositing_iosurface_->GetRendererID() : 0);
1843 CompositorSwapBuffers(
1844 IOSurfaceIDFromSurfaceHandle(params.surface_handle),
1845 gfx::Rect(params.x, params.y, params.width, params.height),
1846 params.surface_size,
1847 params.surface_scale_factor,
1848 params.latency_info);
1851 void RenderWidgetHostViewMac::AcceleratedSurfaceSuspend() {
1852 if (render_widget_host_->is_hidden())
1853 DestroyCompositedIOSurfaceAndLayer();
1856 void RenderWidgetHostViewMac::AcceleratedSurfaceRelease() {
1857 DestroyCompositedIOSurfaceAndLayer();
1860 bool RenderWidgetHostViewMac::HasAcceleratedSurface(
1861 const gfx::Size& desired_size) {
1862 if (browser_compositor_view_)
1863 return browser_compositor_view_->HasFrameOfSize(desired_size);
1864 if (compositing_iosurface_) {
1865 return compositing_iosurface_->HasIOSurface() &&
1866 (desired_size.IsEmpty() ||
1867 compositing_iosurface_->dip_io_surface_size() == desired_size);
1869 if (software_frame_manager_->HasCurrentFrame()) {
1870 return (desired_size.IsEmpty() ||
1871 software_frame_manager_->GetCurrentFrameSizeInDIP() ==
1877 void RenderWidgetHostViewMac::OnSwapCompositorFrame(
1878 uint32 output_surface_id, scoped_ptr<cc::CompositorFrame> frame) {
1879 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::OnSwapCompositorFrame");
1881 if (frame->delegated_frame_data) {
1882 float scale_factor = frame->metadata.device_scale_factor;
1884 // Compute the frame size based on the root render pass rect size.
1885 cc::RenderPass* root_pass =
1886 frame->delegated_frame_data->render_pass_list.back();
1887 gfx::Size pixel_size = root_pass->output_rect.size();
1888 gfx::Size dip_size =
1889 ConvertSizeToDIP(scale_factor, pixel_size);
1891 root_layer_->SetBounds(gfx::Rect(dip_size));
1892 if (!render_widget_host_->is_hidden()) {
1893 EnsureBrowserCompositorView();
1894 browser_compositor_view_->GetCompositor()->SetScaleAndSize(
1895 scale_factor, pixel_size);
1898 SendVSyncParametersToRenderer();
1900 delegated_frame_host_->SwapDelegatedFrame(
1902 frame->delegated_frame_data.Pass(),
1903 frame->metadata.device_scale_factor,
1904 frame->metadata.latency_info);
1905 } else if (frame->software_frame_data) {
1906 if (!software_frame_manager_->SwapToNewFrame(
1908 frame->software_frame_data.get(),
1909 frame->metadata.device_scale_factor,
1910 render_widget_host_->GetProcess()->GetHandle())) {
1911 render_widget_host_->GetProcess()->ReceivedBadMessage();
1915 // Add latency info to report when the frame finishes drawing.
1916 AddPendingLatencyInfo(frame->metadata.latency_info);
1918 const void* pixels = software_frame_manager_->GetCurrentFramePixels();
1919 gfx::Size size_in_pixels =
1920 software_frame_manager_->GetCurrentFrameSizeInPixels();
1922 EnsureSoftwareLayer();
1923 [software_layer_ setContentsToData:pixels
1924 withRowBytes:4 * size_in_pixels.width()
1925 withPixelSize:size_in_pixels
1926 withScaleFactor:frame->metadata.device_scale_factor];
1928 // Send latency information to the host immediately, as there will be no
1929 // subsequent draw call in which to do so.
1930 SendPendingLatencyInfoToHost();
1934 cc::CompositorFrameAck ack;
1935 RenderWidgetHostImpl::SendSwapCompositorFrameAck(
1936 render_widget_host_->GetRoutingID(),
1937 software_frame_manager_->GetCurrentFrameOutputSurfaceId(),
1938 render_widget_host_->GetProcess()->GetID(),
1940 software_frame_manager_->SwapToNewFrameComplete(
1941 !render_widget_host_->is_hidden());
1943 // Notify observers, tab capture observers in particular, that a new
1944 // software frame has come in.
1945 NotificationService::current()->Notify(
1946 NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
1947 Source<RenderWidgetHost>(render_widget_host_),
1948 NotificationService::NoDetails());
1950 DLOG(ERROR) << "Received unexpected frame type.";
1952 base::UserMetricsAction("BadMessageTerminate_UnexpectedFrameType"));
1953 render_widget_host_->GetProcess()->ReceivedBadMessage();
1957 void RenderWidgetHostViewMac::AcceleratedSurfaceInitialized(int host_id,
1961 void RenderWidgetHostViewMac::GetScreenInfo(blink::WebScreenInfo* results) {
1962 *results = GetWebScreenInfo(GetNativeView());
1965 gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
1966 // TODO(shess): In case of !window, the view has been removed from
1967 // the view hierarchy because the tab isn't main. Could retrieve
1968 // the information from the main tab for our window.
1969 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_);
1970 if (!enclosing_window)
1973 NSRect bounds = [enclosing_window frame];
1974 return FlipNSRectToRectScreen(bounds);
1977 gfx::GLSurfaceHandle RenderWidgetHostViewMac::GetCompositingSurface() {
1978 // TODO(kbr): may be able to eliminate PluginWindowHandle argument
1979 // completely on Mac OS.
1980 return gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_TRANSPORT);
1983 bool RenderWidgetHostViewMac::LockMouse() {
1987 mouse_locked_ = true;
1989 // Lock position of mouse cursor and hide it.
1990 CGAssociateMouseAndMouseCursorPosition(NO);
1993 // Clear the tooltip window.
1994 SetTooltipText(base::string16());
1999 void RenderWidgetHostViewMac::UnlockMouse() {
2002 mouse_locked_ = false;
2004 // Unlock position of mouse cursor and unhide it.
2005 CGAssociateMouseAndMouseCursorPosition(YES);
2008 if (render_widget_host_)
2009 render_widget_host_->LostMouseLock();
2012 void RenderWidgetHostViewMac::WheelEventAck(
2013 const blink::WebMouseWheelEvent& event,
2014 InputEventAckState ack_result) {
2015 bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
2016 // Only record a wheel event as unhandled if JavaScript handlers got a chance
2017 // to see it (no-op wheel events are ignored by the event dispatcher)
2018 if (event.deltaX || event.deltaY)
2019 [cocoa_view_ processedWheelEvent:event consumed:consumed];
2022 bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
2023 if (render_widget_host_)
2024 return render_widget_host_->Send(message);
2029 void RenderWidgetHostViewMac::SoftwareFrameWasFreed(
2030 uint32 output_surface_id, unsigned frame_id) {
2031 if (!render_widget_host_)
2033 cc::CompositorFrameAck ack;
2034 ack.last_software_frame_id = frame_id;
2035 RenderWidgetHostImpl::SendReclaimCompositorResources(
2036 render_widget_host_->GetRoutingID(),
2038 render_widget_host_->GetProcess()->GetID(),
2042 void RenderWidgetHostViewMac::ReleaseReferencesToSoftwareFrame() {
2043 DestroySoftwareLayer();
2046 void RenderWidgetHostViewMac::ShutdownHost() {
2047 weak_factory_.InvalidateWeakPtrs();
2048 render_widget_host_->Shutdown();
2049 // Do not touch any members at this point, |this| has been deleted.
2052 void RenderWidgetHostViewMac::ShutdownBrowserCompositor() {
2053 DestroyBrowserCompositorView();
2054 delegated_frame_host_.reset();
2055 root_layer_.reset();
2056 browser_compositor_view_placeholder_.reset();
2059 void RenderWidgetHostViewMac::GotAcceleratedFrame() {
2060 EnsureCompositedIOSurfaceLayer();
2061 SendVSyncParametersToRenderer();
2063 // Delete software backingstore and layer.
2064 software_frame_manager_->DiscardCurrentFrame();
2065 DestroySoftwareLayer();
2068 void RenderWidgetHostViewMac::GotSoftwareFrame() {
2069 TRACE_EVENT0("browser", "RenderWidgetHostViewMac::GotSoftwareFrame");
2071 if (!render_widget_host_)
2074 EnsureSoftwareLayer();
2076 SendVSyncParametersToRenderer();
2078 // Draw the contents of the frame immediately. It is critical that this
2079 // happen before the frame be acked, otherwise the new frame will likely be
2080 // ready before the drawing is complete, thrashing the browser main thread.
2081 [software_layer_ displayIfNeeded];
2083 DestroyCompositedIOSurfaceAndLayer();
2086 void RenderWidgetHostViewMac::SetActive(bool active) {
2087 if (render_widget_host_) {
2088 render_widget_host_->SetActive(active);
2091 render_widget_host_->Focus();
2093 render_widget_host_->Blur();
2097 SetTextInputActive(active);
2099 [cocoa_view_ setPluginImeActive:NO];
2104 void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) {
2105 if (render_widget_host_) {
2106 render_widget_host_->Send(new ViewMsg_SetWindowVisibility(
2107 render_widget_host_->GetRoutingID(), visible));
2111 void RenderWidgetHostViewMac::WindowFrameChanged() {
2112 if (render_widget_host_) {
2113 render_widget_host_->Send(new ViewMsg_WindowFrameChanged(
2114 render_widget_host_->GetRoutingID(), GetBoundsInRootWindow(),
2119 void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
2120 RenderWidgetHostViewMacDictionaryHelper helper(this);
2121 helper.ShowDefinitionForSelection();
2124 void RenderWidgetHostViewMac::SetBackgroundOpaque(bool opaque) {
2125 RenderWidgetHostViewBase::SetBackgroundOpaque(opaque);
2126 if (render_widget_host_)
2127 render_widget_host_->SetBackgroundOpaque(opaque);
2130 BrowserAccessibilityManager*
2131 RenderWidgetHostViewMac::CreateBrowserAccessibilityManager(
2132 BrowserAccessibilityDelegate* delegate) {
2133 return new BrowserAccessibilityManagerMac(
2135 BrowserAccessibilityManagerMac::GetEmptyDocument(),
2139 gfx::Point RenderWidgetHostViewMac::AccessibilityOriginInScreen(
2140 const gfx::Rect& bounds) {
2141 NSPoint origin = NSMakePoint(bounds.x(), bounds.y());
2142 NSSize size = NSMakeSize(bounds.width(), bounds.height());
2143 origin.y = NSHeight([cocoa_view_ bounds]) - origin.y;
2144 NSPoint originInWindow = [cocoa_view_ convertPoint:origin toView:nil];
2145 NSPoint originInScreen =
2146 [[cocoa_view_ window] convertBaseToScreen:originInWindow];
2147 originInScreen.y = originInScreen.y - size.height;
2148 return gfx::Point(originInScreen.x, originInScreen.y);
2151 void RenderWidgetHostViewMac::AccessibilityShowMenu(const gfx::Point& point) {
2152 NSPoint location = NSMakePoint(point.x(), point.y());
2153 location = [[cocoa_view_ window] convertScreenToBase:location];
2154 NSEvent* fakeRightClick = [NSEvent
2155 mouseEventWithType:NSRightMouseDown
2159 windowNumber:[[cocoa_view_ window] windowNumber]
2160 context:[NSGraphicsContext currentContext]
2165 [cocoa_view_ mouseEvent:fakeRightClick];
2168 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
2170 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2171 EnablePasswordInput();
2173 DisablePasswordInput();
2175 if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
2176 DisablePasswordInput();
2180 void RenderWidgetHostViewMac::OnPluginFocusChanged(bool focused,
2182 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id];
2185 void RenderWidgetHostViewMac::OnStartPluginIme() {
2186 [cocoa_view_ setPluginImeActive:YES];
2189 void RenderWidgetHostViewMac::OnGetRenderedTextCompleted(
2190 const std::string& text) {
2194 gfx::Rect RenderWidgetHostViewMac::GetScaledOpenGLPixelRect(
2195 const gfx::Rect& rect) {
2196 gfx::Rect src_gl_subrect = rect;
2197 src_gl_subrect.set_y(GetViewBounds().height() - rect.bottom());
2199 return gfx::ToEnclosingRect(gfx::ScaleRect(src_gl_subrect,
2200 ViewScaleFactor()));
2203 void RenderWidgetHostViewMac::AddPendingLatencyInfo(
2204 const std::vector<ui::LatencyInfo>& latency_info) {
2205 for (size_t i = 0; i < latency_info.size(); i++) {
2206 pending_latency_info_.push_back(latency_info[i]);
2210 void RenderWidgetHostViewMac::SendPendingLatencyInfoToHost() {
2211 for (size_t i = 0; i < pending_latency_info_.size(); i++) {
2212 pending_latency_info_[i].AddLatencyNumber(
2213 ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
2214 render_widget_host_->FrameSwapped(pending_latency_info_[i]);
2216 pending_latency_info_.clear();
2219 void RenderWidgetHostViewMac::AddPendingSwapAck(
2220 int32 route_id, int gpu_host_id, int32 renderer_id) {
2221 // Note that multiple un-acked swaps can come in the event of a GPU process
2222 // loss. Drop the old acks.
2223 pending_swap_ack_.reset(new PendingSwapAck(
2224 route_id, gpu_host_id, renderer_id));
2227 void RenderWidgetHostViewMac::SendPendingSwapAck() {
2228 if (!pending_swap_ack_)
2231 AcceleratedSurfaceMsg_BufferPresented_Params ack_params;
2232 ack_params.sync_point = 0;
2233 ack_params.renderer_id = pending_swap_ack_->renderer_id;
2234 RenderWidgetHostImpl::AcknowledgeBufferPresent(pending_swap_ack_->route_id,
2235 pending_swap_ack_->gpu_host_id,
2237 pending_swap_ack_.reset();
2240 void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() {
2241 if (!render_widget_host_ || render_widget_host_->is_hidden())
2244 // Pausing for one view prevents others from receiving frames.
2245 // This may lead to large delays, causing overlaps. See crbug.com/352020.
2246 if (!allow_pause_for_resize_or_repaint_)
2249 // Ensure that all frames are acked before waiting for a frame to come in.
2250 // Note that we will draw a frame at the end of this function, so it is safe
2251 // to ack a never-drawn frame here.
2252 SendPendingSwapAck();
2254 // Wait for a frame of the right size to come in.
2255 if (browser_compositor_view_)
2256 browser_compositor_view_->BeginPumpingFrames();
2257 render_widget_host_->PauseForPendingResizeOrRepaints();
2258 if (browser_compositor_view_)
2259 browser_compositor_view_->EndPumpingFrames();
2261 // Immediately draw any frames that haven't been drawn yet. This is necessary
2262 // to keep the window and the window's contents in sync.
2263 [cocoa_view_ displayIfNeeded];
2264 [software_layer_ displayIfNeeded];
2265 [compositing_iosurface_layer_ displayIfNeededAndAck];
2268 void RenderWidgetHostViewMac::LayoutLayers() {
2269 if (delegated_frame_host_) {
2273 // Disable animation of the layer's resizing or change in contents scale.
2274 ScopedCAActionDisabler disabler;
2276 // Dynamically calling setContentsScale on a CAOpenGLLayer for which
2277 // setAsynchronous is dynamically toggled can result in flashes of corrupt
2278 // content. Work around this by replacing the entire layer when the scale
2280 if (compositing_iosurface_ &&
2281 [compositing_iosurface_layer_
2282 respondsToSelector:(@selector(contentsScale))]) {
2283 if (compositing_iosurface_->scale_factor() !=
2284 [compositing_iosurface_layer_ contentsScale]) {
2285 DestroyCompositedIOSurfaceLayer(kRemoveLayerFromHierarchy);
2286 EnsureCompositedIOSurfaceLayer();
2289 if (compositing_iosurface_ &&
2290 compositing_iosurface_->HasIOSurface() &&
2291 compositing_iosurface_layer_) {
2292 CGRect layer_bounds = CGRectMake(
2295 compositing_iosurface_->dip_io_surface_size().width(),
2296 compositing_iosurface_->dip_io_surface_size().height());
2297 bool bounds_changed = !CGRectEqualToRect(
2298 layer_bounds, [compositing_iosurface_layer_ bounds]);
2299 [compositing_iosurface_layer_ setBounds:layer_bounds];
2301 // If the bounds changed, then draw the frame immediately, to ensure that
2302 // content displayed is in sync with the window size.
2303 if (bounds_changed) {
2304 // Also, sometimes, especially when infobars are being removed, the
2305 // setNeedsDisplay calls are dropped on the floor, and stale content is
2306 // displayed. Calling displayIfNeeded will ensure that the right size
2307 // frame is drawn to the screen.
2308 // http://crbug.com/350817
2309 [compositing_iosurface_layer_ setNeedsDisplayAndDisplayAndAck];
2314 SkColorType RenderWidgetHostViewMac::PreferredReadbackFormat() {
2315 return kN32_SkColorType;
2318 ////////////////////////////////////////////////////////////////////////////////
2319 // CompositingIOSurfaceLayerClient, public:
2321 bool RenderWidgetHostViewMac::AcceleratedLayerShouldAckImmediately() const {
2322 // If vsync is disabled, then always draw and ack frames immediately.
2323 static bool is_vsync_disabled =
2324 base::CommandLine::ForCurrentProcess()->HasSwitch(
2325 switches::kDisableGpuVsync);
2326 if (is_vsync_disabled)
2329 // If the window is occluded, then this frame's display call may be severely
2330 // throttled. This is a good thing, unless tab capture may be active, because
2331 // the broadcast will be inappropriately throttled.
2332 // http://crbug.com/350410
2334 // If tab capture isn't active then only ack frames when we draw them.
2335 if (delegated_frame_host_) {
2336 if (!delegated_frame_host_->HasFrameSubscriber())
2339 if (!frame_subscriber_)
2343 NSWindow* window = [cocoa_view_ window];
2344 // If the view isn't even in the heirarchy then frames will never be drawn,
2345 // so ack them immediately.
2349 // Check the window occlusion API.
2350 if ([window respondsToSelector:@selector(occlusionState)]) {
2351 if ([window occlusionState] & NSWindowOcclusionStateVisible) {
2352 // If the window is visible then it is safe to wait until frames are
2353 // drawn to ack them.
2356 // If the window is occluded then frames may never be drawn, so ack them
2362 // If the window occlusion API is not present then ack frames when we draw
2367 void RenderWidgetHostViewMac::AcceleratedLayerDidDrawFrame() {
2368 if (!render_widget_host_)
2371 SendPendingLatencyInfoToHost();
2372 SendPendingSwapAck();
2375 void RenderWidgetHostViewMac::AcceleratedLayerHitError() {
2376 if (!render_widget_host_)
2378 // Perform all acks that would have been done if the frame had succeeded, to
2379 // un-block the renderer.
2380 AcceleratedLayerDidDrawFrame();
2381 GotAcceleratedCompositingError();
2384 ////////////////////////////////////////////////////////////////////////////////
2385 // gfx::DisplayObserver, public:
2387 void RenderWidgetHostViewMac::OnDisplayAdded(const gfx::Display& display) {
2390 void RenderWidgetHostViewMac::OnDisplayRemoved(const gfx::Display& display) {
2393 void RenderWidgetHostViewMac::OnDisplayMetricsChanged(
2394 const gfx::Display& display, uint32_t metrics) {
2395 gfx::Screen* screen = gfx::Screen::GetScreenFor(cocoa_view_);
2396 if (display.id() != screen->GetDisplayNearestWindow(cocoa_view_).id())
2399 UpdateScreenInfo(cocoa_view_);
2402 } // namespace content
2404 // RenderWidgetHostViewCocoa ---------------------------------------------------
2406 @implementation RenderWidgetHostViewCocoa
2407 @synthesize selectedRange = selectedRange_;
2408 @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_;
2409 @synthesize markedRange = markedRange_;
2411 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
2412 self = [super initWithFrame:NSZeroRect];
2414 self.acceptsTouchEvents = YES;
2415 editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper);
2416 editCommand_helper_->AddEditingSelectorsToClass([self class]);
2418 renderWidgetHostView_.reset(r);
2419 canBeKeyView_ = YES;
2420 focusedPluginIdentifier_ = -1;
2421 renderWidgetHostView_->backing_store_scale_factor_ =
2422 ui::GetScaleFactorForNativeView(self);
2425 if ([self respondsToSelector:
2426 @selector(setWantsBestResolutionOpenGLSurface:)]) {
2427 [self setWantsBestResolutionOpenGLSurface:YES];
2429 handlingGlobalFrameDidChange_ = NO;
2430 [[NSNotificationCenter defaultCenter]
2432 selector:@selector(didChangeScreenParameters:)
2433 name:NSApplicationDidChangeScreenParametersNotification
2440 // Unbind the GL context from this view. If this is not done before super's
2441 // dealloc is called then the GL context will crash when it reaches into
2442 // the view in its destructor.
2443 // http://crbug.com/255608
2444 if (renderWidgetHostView_)
2445 renderWidgetHostView_->AcceleratedSurfaceRelease();
2447 if (responderDelegate_ &&
2448 [responderDelegate_ respondsToSelector:@selector(viewGone:)])
2449 [responderDelegate_ viewGone:self];
2450 responderDelegate_.reset();
2452 [[NSNotificationCenter defaultCenter] removeObserver:self];
2457 - (void)didChangeScreenParameters:(NSNotification*)notify {
2458 g_screen_info_up_to_date = false;
2461 - (void)setResponderDelegate:
2462 (NSObject<RenderWidgetHostViewMacDelegate>*)delegate {
2463 DCHECK(!responderDelegate_);
2464 responderDelegate_.reset([delegate retain]);
2467 - (void)resetCursorRects {
2468 if (currentCursor_) {
2469 [self addCursorRect:[self visibleRect] cursor:currentCursor_];
2470 [currentCursor_ setOnMouseEntered:YES];
2474 - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event
2475 consumed:(BOOL)consumed {
2476 [responderDelegate_ rendererHandledWheelEvent:event consumed:consumed];
2479 - (BOOL)respondsToSelector:(SEL)selector {
2480 // Trickiness: this doesn't mean "does this object's superclass respond to
2481 // this selector" but rather "does the -respondsToSelector impl from the
2482 // superclass say that this class responds to the selector".
2483 if ([super respondsToSelector:selector])
2486 if (responderDelegate_)
2487 return [responderDelegate_ respondsToSelector:selector];
2492 - (id)forwardingTargetForSelector:(SEL)selector {
2493 if ([responderDelegate_ respondsToSelector:selector])
2494 return responderDelegate_.get();
2496 return [super forwardingTargetForSelector:selector];
2499 - (void)setCanBeKeyView:(BOOL)can {
2500 canBeKeyView_ = can;
2503 - (BOOL)acceptsMouseEventsWhenInactive {
2504 // Some types of windows (balloons, always-on-top panels) want to accept mouse
2505 // clicks w/o the first click being treated as 'activation'. Same applies to
2506 // mouse move events.
2507 return [[self window] level] > NSNormalWindowLevel;
2510 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
2511 return [self acceptsMouseEventsWhenInactive];
2514 - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b {
2515 takesFocusOnlyOnMouseDown_ = b;
2518 - (void)setCloseOnDeactivate:(BOOL)b {
2519 closeOnDeactivate_ = b;
2522 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
2523 NSWindow* window = [self window];
2524 // If this is a background window, don't handle mouse movement events. This
2525 // is the expected behavior on the Mac as evidenced by other applications.
2526 if ([theEvent type] == NSMouseMoved &&
2527 ![self acceptsMouseEventsWhenInactive] &&
2528 ![window isKeyWindow]) {
2532 // Use hitTest to check whether the mouse is over a nonWebContentView - in
2533 // which case the mouse event should not be handled by the render host.
2534 const SEL nonWebContentViewSelector = @selector(nonWebContentView);
2535 NSView* contentView = [window contentView];
2536 NSView* view = [contentView hitTest:[theEvent locationInWindow]];
2537 // Traverse the superview hierarchy as the hitTest will return the frontmost
2538 // view, such as an NSTextView, while nonWebContentView may be specified by
2541 if ([view respondsToSelector:nonWebContentViewSelector] &&
2542 [view performSelector:nonWebContentViewSelector]) {
2543 // The cursor is over a nonWebContentView - ignore this mouse event.
2546 if ([view isKindOfClass:[self class]] && ![view isEqual:self] &&
2547 !hasOpenMouseDown_) {
2548 // The cursor is over an overlapping render widget. This check is done by
2549 // both views so the one that's returned by -hitTest: will end up
2550 // processing the event.
2551 // Note that while dragging, we only get events for the render view where
2552 // drag started, even if mouse is actually over another view or outside
2553 // the window. Cocoa does this for us. We should handle these events and
2554 // not ignore (since there is no other render view to handle them). Thus
2555 // the |!hasOpenMouseDown_| check above.
2558 view = [view superview];
2563 - (void)mouseEvent:(NSEvent*)theEvent {
2564 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent");
2565 if (responderDelegate_ &&
2566 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2567 BOOL handled = [responderDelegate_ handleEvent:theEvent];
2572 if ([self shouldIgnoreMouseEvent:theEvent]) {
2573 // If this is the first such event, send a mouse exit to the host view.
2574 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) {
2575 WebMouseEvent exitEvent =
2576 WebInputEventFactory::mouseEvent(theEvent, self);
2577 exitEvent.type = WebInputEvent::MouseLeave;
2578 exitEvent.button = WebMouseEvent::ButtonNone;
2579 renderWidgetHostView_->ForwardMouseEvent(exitEvent);
2581 mouseEventWasIgnored_ = YES;
2585 if (mouseEventWasIgnored_) {
2586 // If this is the first mouse event after a previous event that was ignored
2587 // due to the hitTest, send a mouse enter event to the host view.
2588 if (renderWidgetHostView_->render_widget_host_) {
2589 WebMouseEvent enterEvent =
2590 WebInputEventFactory::mouseEvent(theEvent, self);
2591 enterEvent.type = WebInputEvent::MouseMove;
2592 enterEvent.button = WebMouseEvent::ButtonNone;
2593 renderWidgetHostView_->ForwardMouseEvent(enterEvent);
2596 mouseEventWasIgnored_ = NO;
2598 // TODO(rohitrao): Probably need to handle other mouse down events here.
2599 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) {
2600 if (renderWidgetHostView_->render_widget_host_)
2601 renderWidgetHostView_->render_widget_host_->OnPointerEventActivate();
2603 // Manually take focus after the click but before forwarding it to the
2605 [[self window] makeFirstResponder:self];
2608 // Don't cancel child popups; killing them on a mouse click would prevent the
2609 // user from positioning the insertion point in the text field spawning the
2610 // popup. A click outside the text field would cause the text field to drop
2611 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel
2612 // the popup anyway, so we're OK.
2614 NSEventType type = [theEvent type];
2615 if (type == NSLeftMouseDown)
2616 hasOpenMouseDown_ = YES;
2617 else if (type == NSLeftMouseUp)
2618 hasOpenMouseDown_ = NO;
2620 // TODO(suzhe): We should send mouse events to the input method first if it
2621 // wants to handle them. But it won't work without implementing method
2622 // - (NSUInteger)characterIndexForPoint:.
2623 // See: http://code.google.com/p/chromium/issues/detail?id=47141
2624 // Instead of sending mouse events to the input method first, we now just
2625 // simply confirm all ongoing composition here.
2626 if (type == NSLeftMouseDown || type == NSRightMouseDown ||
2627 type == NSOtherMouseDown) {
2628 [self confirmComposition];
2631 const WebMouseEvent event =
2632 WebInputEventFactory::mouseEvent(theEvent, self);
2633 renderWidgetHostView_->ForwardMouseEvent(event);
2636 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
2637 // |performKeyEquivalent:| is sent to all views of a window, not only down the
2638 // responder chain (cf. "Handling Key Equivalents" in
2639 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
2640 // ). We only want to handle key equivalents if we're first responder.
2641 if ([[self window] firstResponder] != self)
2644 // If the event is reserved by the system, then do not pass it to web content.
2645 if (EventIsReservedBySystem(theEvent))
2648 // If we return |NO| from this function, cocoa will send the key event to
2649 // the menu and only if the menu does not process the event to |keyDown:|. We
2650 // want to send the event to a renderer _before_ sending it to the menu, so
2651 // we need to return |YES| for all events that might be swallowed by the menu.
2652 // We do not return |YES| for every keypress because we don't get |keyDown:|
2653 // events for keys that we handle this way.
2654 NSUInteger modifierFlags = [theEvent modifierFlags];
2655 if ((modifierFlags & NSCommandKeyMask) == 0) {
2656 // Make sure the menu does not contain key equivalents that don't
2658 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]);
2662 // Command key combinations are sent via performKeyEquivalent rather than
2663 // keyDown:. We just forward this on and if WebCore doesn't want to handle
2664 // it, we let the WebContentsView figure out how to reinject it.
2665 [self keyEvent:theEvent wasKeyEquivalent:YES];
2669 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
2670 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:|
2671 // returned NO. If this function returns |YES|, Cocoa sends the event to
2672 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent
2673 // to us instead of doing key view loop control, ctrl-left/right get handled
2675 // (However, there are still some keys that Cocoa swallows, e.g. the key
2676 // equivalent that Cocoa uses for toggling the input language. In this case,
2677 // that's actually a good thing, though -- see http://crbug.com/26115 .)
2681 - (EventHandled)keyEvent:(NSEvent*)theEvent {
2682 if (responderDelegate_ &&
2683 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
2684 BOOL handled = [responderDelegate_ handleEvent:theEvent];
2686 return kEventHandled;
2689 [self keyEvent:theEvent wasKeyEquivalent:NO];
2690 return kEventHandled;
2693 - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv {
2694 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent");
2696 // If the user changes the system hotkey mapping after Chrome has been
2697 // launched, then it is possible that a formerly reserved system hotkey is no
2698 // longer reserved. The hotkey would have skipped the renderer, but would
2699 // also have not been handled by the system. If this is the case, immediately
2701 // TODO(erikchen): SystemHotkeyHelperMac should use the File System Events
2702 // api to monitor changes to system hotkeys. This logic will have to be
2704 // http://crbug.com/383558.
2705 if (EventIsReservedBySystem(theEvent))
2708 DCHECK([theEvent type] != NSKeyDown ||
2709 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask));
2711 if ([theEvent type] == NSFlagsChanged) {
2712 // Ignore NSFlagsChanged events from the NumLock and Fn keys as
2713 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm").
2714 int keyCode = [theEvent keyCode];
2715 if (!keyCode || keyCode == 10 || keyCode == 63)
2719 // Don't cancel child popups; the key events are probably what's triggering
2720 // the popup in the first place.
2722 RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
2725 NativeWebKeyboardEvent event(theEvent);
2727 // Force fullscreen windows to close on Escape so they won't keep the keyboard
2728 // grabbed or be stuck onscreen if the renderer is hanging.
2729 if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
2730 event.windowsKeyCode == ui::VKEY_ESCAPE &&
2731 renderWidgetHostView_->pepper_fullscreen_window()) {
2732 RenderWidgetHostViewMac* parent =
2733 renderWidgetHostView_->fullscreen_parent_host_view();
2735 parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES;
2736 widgetHost->Shutdown();
2740 // Suppress the escape key up event if necessary.
2741 if (event.windowsKeyCode == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) {
2742 if (event.type == NativeWebKeyboardEvent::KeyUp)
2743 suppressNextEscapeKeyUp_ = NO;
2747 // We only handle key down events and just simply forward other events.
2748 if ([theEvent type] != NSKeyDown) {
2749 widgetHost->ForwardKeyboardEvent(event);
2751 // Possibly autohide the cursor.
2752 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2753 [NSCursor setHiddenUntilMouseMoves:YES];
2758 base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]);
2760 // Records the current marked text state, so that we can know if the marked
2761 // text was deleted or not after handling the key down event.
2762 BOOL oldHasMarkedText = hasMarkedText_;
2764 // This method should not be called recursively.
2765 DCHECK(!handlingKeyDown_);
2767 // Tells insertText: and doCommandBySelector: that we are handling a key
2769 handlingKeyDown_ = YES;
2771 // These variables might be set when handling the keyboard event.
2772 // Clear them here so that we can know whether they have changed afterwards.
2773 textToBeInserted_.clear();
2774 markedText_.clear();
2775 underlines_.clear();
2776 unmarkTextCalled_ = NO;
2777 hasEditCommands_ = NO;
2778 editCommands_.clear();
2780 // Before doing anything with a key down, check to see if plugin IME has been
2781 // cancelled, since the plugin host needs to be informed of that before
2782 // receiving the keydown.
2783 if ([theEvent type] == NSKeyDown)
2784 [self checkForPluginImeCancellation];
2786 // Sends key down events to input method first, then we can decide what should
2787 // be done according to input method's feedback.
2788 // If a plugin is active, bypass this step since events are forwarded directly
2789 // to the plugin IME.
2790 if (focusedPluginIdentifier_ == -1)
2791 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
2793 handlingKeyDown_ = NO;
2795 // Indicates if we should send the key event and corresponding editor commands
2796 // after processing the input method result.
2797 BOOL delayEventUntilAfterImeCompostion = NO;
2799 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
2800 // while an input method is composing or inserting a text.
2801 // Gmail checks this code in its onkeydown handler to stop auto-completing
2802 // e-mail addresses while composing a CJK text.
2803 // If the text to be inserted has only one character, then we don't need this
2804 // trick, because we'll send the text as a key press event instead.
2805 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
2806 NativeWebKeyboardEvent fakeEvent = event;
2807 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
2808 fakeEvent.setKeyIdentifierFromWindowsKeyCode();
2809 fakeEvent.skip_in_browser = true;
2810 widgetHost->ForwardKeyboardEvent(fakeEvent);
2811 // If this key event was handled by the input method, but
2812 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
2813 // enqueued edit commands, then in order to let webkit handle them
2814 // correctly, we need to send the real key event and corresponding edit
2815 // commands after processing the input method result.
2816 // We shouldn't do this if a new marked text was set by the input method,
2817 // otherwise the new marked text might be cancelled by webkit.
2818 if (hasEditCommands_ && !hasMarkedText_)
2819 delayEventUntilAfterImeCompostion = YES;
2821 if (!editCommands_.empty()) {
2822 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2823 widgetHost->GetRoutingID(), editCommands_));
2825 widgetHost->ForwardKeyboardEvent(event);
2828 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2829 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2830 // be set to NULL. So we check it here and return immediately if it's NULL.
2831 if (!renderWidgetHostView_->render_widget_host_)
2834 // Then send keypress and/or composition related events.
2835 // If there was a marked text or the text to be inserted is longer than 1
2836 // character, then we send the text by calling ConfirmComposition().
2837 // Otherwise, if the text to be inserted only contains 1 character, then we
2838 // can just send a keypress event which is fabricated by changing the type of
2839 // the keydown event, so that we can retain all necessary informations, such
2840 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
2841 // prevent the browser from handling it again.
2842 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
2843 // handle BMP characters here, as we can always insert non-BMP characters as
2845 BOOL textInserted = NO;
2846 if (textToBeInserted_.length() >
2847 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
2848 widgetHost->ImeConfirmComposition(
2849 textToBeInserted_, gfx::Range::InvalidRange(), false);
2853 // Updates or cancels the composition. If some text has been inserted, then
2854 // we don't need to cancel the composition explicitly.
2855 if (hasMarkedText_ && markedText_.length()) {
2856 // Sends the updated marked text to the renderer so it can update the
2857 // composition node in WebKit.
2858 // When marked text is available, |selectedRange_| will be the range being
2859 // selected inside the marked text.
2860 widgetHost->ImeSetComposition(markedText_, underlines_,
2861 selectedRange_.location,
2862 NSMaxRange(selectedRange_));
2863 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
2864 if (unmarkTextCalled_) {
2865 widgetHost->ImeConfirmComposition(
2866 base::string16(), gfx::Range::InvalidRange(), false);
2868 widgetHost->ImeCancelComposition();
2872 // If the key event was handled by the input method but it also generated some
2873 // edit commands, then we need to send the real key event and corresponding
2874 // edit commands here. This usually occurs when the input method wants to
2875 // finish current composition session but still wants the application to
2876 // handle the key event. See http://crbug.com/48161 for reference.
2877 if (delayEventUntilAfterImeCompostion) {
2878 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
2879 // with windowsKeyCode == 0xE5 has already been sent to webkit.
2880 // So before sending the real key down event, we need to send a fake key up
2881 // event to balance it.
2882 NativeWebKeyboardEvent fakeEvent = event;
2883 fakeEvent.type = blink::WebInputEvent::KeyUp;
2884 fakeEvent.skip_in_browser = true;
2885 widgetHost->ForwardKeyboardEvent(fakeEvent);
2886 // Not checking |renderWidgetHostView_->render_widget_host_| here because
2887 // a key event with |skip_in_browser| == true won't be handled by browser,
2888 // thus it won't destroy the widget.
2890 if (!editCommands_.empty()) {
2891 widgetHost->Send(new InputMsg_SetEditCommandsForNextKeyEvent(
2892 widgetHost->GetRoutingID(), editCommands_));
2894 widgetHost->ForwardKeyboardEvent(event);
2896 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
2897 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
2898 // be set to NULL. So we check it here and return immediately if it's NULL.
2899 if (!renderWidgetHostView_->render_widget_host_)
2903 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask;
2904 // Only send a corresponding key press event if there is no marked text.
2905 if (!hasMarkedText_) {
2906 if (!textInserted && textToBeInserted_.length() == 1) {
2907 // If a single character was inserted, then we just send it as a keypress
2909 event.type = blink::WebInputEvent::Char;
2910 event.text[0] = textToBeInserted_[0];
2912 event.skip_in_browser = true;
2913 widgetHost->ForwardKeyboardEvent(event);
2914 } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
2915 [[theEvent characters] length] > 0 &&
2916 (([theEvent modifierFlags] & kCtrlCmdKeyMask) ||
2917 (hasEditCommands_ && editCommands_.empty()))) {
2918 // We don't get insertText: calls if ctrl or cmd is down, or the key event
2919 // generates an insert command. So synthesize a keypress event for these
2920 // cases, unless the key event generated any other command.
2921 event.type = blink::WebInputEvent::Char;
2922 event.skip_in_browser = true;
2923 widgetHost->ForwardKeyboardEvent(event);
2927 // Possibly autohide the cursor.
2928 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
2929 [NSCursor setHiddenUntilMouseMoves:YES];
2932 - (void)shortCircuitScrollWheelEvent:(NSEvent*)event {
2933 DCHECK(base::mac::IsOSLionOrLater());
2935 if ([event phase] != NSEventPhaseEnded &&
2936 [event phase] != NSEventPhaseCancelled) {
2940 if (renderWidgetHostView_->render_widget_host_) {
2941 // History-swiping is not possible if the logic reaches this point.
2942 // Allow rubber-banding in both directions.
2943 bool canRubberbandLeft = true;
2944 bool canRubberbandRight = true;
2945 const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
2946 event, self, canRubberbandLeft, canRubberbandRight);
2947 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
2950 if (endWheelMonitor_) {
2951 [NSEvent removeMonitor:endWheelMonitor_];
2952 endWheelMonitor_ = nil;
2956 - (void)beginGestureWithEvent:(NSEvent*)event {
2957 [responderDelegate_ beginGestureWithEvent:event];
2959 - (void)endGestureWithEvent:(NSEvent*)event {
2960 [responderDelegate_ endGestureWithEvent:event];
2962 - (void)touchesMovedWithEvent:(NSEvent*)event {
2963 [responderDelegate_ touchesMovedWithEvent:event];
2965 - (void)touchesBeganWithEvent:(NSEvent*)event {
2966 [responderDelegate_ touchesBeganWithEvent:event];
2968 - (void)touchesCancelledWithEvent:(NSEvent*)event {
2969 [responderDelegate_ touchesCancelledWithEvent:event];
2971 - (void)touchesEndedWithEvent:(NSEvent*)event {
2972 [responderDelegate_ touchesEndedWithEvent:event];
2975 // This is invoked only on 10.8 or newer when the user taps a word using
2977 - (void)quickLookWithEvent:(NSEvent*)event {
2978 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
2979 TextInputClientMac::GetInstance()->GetStringAtPoint(
2980 renderWidgetHostView_->render_widget_host_,
2981 gfx::Point(point.x, NSHeight([self frame]) - point.y),
2982 ^(NSAttributedString* string, NSPoint baselinePoint) {
2983 if (string && [string length] > 0) {
2984 dispatch_async(dispatch_get_main_queue(), ^{
2985 [self showDefinitionForAttributedString:string
2986 atPoint:baselinePoint];
2993 // This method handles 2 different types of hardware events.
2994 // (Apple does not distinguish between them).
2995 // a. Scrolling the middle wheel of a mouse.
2996 // b. Swiping on the track pad.
2998 // This method is responsible for 2 types of behavior:
2999 // a. Scrolling the content of window.
3000 // b. Navigating forwards/backwards in history.
3002 // This is a brief description of the logic:
3003 // 1. If the content can be scrolled, scroll the content.
3004 // (This requires a roundtrip to blink to determine whether the content
3005 // can be scrolled.)
3006 // Once this logic is triggered, the navigate logic cannot be triggered
3007 // until the gesture finishes.
3008 // 2. If the user is making a horizontal swipe, start the navigate
3009 // forward/backwards UI.
3010 // Once this logic is triggered, the user can either cancel or complete
3011 // the gesture. If the user completes the gesture, all remaining touches
3012 // are swallowed, and not allowed to scroll the content. If the user
3013 // cancels the gesture, all remaining touches are forwarded to the content
3014 // scroll logic. The user cannot trigger the navigation logic again.
3015 - (void)scrollWheel:(NSEvent*)event {
3016 if (responderDelegate_ &&
3017 [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) {
3018 BOOL handled = [responderDelegate_ handleEvent:event];
3023 // Use an NSEvent monitor to listen for the wheel-end end. This ensures that
3024 // the event is received even when the mouse cursor is no longer over the view
3025 // when the scrolling ends (e.g. if the tab was switched). This is necessary
3026 // for ending rubber-banding in such cases.
3027 if (base::mac::IsOSLionOrLater() && [event phase] == NSEventPhaseBegan &&
3028 !endWheelMonitor_) {
3030 [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
3031 handler:^(NSEvent* blockEvent) {
3032 [self shortCircuitScrollWheelEvent:blockEvent];
3037 // This is responsible for content scrolling!
3038 if (renderWidgetHostView_->render_widget_host_) {
3039 BOOL canRubberbandLeft = [responderDelegate_ canRubberbandLeft:self];
3040 BOOL canRubberbandRight = [responderDelegate_ canRubberbandRight:self];
3041 const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
3042 event, self, canRubberbandLeft, canRubberbandRight);
3043 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
3047 // Called repeatedly during a pinch gesture, with incremental change values.
3048 - (void)magnifyWithEvent:(NSEvent*)event {
3049 if (renderWidgetHostView_->render_widget_host_) {
3050 // Send a GesturePinchUpdate event.
3051 // Note that we don't attempt to bracket these by GesturePinchBegin/End (or
3052 // GestureSrollBegin/End) as is done for touchscreen. Keeping track of when
3053 // a pinch is active would take a little more work here, and we don't need
3054 // it for anything yet.
3055 const WebGestureEvent& webEvent =
3056 WebInputEventFactory::gestureEvent(event, self);
3057 renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(webEvent);
3061 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
3062 NSWindow* oldWindow = [self window];
3064 NSNotificationCenter* notificationCenter =
3065 [NSNotificationCenter defaultCenter];
3067 // Backing property notifications crash on 10.6 when building with the 10.7
3068 // SDK, see http://crbug.com/260595.
3069 static BOOL supportsBackingPropertiesNotification =
3070 SupportsBackingPropertiesChangedNotification();
3073 if (supportsBackingPropertiesNotification) {
3076 name:NSWindowDidChangeBackingPropertiesNotification
3081 name:NSWindowDidMoveNotification
3085 name:NSWindowDidEndLiveResizeNotification
3089 if (supportsBackingPropertiesNotification) {
3092 selector:@selector(windowDidChangeBackingProperties:)
3093 name:NSWindowDidChangeBackingPropertiesNotification
3098 selector:@selector(windowChangedGlobalFrame:)
3099 name:NSWindowDidMoveNotification
3103 selector:@selector(windowChangedGlobalFrame:)
3104 name:NSWindowDidEndLiveResizeNotification
3109 - (void)updateScreenProperties{
3110 renderWidgetHostView_->UpdateBackingStoreScaleFactor();
3111 renderWidgetHostView_->UpdateDisplayLink();
3114 // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4
3115 - (void)windowDidChangeBackingProperties:(NSNotification*)notification {
3116 // Background tabs check if their scale factor or vsync properties changed
3117 // when they are added to a window.
3119 // Allocating a CGLayerRef with the current scale factor immediately from
3120 // this handler doesn't work. Schedule the backing store update on the
3121 // next runloop cycle, then things are read for CGLayerRef allocations to
3123 [self performSelector:@selector(updateScreenProperties)
3128 - (void)windowChangedGlobalFrame:(NSNotification*)notification {
3129 renderWidgetHostView_->UpdateScreenInfo(
3130 renderWidgetHostView_->GetNativeView());
3133 - (void)setFrameSize:(NSSize)newSize {
3134 TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize");
3136 // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding
3137 // -setFrame: isn't neccessary.
3138 [super setFrameSize:newSize];
3140 if (!renderWidgetHostView_->render_widget_host_)
3143 // Move the CALayers to their positions in the new view size. Note that
3144 // this will not draw anything because the non-background layers' sizes
3145 // didn't actually change.
3146 renderWidgetHostView_->LayoutLayers();
3148 renderWidgetHostView_->render_widget_host_->SendScreenRects();
3149 renderWidgetHostView_->render_widget_host_->WasResized();
3150 if (renderWidgetHostView_->delegated_frame_host_)
3151 renderWidgetHostView_->delegated_frame_host_->WasResized();
3153 // Wait for the frame that WasResize might have requested. If the view is
3154 // being made visible at a new size, then this call will have no effect
3155 // because the view widget is still hidden, and the pause call in WasShown
3156 // will have this effect for us.
3157 renderWidgetHostView_->PauseForPendingResizeOrRepaintsAndDraw();
3160 - (BOOL)canBecomeKeyView {
3161 if (!renderWidgetHostView_->render_widget_host_)
3164 return canBeKeyView_;
3167 - (BOOL)acceptsFirstResponder {
3168 if (!renderWidgetHostView_->render_widget_host_)
3171 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_;
3174 - (BOOL)becomeFirstResponder {
3175 if (!renderWidgetHostView_->render_widget_host_)
3178 renderWidgetHostView_->render_widget_host_->Focus();
3179 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true);
3180 renderWidgetHostView_->SetTextInputActive(true);
3182 // Cancel any onging composition text which was left before we lost focus.
3183 // TODO(suzhe): We should do it in -resignFirstResponder: method, but
3184 // somehow that method won't be called when switching among different tabs.
3185 // See http://crbug.com/47209
3186 [self cancelComposition];
3188 NSNumber* direction = [NSNumber numberWithUnsignedInteger:
3189 [[self window] keyViewSelectionDirection]];
3190 NSDictionary* userInfo =
3191 [NSDictionary dictionaryWithObject:direction
3192 forKey:kSelectionDirection];
3193 [[NSNotificationCenter defaultCenter]
3194 postNotificationName:kViewDidBecomeFirstResponder
3201 - (BOOL)resignFirstResponder {
3202 renderWidgetHostView_->SetTextInputActive(false);
3203 if (!renderWidgetHostView_->render_widget_host_)
3206 if (closeOnDeactivate_)
3207 renderWidgetHostView_->KillSelf();
3209 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false);
3210 renderWidgetHostView_->render_widget_host_->Blur();
3212 // We should cancel any onging composition whenever RWH's Blur() method gets
3213 // called, because in this case, webkit will confirm the ongoing composition
3215 [self cancelComposition];
3220 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
3221 if (responderDelegate_ &&
3223 respondsToSelector:@selector(validateUserInterfaceItem:
3227 [responderDelegate_ validateUserInterfaceItem:item isValidItem:&valid];
3232 SEL action = [item action];
3234 if (action == @selector(stopSpeaking:)) {
3235 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3236 renderWidgetHostView_->IsSpeaking();
3238 if (action == @selector(startSpeaking:)) {
3239 return renderWidgetHostView_->render_widget_host_->IsRenderView() &&
3240 renderWidgetHostView_->SupportsSpeech();
3243 // For now, these actions are always enabled for render view,
3244 // this is sub-optimal.
3245 // TODO(suzhe): Plumb the "can*" methods up from WebCore.
3246 if (action == @selector(undo:) ||
3247 action == @selector(redo:) ||
3248 action == @selector(cut:) ||
3249 action == @selector(copy:) ||
3250 action == @selector(copyToFindPboard:) ||
3251 action == @selector(paste:) ||
3252 action == @selector(pasteAndMatchStyle:)) {
3253 return renderWidgetHostView_->render_widget_host_->IsRenderView();
3256 return editCommand_helper_->IsMenuItemEnabled(action, self);
3259 - (RenderWidgetHostViewMac*)renderWidgetHostViewMac {
3260 return renderWidgetHostView_.get();
3263 // Determine whether we should autohide the cursor (i.e., hide it until mouse
3264 // move) for the given event. Customize here to be more selective about which
3265 // key presses to autohide on.
3266 + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event {
3267 return ([event type] == NSKeyDown &&
3268 !([event modifierFlags] & NSCommandKeyMask)) ? YES : NO;
3271 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute
3272 index:(NSUInteger)index
3273 maxCount:(NSUInteger)maxCount {
3274 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3275 NSUInteger totalLength = [fullArray count];
3276 if (index >= totalLength)
3278 NSUInteger length = MIN(totalLength - index, maxCount);
3279 return [fullArray subarrayWithRange:NSMakeRange(index, length)];
3282 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute {
3283 NSArray* fullArray = [self accessibilityAttributeValue:attribute];
3284 return [fullArray count];
3287 - (id)accessibilityAttributeValue:(NSString *)attribute {
3288 BrowserAccessibilityManager* manager =
3289 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager();
3291 // Contents specifies document view of RenderWidgetHostViewCocoa provided by
3292 // BrowserAccessibilityManager. Children includes all subviews in addition to
3293 // contents. Currently we do not have subviews besides the document view.
3294 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] ||
3295 [attribute isEqualToString:NSAccessibilityContentsAttribute]) &&
3297 return [NSArray arrayWithObjects:manager->
3298 GetRoot()->ToBrowserAccessibilityCocoa(), nil];
3299 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
3300 return NSAccessibilityScrollAreaRole;
3302 id ret = [super accessibilityAttributeValue:attribute];
3306 - (NSArray*)accessibilityAttributeNames {
3307 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
3308 [ret addObject:NSAccessibilityContentsAttribute];
3309 [ret addObjectsFromArray:[super accessibilityAttributeNames]];
3313 - (id)accessibilityHitTest:(NSPoint)point {
3314 BrowserAccessibilityManager* manager =
3315 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager();
3318 NSPoint pointInWindow = [[self window] convertScreenToBase:point];
3319 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil];
3320 localPoint.y = NSHeight([self bounds]) - localPoint.y;
3321 BrowserAccessibilityCocoa* root =
3322 manager->GetRoot()->ToBrowserAccessibilityCocoa();
3323 id obj = [root accessibilityHitTest:localPoint];
3327 - (BOOL)accessibilityIsIgnored {
3328 BrowserAccessibilityManager* manager =
3329 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager();
3333 - (NSUInteger)accessibilityGetIndexOf:(id)child {
3334 BrowserAccessibilityManager* manager =
3335 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager();
3336 // Only child is root.
3338 manager->GetRoot()->ToBrowserAccessibilityCocoa() == child) {
3345 - (id)accessibilityFocusedUIElement {
3346 BrowserAccessibilityManager* manager =
3347 renderWidgetHostView_->GetHost()->GetRootBrowserAccessibilityManager();
3349 BrowserAccessibility* focused_item = manager->GetFocus(NULL);
3350 DCHECK(focused_item);
3352 BrowserAccessibilityCocoa* focused_item_cocoa =
3353 focused_item->ToBrowserAccessibilityCocoa();
3354 DCHECK(focused_item_cocoa);
3355 if (focused_item_cocoa)
3356 return focused_item_cocoa;
3359 return [super accessibilityFocusedUIElement];
3362 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
3363 // with minor modifications for code style and commenting.
3365 // The 'public' interface is -setToolTipAtMousePoint:. This differs from
3366 // -setToolTip: in that the updated tooltip takes effect immediately,
3367 // without the user's having to move the mouse out of and back into the view.
3369 // Unfortunately, doing this requires sending fake mouseEnter/Exit events to
3370 // the view, which in turn requires overriding some internal tracking-rect
3371 // methods (to keep track of its owner & userdata, which need to be filled out
3372 // in the fake events.) --snej 7/6/09
3376 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3377 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
3379 * Redistribution and use in source and binary forms, with or without
3380 * modification, are permitted provided that the following conditions
3383 * 1. Redistributions of source code must retain the above copyright
3384 * notice, this list of conditions and the following disclaimer.
3385 * 2. Redistributions in binary form must reproduce the above copyright
3386 * notice, this list of conditions and the following disclaimer in the
3387 * documentation and/or other materials provided with the distribution.
3388 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
3389 * its contributors may be used to endorse or promote products derived
3390 * from this software without specific prior written permission.
3392 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
3393 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3394 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3395 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
3396 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3397 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
3398 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3399 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3400 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3401 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3404 // Any non-zero value will do, but using something recognizable might help us
3406 static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
3408 // Override of a public NSView method, replacing the inherited functionality.
3409 // See above for rationale.
3410 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
3412 userData:(void *)data
3413 assumeInside:(BOOL)assumeInside {
3414 DCHECK(trackingRectOwner_ == nil);
3415 trackingRectOwner_ = owner;
3416 trackingRectUserData_ = data;
3417 return kTrackingRectTag;
3420 // Override of (apparently) a private NSView method(!) See above for rationale.
3421 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
3423 userData:(void *)data
3424 assumeInside:(BOOL)assumeInside
3425 useTrackingNum:(int)tag {
3426 DCHECK(tag == 0 || tag == kTrackingRectTag);
3427 DCHECK(trackingRectOwner_ == nil);
3428 trackingRectOwner_ = owner;
3429 trackingRectUserData_ = data;
3430 return kTrackingRectTag;
3433 // Override of (apparently) a private NSView method(!) See above for rationale.
3434 - (void)_addTrackingRects:(NSRect *)rects
3436 userDataList:(void **)userDataList
3437 assumeInsideList:(BOOL *)assumeInsideList
3438 trackingNums:(NSTrackingRectTag *)trackingNums
3441 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
3442 DCHECK(trackingRectOwner_ == nil);
3443 trackingRectOwner_ = owner;
3444 trackingRectUserData_ = userDataList[0];
3445 trackingNums[0] = kTrackingRectTag;
3448 // Override of a public NSView method, replacing the inherited functionality.
3449 // See above for rationale.
3450 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
3454 if (tag == kTrackingRectTag) {
3455 trackingRectOwner_ = nil;
3459 if (tag == lastToolTipTag_) {
3460 [super removeTrackingRect:tag];
3461 lastToolTipTag_ = 0;
3465 // If any other tracking rect is being removed, we don't know how it was
3466 // created and it's possible there's a leak involved (see Radar 3500217).
3470 // Override of (apparently) a private NSView method(!)
3471 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
3472 for (int i = 0; i < count; ++i) {
3476 DCHECK(tag == kTrackingRectTag);
3477 trackingRectOwner_ = nil;
3481 // Sends a fake NSMouseExited event to the view for its current tracking rect.
3482 - (void)_sendToolTipMouseExited {
3483 // Nothing matters except window, trackingNumber, and userData.
3484 int windowNumber = [[self window] windowNumber];
3485 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
3486 location:NSZeroPoint
3489 windowNumber:windowNumber
3492 trackingNumber:kTrackingRectTag
3493 userData:trackingRectUserData_];
3494 [trackingRectOwner_ mouseExited:fakeEvent];
3497 // Sends a fake NSMouseEntered event to the view for its current tracking rect.
3498 - (void)_sendToolTipMouseEntered {
3499 // Nothing matters except window, trackingNumber, and userData.
3500 int windowNumber = [[self window] windowNumber];
3501 NSEvent* fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
3502 location:NSZeroPoint
3505 windowNumber:windowNumber
3508 trackingNumber:kTrackingRectTag
3509 userData:trackingRectUserData_];
3510 [trackingRectOwner_ mouseEntered:fakeEvent];
3513 // Sets the view's current tooltip, to be displayed at the current mouse
3514 // location. (This does not make the tooltip appear -- as usual, it only
3515 // appears after a delay.) Pass null to remove the tooltip.
3516 - (void)setToolTipAtMousePoint:(NSString *)string {
3517 NSString *toolTip = [string length] == 0 ? nil : string;
3518 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) ||
3519 (!toolTip && !toolTip_)) {
3524 [self _sendToolTipMouseExited];
3527 toolTip_.reset([toolTip copy]);
3530 // See radar 3500217 for why we remove all tooltips
3531 // rather than just the single one we created.
3532 [self removeAllToolTips];
3533 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
3534 lastToolTipTag_ = [self addToolTipRect:wideOpenRect
3537 [self _sendToolTipMouseEntered];
3541 // NSView calls this to get the text when displaying the tooltip.
3542 - (NSString *)view:(NSView *)view
3543 stringForToolTip:(NSToolTipTag)tag
3544 point:(NSPoint)point
3545 userData:(void *)data {
3546 return [[toolTip_ copy] autorelease];
3549 // Below is our NSTextInputClient implementation.
3551 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
3552 // functions to process this event.
3554 // [WebHTMLView keyDown] ->
3555 // EventHandler::keyEvent() ->
3557 // [WebEditorClient handleKeyboardEvent] ->
3558 // [WebHTMLView _interceptEditingKeyEvent] ->
3559 // [NSResponder interpretKeyEvents] ->
3560 // [WebHTMLView insertText] ->
3561 // Editor::insertText()
3563 // Unfortunately, it is hard for Chromium to use this implementation because
3564 // it causes key-typing jank.
3565 // RenderWidgetHostViewMac is running in a browser process. On the other
3566 // hand, Editor and EventHandler are running in a renderer process.
3567 // So, if we used this implementation, a NSKeyDown event is dispatched to
3568 // the following functions of Chromium.
3570 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3571 // |Sync IPC (KeyDown)| (*1) ->
3572 // EventHandler::keyEvent() (renderer) ->
3574 // EditorClientImpl::handleKeyboardEvent() (renderer) ->
3575 // |Sync IPC| (*2) ->
3576 // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
3577 // [self interpretKeyEvents] ->
3578 // [RenderWidgetHostViewMac insertText] (browser) ->
3580 // Editor::insertText() (renderer)
3582 // (*1) we need to wait until this call finishes since WebHTMLView uses the
3583 // result of EventHandler::keyEvent().
3584 // (*2) we need to wait until this call finishes since WebEditorClient uses
3585 // the result of [WebHTMLView _interceptEditingKeyEvent].
3587 // This needs many sync IPC messages sent between a browser and a renderer for
3588 // each key event, which would probably result in key-typing jank.
3589 // To avoid this problem, this implementation processes key events (and input
3590 // method events) totally in a browser process and sends asynchronous input
3591 // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
3592 // renderer process.
3594 // [RenderWidgetHostViewMac keyEvent] (browser) ->
3595 // |Async IPC (RawKeyDown)| ->
3596 // [self interpretKeyEvents] ->
3597 // [RenderWidgetHostViewMac insertText] (browser) ->
3598 // |Async IPC (Char)| ->
3599 // Editor::insertText() (renderer)
3601 // Since this implementation doesn't have to wait any IPC calls, this doesn't
3602 // make any key-typing jank. --hbono 7/23/09
3605 extern NSString *NSTextInputReplacementRangeAttributeName;
3608 - (NSArray *)validAttributesForMarkedText {
3609 // This code is just copied from WebKit except renaming variables.
3610 if (!validAttributesForMarkedText_) {
3611 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects:
3612 NSUnderlineStyleAttributeName,
3613 NSUnderlineColorAttributeName,
3614 NSMarkedClauseSegmentAttributeName,
3615 NSTextInputReplacementRangeAttributeName,
3618 return validAttributesForMarkedText_.get();
3621 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3622 DCHECK([self window]);
3623 // |thePoint| is in screen coordinates, but needs to be converted to WebKit
3624 // coordinates (upper left origin). Scroll offsets will be taken care of in
3626 thePoint = [[self window] convertScreenToBase:thePoint];
3627 thePoint = [self convertPoint:thePoint fromView:nil];
3628 thePoint.y = NSHeight([self frame]) - thePoint.y;
3631 TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
3632 renderWidgetHostView_->render_widget_host_,
3633 gfx::Point(thePoint.x, thePoint.y));
3637 - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
3638 actualRange:(NSRangePointer)actualRange {
3640 if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
3644 rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
3645 renderWidgetHostView_->render_widget_host_, theRange);
3647 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3649 *actualRange = theRange;
3652 // The returned rectangle is in WebKit coordinates (upper left origin), so
3653 // flip the coordinate system.
3654 NSRect viewFrame = [self frame];
3655 rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect);
3659 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
3660 actualRange:(NSRangePointer)actualRange {
3661 NSRect rect = [self firstViewRectForCharacterRange:theRange
3662 actualRange:actualRange];
3664 // Convert into screen coordinates for return.
3665 rect = [self convertRect:rect toView:nil];
3666 rect.origin = [[self window] convertBaseToScreen:rect.origin];
3670 - (NSRange)markedRange {
3671 // An input method calls this method to check if an application really has
3672 // a text being composed when hasMarkedText call returns true.
3673 // Returns the range saved in the setMarkedText method so the input method
3674 // calls the setMarkedText method and we can update the composition node
3675 // there. (When this method returns an empty range, the input method doesn't
3676 // call the setMarkedText method.)
3677 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
3680 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
3681 actualRange:(NSRangePointer)actualRange {
3682 // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
3684 *actualRange = range;
3685 NSAttributedString* str =
3686 TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange(
3687 renderWidgetHostView_->render_widget_host_, range);
3691 - (NSInteger)conversationIdentifier {
3692 return reinterpret_cast<NSInteger>(self);
3695 // Each RenderWidgetHostViewCocoa has its own input context, but we return
3696 // nil when the caret is in non-editable content or password box to avoid
3697 // making input methods do their work.
3698 - (NSTextInputContext *)inputContext {
3699 if (focusedPluginIdentifier_ != -1)
3700 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
3702 switch(renderWidgetHostView_->text_input_type_) {
3703 case ui::TEXT_INPUT_TYPE_NONE:
3704 case ui::TEXT_INPUT_TYPE_PASSWORD:
3707 return [super inputContext];
3711 - (BOOL)hasMarkedText {
3712 // An input method calls this function to figure out whether or not an
3713 // application is really composing a text. If it is composing, it calls
3714 // the markedRange method, and maybe calls the setMarkedText method.
3715 // It seems an input method usually calls this function when it is about to
3716 // cancel an ongoing composition. If an application has a non-empty marked
3717 // range, it calls the setMarkedText method to delete the range.
3718 return hasMarkedText_;
3721 - (void)unmarkText {
3722 // Delete the composition node of the renderer and finish an ongoing
3724 // It seems an input method calls the setMarkedText method and set an empty
3725 // text when it cancels an ongoing composition, i.e. I have never seen an
3726 // input method calls this method.
3727 hasMarkedText_ = NO;
3728 markedText_.clear();
3729 underlines_.clear();
3731 // If we are handling a key down event, then ConfirmComposition() will be
3732 // called in keyEvent: method.
3733 if (!handlingKeyDown_) {
3734 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3735 base::string16(), gfx::Range::InvalidRange(), false);
3737 unmarkTextCalled_ = YES;
3741 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
3742 replacementRange:(NSRange)replacementRange {
3743 // An input method updates the composition string.
3744 // We send the given text and range to the renderer so it can update the
3745 // composition node of WebKit.
3746 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3747 // the full web content.
3748 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3749 NSString* im_text = isAttributedString ? [string string] : string;
3750 int length = [im_text length];
3752 // |markedRange_| will get set on a callback from ImeSetComposition().
3753 selectedRange_ = newSelRange;
3754 markedText_ = base::SysNSStringToUTF16(im_text);
3755 hasMarkedText_ = (length > 0);
3757 underlines_.clear();
3758 if (isAttributedString) {
3759 ExtractUnderlines(string, &underlines_);
3761 // Use a thin black underline by default.
3762 underlines_.push_back(blink::WebCompositionUnderline(
3763 0, length, SK_ColorBLACK, false, SK_ColorTRANSPARENT));
3766 // If we are handling a key down event, then SetComposition() will be
3767 // called in keyEvent: method.
3768 // Input methods of Mac use setMarkedText calls with an empty text to cancel
3769 // an ongoing composition. So, we should check whether or not the given text
3770 // is empty to update the input method state. (Our input method backend can
3771 // automatically cancels an ongoing composition when we send an empty text.
3772 // So, it is OK to send an empty text to the renderer.)
3773 if (!handlingKeyDown_) {
3774 renderWidgetHostView_->render_widget_host_->ImeSetComposition(
3775 markedText_, underlines_,
3776 newSelRange.location, NSMaxRange(newSelRange));
3780 - (void)doCommandBySelector:(SEL)selector {
3781 // An input method calls this function to dispatch an editing command to be
3782 // handled by this view.
3783 if (selector == @selector(noop:))
3786 std::string command(
3787 [RenderWidgetHostViewMacEditCommandHelper::
3788 CommandNameForSelector(selector) UTF8String]);
3790 // If this method is called when handling a key down event, then we need to
3791 // handle the command in the key event handler. Otherwise we can just handle
3793 if (handlingKeyDown_) {
3794 hasEditCommands_ = YES;
3795 // We ignore commands that insert characters, because this was causing
3796 // strange behavior (e.g. tab always inserted a tab rather than moving to
3797 // the next field on the page).
3798 if (!StartsWithASCII(command, "insert", false))
3799 editCommands_.push_back(EditCommand(command, ""));
3801 RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_;
3802 rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(),
3807 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
3808 // An input method has characters to be inserted.
3809 // Same as Linux, Mac calls this method not only:
3810 // * when an input method finishs composing text, but also;
3811 // * when we type an ASCII character (without using input methods).
3812 // When we aren't using input methods, we should send the given character as
3813 // a Char event so it is dispatched to an onkeypress() event handler of
3815 // On the other hand, when we are using input methods, we should send the
3816 // given characters as an input method event and prevent the characters from
3817 // being dispatched to onkeypress() event handlers.
3818 // Text inserting might be initiated by other source instead of keyboard
3819 // events, such as the Characters dialog. In this case the text should be
3820 // sent as an input method event as well.
3821 // TODO(suzhe): It's hard for us to support replacementRange without accessing
3822 // the full web content.
3823 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
3824 NSString* im_text = isAttributedString ? [string string] : string;
3825 if (handlingKeyDown_) {
3826 textToBeInserted_.append(base::SysNSStringToUTF16(im_text));
3828 gfx::Range replacement_range(replacementRange);
3829 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3830 base::SysNSStringToUTF16(im_text), replacement_range, false);
3833 // Inserting text will delete all marked text automatically.
3834 hasMarkedText_ = NO;
3837 - (void)insertText:(id)string {
3838 [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
3841 - (void)viewDidMoveToWindow {
3843 [self updateScreenProperties];
3845 if (canBeKeyView_) {
3846 NSWindow* newWindow = [self window];
3847 // Pointer comparison only, since we don't know if lastWindow_ is still
3850 // If we move into a new window, refresh the frame information. We
3851 // don't need to do it if it was the same window as it used to be in,
3852 // since that case is covered by WasShown(). We only want to do this for
3853 // real browser views, not popups.
3854 if (newWindow != lastWindow_) {
3855 lastWindow_ = newWindow;
3856 renderWidgetHostView_->WindowFrameChanged();
3861 // If we switch windows (or are removed from the view hierarchy), cancel any
3862 // open mouse-downs.
3863 if (hasOpenMouseDown_) {
3864 WebMouseEvent event;
3865 event.type = WebInputEvent::MouseUp;
3866 event.button = WebMouseEvent::ButtonLeft;
3867 renderWidgetHostView_->ForwardMouseEvent(event);
3869 hasOpenMouseDown_ = NO;
3873 - (void)undo:(id)sender {
3874 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3876 web_contents->Undo();
3879 - (void)redo:(id)sender {
3880 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3882 web_contents->Redo();
3885 - (void)cut:(id)sender {
3886 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3888 web_contents->Cut();
3891 - (void)copy:(id)sender {
3892 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3894 web_contents->Copy();
3897 - (void)copyToFindPboard:(id)sender {
3898 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3900 web_contents->CopyToFindPboard();
3903 - (void)paste:(id)sender {
3904 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3906 web_contents->Paste();
3909 - (void)pasteAndMatchStyle:(id)sender {
3910 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3912 web_contents->PasteAndMatchStyle();
3915 - (void)selectAll:(id)sender {
3916 // editCommand_helper_ adds implementations for most NSResponder methods
3917 // dynamically. But the renderer side only sends selection results back to
3918 // the browser if they were triggered by a keyboard event or went through
3919 // one of the Select methods on RWH. Since selectAll: is called from the
3920 // menu handler, neither is true.
3921 // Explicitly call SelectAll() here to make sure the renderer returns
3922 // selection results.
3923 WebContents* web_contents = renderWidgetHostView_->GetWebContents();
3925 web_contents->SelectAll();
3928 - (void)startSpeaking:(id)sender {
3929 renderWidgetHostView_->SpeakSelection();
3932 - (void)stopSpeaking:(id)sender {
3933 renderWidgetHostView_->StopSpeaking();
3936 - (void)cancelComposition {
3937 if (!hasMarkedText_)
3940 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
3941 // doesn't call any NSTextInput functions, such as setMarkedText or
3942 // insertText. So, we need to send an IPC message to a renderer so it can
3943 // delete the composition node.
3944 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3945 [currentInputManager markedTextAbandoned:self];
3947 hasMarkedText_ = NO;
3948 // Should not call [self unmarkText] here, because it'll send unnecessary
3949 // cancel composition IPC message to the renderer.
3952 - (void)confirmComposition {
3953 if (!hasMarkedText_)
3956 if (renderWidgetHostView_->render_widget_host_)
3957 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
3958 base::string16(), gfx::Range::InvalidRange(), false);
3960 [self cancelComposition];
3963 - (void)setPluginImeActive:(BOOL)active {
3964 if (active == pluginImeActive_)
3967 pluginImeActive_ = active;
3969 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition];
3970 renderWidgetHostView_->PluginImeCompositionCompleted(
3971 base::string16(), focusedPluginIdentifier_);
3975 - (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId {
3977 focusedPluginIdentifier_ = pluginId;
3978 else if (focusedPluginIdentifier_ == pluginId)
3979 focusedPluginIdentifier_ = -1;
3981 // Whenever plugin focus changes, plugin IME resets.
3982 [self setPluginImeActive:NO];
3985 - (BOOL)postProcessEventForPluginIme:(NSEvent*)event {
3986 if (!pluginImeActive_)
3989 ComplexTextInputPanel* inputPanel =
3990 [ComplexTextInputPanel sharedComplexTextInputPanel];
3991 NSString* composited_string = nil;
3992 BOOL handled = [inputPanel interpretKeyEvent:event
3993 string:&composited_string];
3994 if (composited_string) {
3995 renderWidgetHostView_->PluginImeCompositionCompleted(
3996 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_);
3997 pluginImeActive_ = NO;
4002 - (void)checkForPluginImeCancellation {
4003 if (pluginImeActive_ &&
4004 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) {
4005 renderWidgetHostView_->PluginImeCompositionCompleted(
4006 base::string16(), focusedPluginIdentifier_);
4007 pluginImeActive_ = NO;
4011 // Overriding a NSResponder method to support application services.
4013 - (id)validRequestorForSendType:(NSString*)sendType
4014 returnType:(NSString*)returnType {
4016 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType];
4017 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType];
4018 BOOL hasText = !renderWidgetHostView_->selected_text().empty();
4020 renderWidgetHostView_->text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
4022 if (sendTypeIsString && hasText && !returnType) {
4024 } else if (!sendType && returnTypeIsString && takesText) {
4026 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) {
4029 requestor = [super validRequestorForSendType:sendType
4030 returnType:returnType];
4035 - (void)viewWillStartLiveResize {
4036 [super viewWillStartLiveResize];
4037 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
4039 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), true));
4042 - (void)viewDidEndLiveResize {
4043 [super viewDidEndLiveResize];
4044 RenderWidgetHostImpl* widget = renderWidgetHostView_->render_widget_host_;
4046 widget->Send(new ViewMsg_SetInLiveResize(widget->GetRoutingID(), false));
4049 - (void)updateCursor:(NSCursor*)cursor {
4050 if (currentCursor_ == cursor)
4053 currentCursor_.reset([cursor retain]);
4054 [[self window] invalidateCursorRectsForView:self];
4057 - (void)popupWindowWillClose:(NSNotification *)notification {
4058 renderWidgetHostView_->KillSelf();
4064 // Supporting application services
4066 @implementation RenderWidgetHostViewCocoa(NSServicesRequests)
4068 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard
4069 types:(NSArray*)types {
4070 const std::string& str = renderWidgetHostView_->selected_text();
4071 if (![types containsObject:NSStringPboardType] || str.empty()) return NO;
4073 base::scoped_nsobject<NSString> text(
4074 [[NSString alloc] initWithUTF8String:str.c_str()]);
4075 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType];
4076 [pboard declareTypes:toDeclare owner:nil];
4077 return [pboard setString:text forType:NSStringPboardType];
4080 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
4081 NSString *string = [pboard stringForType:NSStringPboardType];
4082 if (!string) return NO;
4084 // If the user is currently using an IME, confirm the IME input,
4085 // and then insert the text from the service, the same as TextEdit and Safari.
4086 [self confirmComposition];
4087 [self insertText:string];
4095 // "-webkit-app-region: drag | no-drag" is implemented on Mac by excluding
4096 // regions that are not draggable. (See ControlRegionView in
4097 // native_app_window_cocoa.mm). This requires the render host view to be
4098 // draggable by default.
4099 - (BOOL)mouseDownCanMoveWindow {