1// Copyright (c) 2011 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. 4 5#include <QuartzCore/QuartzCore.h> 6 7#include "chrome/browser/renderer_host/render_widget_host_view_mac.h" 8 9#include "base/logging.h" 10#include "base/mac/scoped_cftyperef.h" 11#import "base/mac/scoped_nsautorelease_pool.h" 12#include "base/metrics/histogram.h" 13#import "base/memory/scoped_nsobject.h" 14#include "base/string_util.h" 15#include "base/sys_info.h" 16#include "base/sys_string_conversions.h" 17#import "chrome/browser/accessibility/browser_accessibility_cocoa.h" 18#include "chrome/browser/accessibility/browser_accessibility_state.h" 19#include "chrome/browser/browser_trial.h" 20#include "chrome/browser/gpu_process_host_ui_shim.h" 21#import "chrome/browser/renderer_host/accelerated_plugin_view_mac.h" 22#include "chrome/browser/spellchecker_platform_engine.h" 23#import "chrome/browser/ui/cocoa/rwhvm_editcommand_helper.h" 24#import "chrome/browser/ui/cocoa/view_id_util.h" 25#include "chrome/common/render_messages.h" 26#include "content/browser/browser_thread.h" 27#include "content/browser/gpu_process_host.h" 28#include "content/browser/plugin_process_host.h" 29#include "content/browser/renderer_host/backing_store_mac.h" 30#include "content/browser/renderer_host/render_process_host.h" 31#include "content/browser/renderer_host/render_view_host.h" 32#include "content/browser/renderer_host/render_widget_host.h" 33#include "content/common/edit_command.h" 34#include "content/common/gpu_messages.h" 35#include "content/common/native_web_keyboard_event.h" 36#include "content/common/plugin_messages.h" 37#include "content/common/view_messages.h" 38#include "skia/ext/platform_canvas.h" 39#import "third_party/mozilla/ComplexTextInputPanel.h" 40#include "third_party/skia/include/core/SkColor.h" 41#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebInputEventFactory.h" 42#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" 43#include "ui/gfx/point.h" 44#include "ui/gfx/surface/io_surface_support_mac.h" 45#include "webkit/glue/webaccessibility.h" 46#include "webkit/plugins/npapi/webplugin.h" 47 48using WebKit::WebInputEvent; 49using WebKit::WebInputEventFactory; 50using WebKit::WebMouseEvent; 51using WebKit::WebMouseWheelEvent; 52 53static inline int ToWebKitModifiers(NSUInteger flags) { 54 int modifiers = 0; 55 if (flags & NSControlKeyMask) modifiers |= WebInputEvent::ControlKey; 56 if (flags & NSShiftKeyMask) modifiers |= WebInputEvent::ShiftKey; 57 if (flags & NSAlternateKeyMask) modifiers |= WebInputEvent::AltKey; 58 if (flags & NSCommandKeyMask) modifiers |= WebInputEvent::MetaKey; 59 return modifiers; 60} 61 62@interface RenderWidgetHostViewCocoa (Private) 63+ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event; 64- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r; 65- (void)keyEvent:(NSEvent *)theEvent wasKeyEquivalent:(BOOL)equiv; 66- (void)cancelChildPopups; 67- (void)checkForPluginImeCancellation; 68@end 69 70// This API was published since 10.6. Provide the declaration so it can be 71// // called below when building with the 10.5 SDK. 72#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 73@class NSTextInputContext; 74@interface NSResponder (AppKitDetails) 75- (NSTextInputContext *)inputContext; 76@end 77#endif 78 79namespace { 80 81// Maximum number of characters we allow in a tooltip. 82const size_t kMaxTooltipLength = 1024; 83 84// TODO(suzhe): Upstream this function. 85WebKit::WebColor WebColorFromNSColor(NSColor *color) { 86 CGFloat r, g, b, a; 87 [color getRed:&r green:&g blue:&b alpha:&a]; 88 89 return 90 std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 | 91 std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 | 92 std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 | 93 std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255)); 94} 95 96// Extract underline information from an attributed string. Mostly copied from 97// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm 98void ExtractUnderlines( 99 NSAttributedString* string, 100 std::vector<WebKit::WebCompositionUnderline>* underlines) { 101 int length = [[string string] length]; 102 int i = 0; 103 while (i < length) { 104 NSRange range; 105 NSDictionary* attrs = [string attributesAtIndex:i 106 longestEffectiveRange:&range 107 inRange:NSMakeRange(i, length - i)]; 108 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { 109 WebKit::WebColor color = SK_ColorBLACK; 110 if (NSColor *colorAttr = 111 [attrs objectForKey:NSUnderlineColorAttributeName]) { 112 color = WebColorFromNSColor( 113 [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); 114 } 115 underlines->push_back(WebKit::WebCompositionUnderline( 116 range.location, NSMaxRange(range), color, [style intValue] > 1)); 117 } 118 i = range.location + range.length; 119 } 120} 121 122// EnablePasswordInput() and DisablePasswordInput() are copied from 123// enableSecureTextInput() and disableSecureTextInput() functions in 124// third_party/WebKit/WebCore/platform/SecureTextInput.cpp 125// But we don't call EnableSecureEventInput() and DisableSecureEventInput() 126// here, because they are already called in webkit and they are system wide 127// functions. 128void EnablePasswordInput() { 129 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); 130 TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag, 131 sizeof(CFArrayRef), &inputSources); 132 CFRelease(inputSources); 133} 134 135void DisablePasswordInput() { 136 TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag); 137} 138 139// Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper 140// left of the primary screen (Carbon coordinates), and stuffs it into a 141// gfx::Rect. 142gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) { 143 gfx::Rect new_rect(NSRectToCGRect(rect)); 144 if ([[NSScreen screens] count] > 0) { 145 new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height - 146 new_rect.y() - new_rect.height()); 147 } 148 return new_rect; 149} 150 151// Returns the window that visually contains the given view. This is different 152// from [view window] in the case of tab dragging, where the view's owning 153// window is a floating panel attached to the actual browser window that the tab 154// is visually part of. 155NSWindow* ApparentWindowForView(NSView* view) { 156 // TODO(shess): In case of !window, the view has been removed from 157 // the view hierarchy because the tab isn't main. Could retrieve 158 // the information from the main tab for our window. 159 NSWindow* enclosing_window = [view window]; 160 161 // See if this is a tab drag window. The width check is to distinguish that 162 // case from extension popup windows. 163 NSWindow* ancestor_window = [enclosing_window parentWindow]; 164 if (ancestor_window && (NSWidth([enclosing_window frame]) == 165 NSWidth([ancestor_window frame]))) { 166 enclosing_window = ancestor_window; 167 } 168 169 return enclosing_window; 170} 171 172} // namespace 173 174// RenderWidgetHostView -------------------------------------------------------- 175 176// static 177RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget( 178 RenderWidgetHost* widget) { 179 return new RenderWidgetHostViewMac(widget); 180} 181 182// static 183RenderWidgetHostView* RenderWidgetHostView:: 184 GetRenderWidgetHostViewFromNativeView(gfx::NativeView native_view) { 185 // TODO(port) 186 NOTREACHED() << 187 "RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView not" 188 "implemented"; 189 return NULL; 190} 191 192/////////////////////////////////////////////////////////////////////////////// 193// RenderWidgetHostViewMac, public: 194 195RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget) 196 : render_widget_host_(widget), 197 about_to_validate_and_paint_(false), 198 call_set_needs_display_in_rect_pending_(false), 199 text_input_type_(WebKit::WebTextInputTypeNone), 200 is_loading_(false), 201 is_hidden_(false), 202 shutdown_factory_(this), 203 needs_gpu_visibility_update_after_repaint_(false), 204 compositing_surface_(gfx::kNullPluginWindow) { 205 // |cocoa_view_| owns us and we will be deleted when |cocoa_view_| goes away. 206 // Since we autorelease it, our caller must put |native_view()| into the view 207 // hierarchy right after calling us. 208 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc] 209 initWithRenderWidgetHostViewMac:this] autorelease]; 210 render_widget_host_->set_view(this); 211 212 // Turn on accessibility only if VoiceOver is running. 213 if (IsVoiceOverRunning()) { 214 BrowserAccessibilityState::GetInstance()->OnScreenReaderDetected(); 215 render_widget_host_->EnableRendererAccessibility(); 216 } 217} 218 219RenderWidgetHostViewMac::~RenderWidgetHostViewMac() { 220} 221 222/////////////////////////////////////////////////////////////////////////////// 223// RenderWidgetHostViewMac, RenderWidgetHostView implementation: 224 225void RenderWidgetHostViewMac::InitAsPopup( 226 RenderWidgetHostView* parent_host_view, 227 const gfx::Rect& pos) { 228 bool activatable = popup_type_ == WebKit::WebPopupTypeNone; 229 [cocoa_view_ setCloseOnDeactivate:YES]; 230 [cocoa_view_ setCanBeKeyView:activatable ? YES : NO]; 231 [parent_host_view->GetNativeView() addSubview:cocoa_view_]; 232 233 NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint()); 234 if ([[NSScreen screens] count] > 0) { 235 origin_global.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height - 236 pos.height() - origin_global.y; 237 } 238 NSPoint origin_window = 239 [[cocoa_view_ window] convertScreenToBase:origin_global]; 240 NSPoint origin_view = 241 [cocoa_view_ convertPoint:origin_window fromView:nil]; 242 NSRect initial_frame = NSMakeRect(origin_view.x, 243 origin_view.y, 244 pos.width(), 245 pos.height()); 246 [cocoa_view_ setFrame:initial_frame]; 247} 248 249void RenderWidgetHostViewMac::InitAsFullscreen() { 250 NOTIMPLEMENTED() << "Full screen not implemented on Mac"; 251} 252 253RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const { 254 return render_widget_host_; 255} 256 257void RenderWidgetHostViewMac::DidBecomeSelected() { 258 if (!is_hidden_) 259 return; 260 261 if (tab_switch_paint_time_.is_null()) 262 tab_switch_paint_time_ = base::TimeTicks::Now(); 263 is_hidden_ = false; 264 render_widget_host_->WasRestored(); 265} 266 267void RenderWidgetHostViewMac::WasHidden() { 268 if (is_hidden_) 269 return; 270 271 // If we receive any more paint messages while we are hidden, we want to 272 // ignore them so we don't re-allocate the backing store. We will paint 273 // everything again when we become selected again. 274 is_hidden_ = true; 275 276 // If we have a renderer, then inform it that we are being hidden so it can 277 // reduce its resource utilization. 278 render_widget_host_->WasHidden(); 279} 280 281void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) { 282 gfx::Rect rect = GetViewBounds(); 283 rect.set_size(size); 284 SetBounds(rect); 285} 286 287void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) { 288 // |rect.size()| is view coordinates, |rect.origin| is screen coordinates, 289 // TODO(thakis): fix, http://crbug.com/73362 290 if (is_hidden_) 291 return; 292 293 // During the initial creation of the RenderWidgetHostView in 294 // TabContents::CreateRenderViewForRenderManager, SetSize is called with an 295 // empty size. In the Windows code flow, it is not ignored because subsequent 296 // sizing calls from the OS flow through TCVW::WasSized which calls SetSize() 297 // again. On Cocoa, we rely on the Cocoa view struture and resizer flags to 298 // keep things sized properly. On the other hand, if the size is not empty 299 // then this is a valid request for a pop-up. 300 if (rect.size().IsEmpty()) 301 return; 302 303 // Ignore the position of |rect| for non-popup rwhvs. This is because 304 // background tabs do not have a window, but the window is required for the 305 // coordinate conversions. Popups are always for a visible tab. 306 if (IsPopup()) { 307 // The position of |rect| is screen coordinate system and we have to 308 // consider Cocoa coordinate system is upside-down and also multi-screen. 309 NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint()); 310 if ([[NSScreen screens] count] > 0) { 311 NSSize size = NSMakeSize(rect.width(), rect.height()); 312 size = [cocoa_view_ convertSize:size toView:nil]; 313 NSScreen* screen = 314 static_cast<NSScreen*>([[NSScreen screens] objectAtIndex:0]); 315 origin_global.y = 316 NSHeight([screen frame]) - size.height - origin_global.y; 317 } 318 319 // Then |origin_global| is converted to client coordinate system. 320 DCHECK([cocoa_view_ window]); 321 NSPoint origin_window = 322 [[cocoa_view_ window] convertScreenToBase:origin_global]; 323 NSPoint origin_view = 324 [[cocoa_view_ superview] convertPoint:origin_window fromView:nil]; 325 NSRect frame = NSMakeRect(origin_view.x, origin_view.y, 326 rect.width(), rect.height()); 327 [cocoa_view_ setFrame:frame]; 328 } else { 329 DCHECK([[cocoa_view_ superview] isKindOfClass:[BaseView class]]); 330 BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]); 331 gfx::Rect rect = [superview flipNSRectToRect:[cocoa_view_ frame]]; 332 rect.set_width(rect.width()); 333 rect.set_height(rect.height()); 334 [cocoa_view_ setFrame:[superview flipRectToNSRect:rect]]; 335 } 336} 337 338gfx::NativeView RenderWidgetHostViewMac::GetNativeView() { 339 return native_view(); 340} 341 342void RenderWidgetHostViewMac::MovePluginWindows( 343 const std::vector<webkit::npapi::WebPluginGeometry>& moves) { 344 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 345 // Handle movement of accelerated plugins, which are the only "windowed" 346 // plugins that exist on the Mac. 347 for (std::vector<webkit::npapi::WebPluginGeometry>::const_iterator iter = 348 moves.begin(); 349 iter != moves.end(); 350 ++iter) { 351 webkit::npapi::WebPluginGeometry geom = *iter; 352 353 AcceleratedPluginView* view = ViewForPluginWindowHandle(geom.window); 354 DCHECK(view); 355 if (!view) 356 continue; 357 358 if (geom.rects_valid) { 359 gfx::Rect rect = geom.window_rect; 360 if (geom.visible) { 361 rect.set_x(rect.x() + geom.clip_rect.x()); 362 rect.set_y(rect.y() + geom.clip_rect.y()); 363 rect.set_width(geom.clip_rect.width()); 364 rect.set_height(geom.clip_rect.height()); 365 } 366 NSRect new_rect([cocoa_view_ flipRectToNSRect:rect]); 367 [view setFrame:new_rect]; 368 NSMutableArray* cutout_rects = 369 [NSMutableArray arrayWithCapacity:geom.cutout_rects.size()]; 370 for (unsigned int i = 0; i < geom.cutout_rects.size(); ++i) { 371 // Convert to NSRect, and flip vertically. 372 NSRect cutout_rect = NSRectFromCGRect(geom.cutout_rects[i].ToCGRect()); 373 cutout_rect.origin.y = new_rect.size.height - NSMaxY(cutout_rect); 374 [cutout_rects addObject:[NSValue valueWithRect:cutout_rect]]; 375 } 376 [view setCutoutRects:cutout_rects]; 377 [view setNeedsDisplay:YES]; 378 } 379 380 plugin_container_manager_.SetPluginContainerGeometry(geom); 381 382 BOOL visible = 383 plugin_container_manager_.SurfaceShouldBeVisible(geom.window); 384 [view setHidden:!visible]; 385 } 386} 387 388void RenderWidgetHostViewMac::Focus() { 389 [[cocoa_view_ window] makeFirstResponder:cocoa_view_]; 390} 391 392void RenderWidgetHostViewMac::Blur() { 393 [[cocoa_view_ window] makeFirstResponder:nil]; 394} 395 396bool RenderWidgetHostViewMac::HasFocus() { 397 return [[cocoa_view_ window] firstResponder] == cocoa_view_; 398} 399 400void RenderWidgetHostViewMac::Show() { 401 [cocoa_view_ setHidden:NO]; 402 403 DidBecomeSelected(); 404} 405 406void RenderWidgetHostViewMac::Hide() { 407 [cocoa_view_ setHidden:YES]; 408 409 WasHidden(); 410} 411 412bool RenderWidgetHostViewMac::IsShowing() { 413 return ![cocoa_view_ isHidden]; 414} 415 416gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const { 417 // TODO(shess): In case of !window, the view has been removed from 418 // the view hierarchy because the tab isn't main. Could retrieve 419 // the information from the main tab for our window. 420 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); 421 if (!enclosing_window) 422 return gfx::Rect(); 423 424 NSRect bounds = [cocoa_view_ bounds]; 425 bounds = [cocoa_view_ convertRect:bounds toView:nil]; 426 bounds.origin = [enclosing_window convertBaseToScreen:bounds.origin]; 427 return FlipNSRectToRectScreen(bounds); 428} 429 430void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) { 431 current_cursor_ = cursor; 432 UpdateCursorIfOverSelf(); 433} 434 435void RenderWidgetHostViewMac::UpdateCursorIfOverSelf() { 436 // Do something special (as Win Chromium does) for arrow cursor while loading 437 // a page? TODO(avi): decide 438 // Can we synchronize to the event stream? Switch to -[NSWindow 439 // mouseLocationOutsideOfEventStream] if we cannot. TODO(avi): test and see 440 NSEvent* event = [[cocoa_view_ window] currentEvent]; 441 if ([event window] != [cocoa_view_ window]) 442 return; 443 444 NSPoint event_location = [event locationInWindow]; 445 NSPoint local_point = [cocoa_view_ convertPoint:event_location fromView:nil]; 446 447 if (!NSPointInRect(local_point, [cocoa_view_ bounds])) 448 return; 449 450 NSCursor* ns_cursor = current_cursor_.GetCursor(); 451 [ns_cursor set]; 452} 453 454void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) { 455 is_loading_ = is_loading; 456 UpdateCursorIfOverSelf(); 457} 458 459void RenderWidgetHostViewMac::ImeUpdateTextInputState( 460 WebKit::WebTextInputType type, 461 const gfx::Rect& caret_rect) { 462 if (text_input_type_ != type) { 463 text_input_type_ = type; 464 if (HasFocus()) 465 SetTextInputActive(true); 466 } 467 468 // We need to convert the coordinate of the cursor rectangle sent from the 469 // renderer and save it. Our input method backend uses a coordinate system 470 // whose origin is the upper-left corner of this view. On the other hand, 471 // Cocoa uses a coordinate system whose origin is the lower-left corner of 472 // this view. So, we convert the cursor rectangle and save it. 473 [cocoa_view_ setCaretRect:[cocoa_view_ flipRectToNSRect:caret_rect]]; 474} 475 476void RenderWidgetHostViewMac::ImeCancelComposition() { 477 [cocoa_view_ cancelComposition]; 478} 479 480void RenderWidgetHostViewMac::DidUpdateBackingStore( 481 const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, 482 const std::vector<gfx::Rect>& copy_rects) { 483 if (!is_hidden_) { 484 std::vector<gfx::Rect> rects(copy_rects); 485 486 // Because the findbar might be open, we cannot use scrollRect:by: here. For 487 // now, simply mark all of scroll rect as dirty. 488 if (!scroll_rect.IsEmpty()) 489 rects.push_back(scroll_rect); 490 491 for (size_t i = 0; i < rects.size(); ++i) { 492 NSRect ns_rect = [cocoa_view_ flipRectToNSRect:rects[i]]; 493 494 if (about_to_validate_and_paint_) { 495 // As much as we'd like to use -setNeedsDisplayInRect: here, we can't. 496 // We're in the middle of executing a -drawRect:, and as soon as it 497 // returns Cocoa will clear its record of what needs display. We 498 // instead use |performSelector:| to call |setNeedsDisplayInRect:| 499 // after returning to the main loop, at which point |drawRect:| is no 500 // longer on the stack. 501 DCHECK([NSThread isMainThread]); 502 if (!call_set_needs_display_in_rect_pending_) { 503 [cocoa_view_ performSelector:@selector(callSetNeedsDisplayInRect) 504 withObject:nil 505 afterDelay:0]; 506 call_set_needs_display_in_rect_pending_ = true; 507 invalid_rect_ = ns_rect; 508 } else { 509 // The old invalid rect is probably invalid now, since the view has 510 // most likely been resized, but there's no harm in dirtying the 511 // union. In the limit, this becomes equivalent to dirtying the 512 // whole view. 513 invalid_rect_ = NSUnionRect(invalid_rect_, ns_rect); 514 } 515 } else { 516 [cocoa_view_ setNeedsDisplayInRect:ns_rect]; 517 } 518 } 519 520 if (!about_to_validate_and_paint_) 521 [cocoa_view_ displayIfNeeded]; 522 } 523 524 // If |about_to_validate_and_paint_| is set, then -drawRect: is on the stack 525 // and it's not allowed to call -setHidden on the accelerated view. In that 526 // case, -callSetNeedsDisplayInRect: will hide it later. 527 // If |about_to_validate_and_paint_| is not set, do it now. 528 if (!about_to_validate_and_paint_) 529 HandleDelayedGpuViewHiding(); 530} 531 532void RenderWidgetHostViewMac::RenderViewGone(base::TerminationStatus status, 533 int error_code) { 534 // TODO(darin): keep this around, and draw sad-tab into it. 535 UpdateCursorIfOverSelf(); 536 Destroy(); 537} 538 539void RenderWidgetHostViewMac::Destroy() { 540 // On Windows, popups are implemented with a popup window style, so that when 541 // an event comes in that would "cancel" it, it receives the OnCancelMode 542 // message and can kill itself. Alas, on the Mac, views cannot capture events 543 // outside of themselves. On Windows, if Destroy is being called on a view, 544 // then the event causing the destroy had also cancelled any popups by the 545 // time Destroy() was called. On the Mac we have to destroy all the popups 546 // ourselves. 547 548 // Depth-first destroy all popups. Use ShutdownHost() to enforce 549 // deepest-first ordering. 550 for (NSView* subview in [cocoa_view_ subviews]) { 551 if ([subview isKindOfClass:[RenderWidgetHostViewCocoa class]]) { 552 [static_cast<RenderWidgetHostViewCocoa*>(subview) 553 renderWidgetHostViewMac]->ShutdownHost(); 554 } else if ([subview isKindOfClass:[AcceleratedPluginView class]]) { 555 [static_cast<AcceleratedPluginView*>(subview) 556 onRenderWidgetHostViewGone]; 557 } 558 } 559 560 // We've been told to destroy. 561 [cocoa_view_ retain]; 562 [cocoa_view_ removeFromSuperview]; 563 [cocoa_view_ autorelease]; 564 565 // We get this call just before |render_widget_host_| deletes 566 // itself. But we are owned by |cocoa_view_|, which may be retained 567 // by some other code. Examples are TabContentsViewMac's 568 // |latent_focus_view_| and TabWindowController's 569 // |cachedContentView_|. 570 render_widget_host_ = NULL; 571} 572 573// Called from the renderer to tell us what the tooltip text should be. It 574// calls us frequently so we need to cache the value to prevent doing a lot 575// of repeat work. 576void RenderWidgetHostViewMac::SetTooltipText(const std::wstring& tooltip_text) { 577 if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) { 578 tooltip_text_ = tooltip_text; 579 580 // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on 581 // Windows; we're just trying to be polite. Don't persist the trimmed 582 // string, as then the comparison above will always fail and we'll try to 583 // set it again every single time the mouse moves. 584 std::wstring display_text = tooltip_text_; 585 if (tooltip_text_.length() > kMaxTooltipLength) 586 display_text = tooltip_text_.substr(0, kMaxTooltipLength); 587 588 NSString* tooltip_nsstring = base::SysWideToNSString(display_text); 589 [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring]; 590 } 591} 592 593// 594// RenderWidgetHostViewCocoa uses the stored selection text, 595// which implements NSServicesRequests protocol. 596// 597void RenderWidgetHostViewMac::SelectionChanged(const std::string& text) { 598 selected_text_ = text; 599} 600 601bool RenderWidgetHostViewMac::IsPopup() const { 602 return popup_type_ != WebKit::WebPopupTypeNone; 603} 604 605BackingStore* RenderWidgetHostViewMac::AllocBackingStore( 606 const gfx::Size& size) { 607 return new BackingStoreMac(render_widget_host_, size); 608} 609 610// Sets whether or not to accept first responder status. 611void RenderWidgetHostViewMac::SetTakesFocusOnlyOnMouseDown(bool flag) { 612 [cocoa_view_ setTakesFocusOnlyOnMouseDown:flag]; 613} 614 615void RenderWidgetHostViewMac::KillSelf() { 616 if (shutdown_factory_.empty()) { 617 [cocoa_view_ setHidden:YES]; 618 MessageLoop::current()->PostTask(FROM_HERE, 619 shutdown_factory_.NewRunnableMethod( 620 &RenderWidgetHostViewMac::ShutdownHost)); 621 } 622} 623 624void RenderWidgetHostViewMac::PluginFocusChanged(bool focused, int plugin_id) { 625 [cocoa_view_ pluginFocusChanged:(focused ? YES : NO) forPlugin:plugin_id]; 626} 627 628void RenderWidgetHostViewMac::StartPluginIme() { 629 [cocoa_view_ setPluginImeActive:YES]; 630} 631 632bool RenderWidgetHostViewMac::PostProcessEventForPluginIme( 633 const NativeWebKeyboardEvent& event) { 634 // Check WebInputEvent type since multiple types of events can be sent into 635 // WebKit for the same OS event (e.g., RawKeyDown and Char), so filtering is 636 // necessary to avoid double processing. 637 // Also check the native type, since NSFlagsChanged is considered a key event 638 // for WebKit purposes, but isn't considered a key event by the OS. 639 if (event.type == WebInputEvent::RawKeyDown && 640 [event.os_event type] == NSKeyDown) 641 return [cocoa_view_ postProcessEventForPluginIme:event.os_event]; 642 return false; 643} 644 645void RenderWidgetHostViewMac::PluginImeCompositionCompleted( 646 const string16& text, int plugin_id) { 647 if (render_widget_host_) { 648 render_widget_host_->Send(new ViewMsg_PluginImeCompositionCompleted( 649 render_widget_host_->routing_id(), text, plugin_id)); 650 } 651} 652 653gfx::PluginWindowHandle 654RenderWidgetHostViewMac::AllocateFakePluginWindowHandle(bool opaque, 655 bool root) { 656 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 657 658 // |render_widget_host_| is set to NULL when |RWHVMac::Destroy()| has 659 // completed. If |AllocateFakePluginWindowHandle()| is called after that, 660 // we will crash when the AcceleratedPluginView we allocate below is 661 // destroyed. 662 DCHECK(render_widget_host_); 663 664 // Create an NSView to host the plugin's/compositor's pixels. 665 gfx::PluginWindowHandle handle = 666 plugin_container_manager_.AllocateFakePluginWindowHandle(opaque, root); 667 668 scoped_nsobject<AcceleratedPluginView> plugin_view( 669 [[AcceleratedPluginView alloc] initWithRenderWidgetHostViewMac:this 670 pluginHandle:handle]); 671 [plugin_view setHidden:YES]; 672 673 [cocoa_view_ addSubview:plugin_view]; 674 plugin_views_[handle] = plugin_view; 675 676 return handle; 677} 678 679void RenderWidgetHostViewMac::DestroyFakePluginWindowHandle( 680 gfx::PluginWindowHandle window) { 681 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 682 PluginViewMap::iterator it = plugin_views_.find(window); 683 DCHECK(plugin_views_.end() != it); 684 if (plugin_views_.end() == it) { 685 return; 686 } 687 [it->second removeFromSuperview]; 688 plugin_views_.erase(it); 689 690 // The view's dealloc will call DeallocFakePluginWindowHandle(), which will 691 // remove the handle from |plugin_container_manager_|. This code path is 692 // taken if a plugin is removed, but the RWHVMac itself stays alive. 693} 694 695// This is called by AcceleratedPluginView's -dealloc. 696void RenderWidgetHostViewMac::DeallocFakePluginWindowHandle( 697 gfx::PluginWindowHandle window) { 698 // When a browser window with a GpuScheduler is closed, the render process 699 // will attempt to finish all GL commands. It will busy-wait on the GPU 700 // process until the command queue is empty. If a paint is pending, the GPU 701 // process won't process any GL commands until the browser sends a paint ack, 702 // but since the browser window is already closed, it will never arrive. 703 // To resolve this we ask the GPU process to destroy the command buffer 704 // associated with the given render widget. Once the command buffer is 705 // destroyed, all GL commands from the renderer will immediately receive 706 // channel error. 707 if (render_widget_host_ && 708 plugin_container_manager_.IsRootContainer(window)) { 709 GpuProcessHost::SendOnIO( 710 render_widget_host_->process()->id(), 711 content::CAUSE_FOR_GPU_LAUNCH_NO_LAUNCH, 712 new GpuMsg_DestroyCommandBuffer( 713 render_widget_host_->process()->id(), 714 render_widget_host_->routing_id())); 715 } 716 717 plugin_container_manager_.DestroyFakePluginWindowHandle(window); 718} 719 720AcceleratedPluginView* RenderWidgetHostViewMac::ViewForPluginWindowHandle( 721 gfx::PluginWindowHandle window) { 722 PluginViewMap::iterator it = plugin_views_.find(window); 723 DCHECK(plugin_views_.end() != it); 724 if (plugin_views_.end() == it) 725 return nil; 726 return it->second; 727} 728 729void RenderWidgetHostViewMac::AcceleratedSurfaceSetIOSurface( 730 gfx::PluginWindowHandle window, 731 int32 width, 732 int32 height, 733 uint64 io_surface_identifier) { 734 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 735 plugin_container_manager_.SetSizeAndIOSurface(window, 736 width, 737 height, 738 io_surface_identifier); 739 740 if (plugin_container_manager_.IsRootContainer(window)) { 741 // Fake up a WebPluginGeometry for the root window to set the 742 // container's size; we will never get a notification from the 743 // browser about the root window, only plugins. 744 webkit::npapi::WebPluginGeometry geom; 745 gfx::Rect rect(0, 0, width, height); 746 geom.window = window; 747 geom.window_rect = rect; 748 geom.clip_rect = rect; 749 geom.visible = true; 750 geom.rects_valid = true; 751 MovePluginWindows(std::vector<webkit::npapi::WebPluginGeometry>(1, geom)); 752 } 753} 754 755void RenderWidgetHostViewMac::AcceleratedSurfaceSetTransportDIB( 756 gfx::PluginWindowHandle window, 757 int32 width, 758 int32 height, 759 TransportDIB::Handle transport_dib) { 760 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 761 plugin_container_manager_.SetSizeAndTransportDIB(window, 762 width, 763 height, 764 transport_dib); 765} 766 767void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped( 768 gfx::PluginWindowHandle window, 769 uint64 surface_id, 770 int renderer_id, 771 int32 route_id, 772 int gpu_host_id, 773 uint64 swap_buffers_count) { 774 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 775 AcceleratedPluginView* view = ViewForPluginWindowHandle(window); 776 DCHECK(view); 777 if (!view) 778 return; 779 780 plugin_container_manager_.SetSurfaceWasPaintedTo(window, surface_id); 781 782 // The surface is hidden until its first paint, to not show gargabe. 783 if (plugin_container_manager_.SurfaceShouldBeVisible(window)) 784 [view setHidden:NO]; 785 [view updateSwapBuffersCount:swap_buffers_count 786 fromRenderer:renderer_id 787 routeId:route_id 788 gpuHostId:gpu_host_id]; 789} 790 791void RenderWidgetHostViewMac::UpdateRootGpuViewVisibility( 792 bool show_gpu_widget) { 793 // Plugins are destroyed on page navigate. The compositor layer on the other 794 // hand is created on demand and then stays alive until its renderer process 795 // dies (usually on cross-domain navigation). Instead, only a flag 796 // |is_accelerated_compositing_active()| is flipped when the compositor output 797 // should be shown/hidden. 798 // Show/hide the view belonging to the compositor here. 799 plugin_container_manager_.set_gpu_rendering_active(show_gpu_widget); 800 801 gfx::PluginWindowHandle root_handle = 802 plugin_container_manager_.root_container_handle(); 803 if (root_handle != gfx::kNullPluginWindow) { 804 AcceleratedPluginView* view = ViewForPluginWindowHandle(root_handle); 805 DCHECK(view); 806 bool visible = 807 plugin_container_manager_.SurfaceShouldBeVisible(root_handle); 808 [[view window] disableScreenUpdatesUntilFlush]; 809 [view setHidden:!visible]; 810 } 811} 812 813void RenderWidgetHostViewMac::HandleDelayedGpuViewHiding() { 814 if (needs_gpu_visibility_update_after_repaint_) { 815 UpdateRootGpuViewVisibility(false); 816 needs_gpu_visibility_update_after_repaint_ = false; 817 } 818} 819 820void RenderWidgetHostViewMac::AcknowledgeSwapBuffers( 821 int renderer_id, 822 int32 route_id, 823 int gpu_host_id, 824 uint64 swap_buffers_count) { 825 // Called on the display link thread. Hand actual work off to the IO thread, 826 // because |GpuProcessHost::Get()| can only be called there. 827 // Currently, this is never called for plugins. 828 if (render_widget_host_) { 829 DCHECK_EQ(render_widget_host_->process()->id(), renderer_id); 830 // |render_widget_host_->routing_id()| and |route_id| are usually not 831 // equal: The former identifies the channel from the RWH in the browser 832 // process to the corresponding render widget in the renderer process, while 833 // the latter identifies the channel from the GpuCommandBufferStub in the 834 // GPU process to the corresponding command buffer client in the renderer. 835 } 836 837 // TODO(apatrick): Send the acknowledgement via the UI thread when running in 838 // single process or in process GPU mode for now. This is bad from a 839 // performance point of view but the plan is to not use AcceleratedSurface at 840 // all in these cases. 841 if (gpu_host_id == 0) { 842 BrowserThread::PostTask( 843 BrowserThread::UI, 844 FROM_HERE, 845 NewRunnableFunction(&GpuProcessHostUIShim::SendToGpuHost, 846 gpu_host_id, 847 new GpuMsg_AcceleratedSurfaceBuffersSwappedACK( 848 renderer_id, 849 route_id, 850 swap_buffers_count))); 851 } else { 852 GpuProcessHost::SendOnIO( 853 gpu_host_id, 854 content::CAUSE_FOR_GPU_LAUNCH_NO_LAUNCH, 855 new GpuMsg_AcceleratedSurfaceBuffersSwappedACK( 856 renderer_id, route_id, swap_buffers_count)); 857 } 858} 859 860void RenderWidgetHostViewMac::GpuRenderingStateDidChange() { 861 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 862 if (GetRenderWidgetHost()->is_accelerated_compositing_active()) { 863 UpdateRootGpuViewVisibility( 864 GetRenderWidgetHost()->is_accelerated_compositing_active()); 865 } else { 866 needs_gpu_visibility_update_after_repaint_ = true; 867 } 868} 869 870gfx::PluginWindowHandle RenderWidgetHostViewMac::GetCompositingSurface() { 871 if (compositing_surface_ == gfx::kNullPluginWindow) 872 compositing_surface_ = AllocateFakePluginWindowHandle( 873 /*opaque=*/true, /*root=*/true); 874 return compositing_surface_; 875} 876 877void RenderWidgetHostViewMac::DrawAcceleratedSurfaceInstance( 878 CGLContextObj context, 879 gfx::PluginWindowHandle plugin_handle, 880 NSSize size) { 881 // Called on the display link thread. 882 CGLSetCurrentContext(context); 883 884 glMatrixMode(GL_PROJECTION); 885 glLoadIdentity(); 886 // Note that we place the origin at the upper left corner with +y 887 // going down 888 glOrtho(0, size.width, size.height, 0, -1, 1); 889 glMatrixMode(GL_MODELVIEW); 890 glLoadIdentity(); 891 892 plugin_container_manager_.Draw(context, plugin_handle); 893} 894 895void RenderWidgetHostViewMac::ForceTextureReload() { 896 plugin_container_manager_.ForceTextureReload(); 897} 898 899void RenderWidgetHostViewMac::SetVisuallyDeemphasized(const SkColor* color, 900 bool animate) { 901 // This is not used on mac. 902} 903 904void RenderWidgetHostViewMac::ShutdownHost() { 905 shutdown_factory_.RevokeAll(); 906 render_widget_host_->Shutdown(); 907 // Do not touch any members at this point, |this| has been deleted. 908} 909 910bool RenderWidgetHostViewMac::IsVoiceOverRunning() { 911 NSUserDefaults* user_defaults = [NSUserDefaults standardUserDefaults]; 912 [user_defaults addSuiteNamed:@"com.apple.universalaccess"]; 913 return 1 == [user_defaults integerForKey:@"voiceOverOnOffKey"]; 914} 915 916gfx::Rect RenderWidgetHostViewMac::GetViewCocoaBounds() const { 917 return gfx::Rect(NSRectToCGRect([cocoa_view_ bounds])); 918} 919 920gfx::Rect RenderWidgetHostViewMac::GetRootWindowRect() { 921 // TODO(shess): In case of !window, the view has been removed from 922 // the view hierarchy because the tab isn't main. Could retrieve 923 // the information from the main tab for our window. 924 NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); 925 if (!enclosing_window) 926 return gfx::Rect(); 927 928 NSRect bounds = [enclosing_window frame]; 929 return FlipNSRectToRectScreen(bounds); 930} 931 932void RenderWidgetHostViewMac::SetActive(bool active) { 933 if (render_widget_host_) 934 render_widget_host_->SetActive(active); 935 if (HasFocus()) 936 SetTextInputActive(active); 937 if (!active) 938 [cocoa_view_ setPluginImeActive:NO]; 939} 940 941void RenderWidgetHostViewMac::SetWindowVisibility(bool visible) { 942 if (render_widget_host_) { 943 render_widget_host_->Send(new ViewMsg_SetWindowVisibility( 944 render_widget_host_->routing_id(), visible)); 945 } 946} 947 948void RenderWidgetHostViewMac::WindowFrameChanged() { 949 if (render_widget_host_) { 950 render_widget_host_->Send(new ViewMsg_WindowFrameChanged( 951 render_widget_host_->routing_id(), GetRootWindowRect(), 952 GetViewBounds())); 953 } 954} 955 956void RenderWidgetHostViewMac::SetBackground(const SkBitmap& background) { 957 RenderWidgetHostView::SetBackground(background); 958 if (render_widget_host_) 959 render_widget_host_->Send(new ViewMsg_SetBackground( 960 render_widget_host_->routing_id(), background)); 961} 962 963bool RenderWidgetHostViewMac::ContainsNativeView( 964 gfx::NativeView native_view) const { 965 // TODO(port) 966 NOTREACHED() << 967 "RenderWidgetHostViewMac::ContainsNativeView not implemented."; 968 return false; 969} 970 971void RenderWidgetHostViewMac::OnAccessibilityNotifications( 972 const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) { 973 if (!browser_accessibility_manager_.get()) { 974 // Use empty document to process notifications 975 webkit_glue::WebAccessibility empty_document; 976 empty_document.role = WebAccessibility::ROLE_WEB_AREA; 977 empty_document.state = 0; 978 browser_accessibility_manager_.reset( 979 BrowserAccessibilityManager::Create(cocoa_view_, empty_document, NULL)); 980 } 981 browser_accessibility_manager_->OnAccessibilityNotifications(params); 982} 983 984void RenderWidgetHostViewMac::SetTextInputActive(bool active) { 985 if (active) { 986 if (text_input_type_ == WebKit::WebTextInputTypePassword) 987 EnablePasswordInput(); 988 else 989 DisablePasswordInput(); 990 } else { 991 if (text_input_type_ == WebKit::WebTextInputTypePassword) 992 DisablePasswordInput(); 993 } 994} 995 996// RenderWidgetHostViewCocoa --------------------------------------------------- 997 998@implementation RenderWidgetHostViewCocoa 999 1000@synthesize caretRect = caretRect_; 1001 1002- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r { 1003 self = [super initWithFrame:NSZeroRect]; 1004 if (self) { 1005 editCommand_helper_.reset(new RWHVMEditCommandHelper); 1006 editCommand_helper_->AddEditingSelectorsToClass([self class]); 1007 1008 renderWidgetHostView_.reset(r); 1009 canBeKeyView_ = YES; 1010 focusedPluginIdentifier_ = -1; 1011 } 1012 return self; 1013} 1014 1015- (void)setCanBeKeyView:(BOOL)can { 1016 canBeKeyView_ = can; 1017} 1018 1019- (void)setTakesFocusOnlyOnMouseDown:(BOOL)b { 1020 takesFocusOnlyOnMouseDown_ = b; 1021} 1022 1023- (void)setCloseOnDeactivate:(BOOL)b { 1024 closeOnDeactivate_ = b; 1025} 1026 1027- (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent { 1028 NSWindow* window = [self window]; 1029 // If this is a background window, don't handle mouse movement events. This 1030 // is the expected behavior on the Mac as evidenced by other applications. 1031 // Do this only if the window level is NSNormalWindowLevel, as this 1032 // does not necessarily apply in other contexts (e.g. balloons). 1033 if ([theEvent type] == NSMouseMoved && 1034 [window level] == NSNormalWindowLevel && ![window isKeyWindow]) { 1035 return YES; 1036 } 1037 1038 // Use hitTest to check whether the mouse is over a nonWebContentView - in 1039 // which case the mouse event should not be handled by the render host. 1040 const SEL nonWebContentViewSelector = @selector(nonWebContentView); 1041 NSView* contentView = [window contentView]; 1042 NSView* view = [contentView hitTest:[theEvent locationInWindow]]; 1043 // Traverse the superview hierarchy as the hitTest will return the frontmost 1044 // view, such as an NSTextView, while nonWebContentView may be specified by 1045 // its parent view. 1046 while (view) { 1047 if ([view respondsToSelector:nonWebContentViewSelector] && 1048 [view performSelector:nonWebContentViewSelector]) { 1049 // The cursor is over a nonWebContentView - ignore this mouse event. 1050 return YES; 1051 } 1052 view = [view superview]; 1053 } 1054 return NO; 1055} 1056 1057- (void)mouseEvent:(NSEvent*)theEvent { 1058 if ([self shouldIgnoreMouseEvent:theEvent]) { 1059 // If this is the first such event, send a mouse exit to the host view. 1060 if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) { 1061 WebMouseEvent exitEvent = 1062 WebInputEventFactory::mouseEvent(theEvent, self); 1063 exitEvent.type = WebInputEvent::MouseLeave; 1064 exitEvent.button = WebMouseEvent::ButtonNone; 1065 renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(exitEvent); 1066 } 1067 mouseEventWasIgnored_ = YES; 1068 return; 1069 } 1070 1071 if (mouseEventWasIgnored_) { 1072 // If this is the first mouse event after a previous event that was ignored 1073 // due to the hitTest, send a mouse enter event to the host view. 1074 if (renderWidgetHostView_->render_widget_host_) { 1075 WebMouseEvent enterEvent = 1076 WebInputEventFactory::mouseEvent(theEvent, self); 1077 enterEvent.type = WebInputEvent::MouseMove; 1078 enterEvent.button = WebMouseEvent::ButtonNone; 1079 renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(enterEvent); 1080 } 1081 } 1082 mouseEventWasIgnored_ = NO; 1083 1084 // TODO(rohitrao): Probably need to handle other mouse down events here. 1085 if ([theEvent type] == NSLeftMouseDown && takesFocusOnlyOnMouseDown_) { 1086 if (renderWidgetHostView_->render_widget_host_) 1087 renderWidgetHostView_->render_widget_host_->OnMouseActivate(); 1088 1089 // Manually take focus after the click but before forwarding it to the 1090 // renderer. 1091 [[self window] makeFirstResponder:self]; 1092 } 1093 1094 // Don't cancel child popups; killing them on a mouse click would prevent the 1095 // user from positioning the insertion point in the text field spawning the 1096 // popup. A click outside the text field would cause the text field to drop 1097 // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel 1098 // the popup anyway, so we're OK. 1099 1100 NSEventType type = [theEvent type]; 1101 if (type == NSLeftMouseDown) 1102 hasOpenMouseDown_ = YES; 1103 else if (type == NSLeftMouseUp) 1104 hasOpenMouseDown_ = NO; 1105 1106 // TODO(suzhe): We should send mouse events to the input method first if it 1107 // wants to handle them. But it won't work without implementing method 1108 // - (NSUInteger)characterIndexForPoint:. 1109 // See: http://code.google.com/p/chromium/issues/detail?id=47141 1110 // Instead of sending mouse events to the input method first, we now just 1111 // simply confirm all ongoing composition here. 1112 if (type == NSLeftMouseDown || type == NSRightMouseDown || 1113 type == NSOtherMouseDown) { 1114 [self confirmComposition]; 1115 } 1116 1117 const WebMouseEvent& event = 1118 WebInputEventFactory::mouseEvent(theEvent, self); 1119 1120 if (renderWidgetHostView_->render_widget_host_) 1121 renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(event); 1122} 1123 1124- (BOOL)performKeyEquivalent:(NSEvent*)theEvent { 1125 // |performKeyEquivalent:| is sent to all views of a window, not only down the 1126 // responder chain (cf. "Handling Key Equivalents" in 1127 // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html 1128 // ). We only want to handle key equivalents if we're first responder. 1129 if ([[self window] firstResponder] != self) 1130 return NO; 1131 1132 // If we return |NO| from this function, cocoa will send the key event to 1133 // the menu and only if the menu does not process the event to |keyDown:|. We 1134 // want to send the event to a renderer _before_ sending it to the menu, so 1135 // we need to return |YES| for all events that might be swallowed by the menu. 1136 // We do not return |YES| for every keypress because we don't get |keyDown:| 1137 // events for keys that we handle this way. 1138 NSUInteger modifierFlags = [theEvent modifierFlags]; 1139 if ((modifierFlags & NSCommandKeyMask) == 0) { 1140 // Make sure the menu does not contain key equivalents that don't 1141 // contain cmd. 1142 DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]); 1143 return NO; 1144 } 1145 1146 // Command key combinations are sent via performKeyEquivalent rather than 1147 // keyDown:. We just forward this on and if WebCore doesn't want to handle 1148 // it, we let the TabContentsView figure out how to reinject it. 1149 [self keyEvent:theEvent wasKeyEquivalent:YES]; 1150 return YES; 1151} 1152 1153- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event { 1154 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:| 1155 // returned NO. If this function returns |YES|, Cocoa sends the event to 1156 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent 1157 // to us instead of doing key view loop control, ctrl-left/right get handled 1158 // correctly, etc. 1159 // (However, there are still some keys that Cocoa swallows, e.g. the key 1160 // equivalent that Cocoa uses for toggling the input langauge. In this case, 1161 // that's actually a good thing, though -- see http://crbug.com/26115 .) 1162 return YES; 1163} 1164 1165- (void)keyEvent:(NSEvent*)theEvent { 1166 [self keyEvent:theEvent wasKeyEquivalent:NO]; 1167} 1168 1169- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv { 1170 DCHECK([theEvent type] != NSKeyDown || 1171 !equiv == !([theEvent modifierFlags] & NSCommandKeyMask)); 1172 1173 if ([theEvent type] == NSFlagsChanged) { 1174 // Ignore NSFlagsChanged events from the NumLock and Fn keys as 1175 // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm"). 1176 int keyCode = [theEvent keyCode]; 1177 if (!keyCode || keyCode == 10 || keyCode == 63) 1178 return; 1179 } 1180 1181 // Don't cancel child popups; the key events are probably what's triggering 1182 // the popup in the first place. 1183 1184 RenderWidgetHost* widgetHost = renderWidgetHostView_->render_widget_host_; 1185 DCHECK(widgetHost); 1186 1187 NativeWebKeyboardEvent event(theEvent); 1188 1189 // We only handle key down events and just simply forward other events. 1190 if ([theEvent type] != NSKeyDown) { 1191 widgetHost->ForwardKeyboardEvent(event); 1192 1193 // Possibly autohide the cursor. 1194 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent]) 1195 [NSCursor setHiddenUntilMouseMoves:YES]; 1196 1197 return; 1198 } 1199 1200 scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]); 1201 1202 // Records the current marked text state, so that we can know if the marked 1203 // text was deleted or not after handling the key down event. 1204 BOOL oldHasMarkedText = hasMarkedText_; 1205 1206 // This method should not be called recursively. 1207 DCHECK(!handlingKeyDown_); 1208 1209 // Tells insertText: and doCommandBySelector: that we are handling a key 1210 // down event. 1211 handlingKeyDown_ = YES; 1212 1213 // These variables might be set when handling the keyboard event. 1214 // Clear them here so that we can know whether they have changed afterwards. 1215 textToBeInserted_.clear(); 1216 markedText_.clear(); 1217 underlines_.clear(); 1218 unmarkTextCalled_ = NO; 1219 hasEditCommands_ = NO; 1220 editCommands_.clear(); 1221 1222 // Before doing anything with a key down, check to see if plugin IME has been 1223 // cancelled, since the plugin host needs to be informed of that before 1224 // receiving the keydown. 1225 if ([theEvent type] == NSKeyDown) 1226 [self checkForPluginImeCancellation]; 1227 1228 // Sends key down events to input method first, then we can decide what should 1229 // be done according to input method's feedback. 1230 // If a plugin is active, bypass this step since events are forwarded directly 1231 // to the plugin IME. 1232 if (focusedPluginIdentifier_ == -1) 1233 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; 1234 1235 handlingKeyDown_ = NO; 1236 1237 // Indicates if we should send the key event and corresponding editor commands 1238 // after processing the input method result. 1239 BOOL delayEventUntilAfterImeCompostion = NO; 1240 1241 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY 1242 // while an input method is composing or inserting a text. 1243 // Gmail checks this code in its onkeydown handler to stop auto-completing 1244 // e-mail addresses while composing a CJK text. 1245 // If the text to be inserted has only one character, then we don't need this 1246 // trick, because we'll send the text as a key press event instead. 1247 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) { 1248 NativeWebKeyboardEvent fakeEvent = event; 1249 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY 1250 fakeEvent.setKeyIdentifierFromWindowsKeyCode(); 1251 fakeEvent.skip_in_browser = true; 1252 widgetHost->ForwardKeyboardEvent(fakeEvent); 1253 // If this key event was handled by the input method, but 1254 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above) 1255 // enqueued edit commands, then in order to let webkit handle them 1256 // correctly, we need to send the real key event and corresponding edit 1257 // commands after processing the input method result. 1258 // We shouldn't do this if a new marked text was set by the input method, 1259 // otherwise the new marked text might be cancelled by webkit. 1260 if (hasEditCommands_ && !hasMarkedText_) 1261 delayEventUntilAfterImeCompostion = YES; 1262 } else { 1263 if (!editCommands_.empty()) 1264 widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands_); 1265 widgetHost->ForwardKeyboardEvent(event); 1266 } 1267 1268 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the 1269 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will 1270 // be set to NULL. So we check it here and return immediately if it's NULL. 1271 if (!renderWidgetHostView_->render_widget_host_) 1272 return; 1273 1274 // Then send keypress and/or composition related events. 1275 // If there was a marked text or the text to be inserted is longer than 1 1276 // character, then we send the text by calling ConfirmComposition(). 1277 // Otherwise, if the text to be inserted only contains 1 character, then we 1278 // can just send a keypress event which is fabricated by changing the type of 1279 // the keydown event, so that we can retain all necessary informations, such 1280 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to 1281 // prevent the browser from handling it again. 1282 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only 1283 // handle BMP characters here, as we can always insert non-BMP characters as 1284 // text. 1285 BOOL textInserted = NO; 1286 if (textToBeInserted_.length() > 1287 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) { 1288 widgetHost->ImeConfirmComposition(textToBeInserted_); 1289 textInserted = YES; 1290 } 1291 1292 // Updates or cancels the composition. If some text has been inserted, then 1293 // we don't need to cancel the composition explicitly. 1294 if (hasMarkedText_ && markedText_.length()) { 1295 // Sends the updated marked text to the renderer so it can update the 1296 // composition node in WebKit. 1297 // When marked text is available, |selectedRange_| will be the range being 1298 // selected inside the marked text. 1299 widgetHost->ImeSetComposition(markedText_, underlines_, 1300 selectedRange_.location, 1301 NSMaxRange(selectedRange_)); 1302 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) { 1303 if (unmarkTextCalled_) 1304 widgetHost->ImeConfirmComposition(); 1305 else 1306 widgetHost->ImeCancelComposition(); 1307 } 1308 1309 // If the key event was handled by the input method but it also generated some 1310 // edit commands, then we need to send the real key event and corresponding 1311 // edit commands here. This usually occurs when the input method wants to 1312 // finish current composition session but still wants the application to 1313 // handle the key event. See http://crbug.com/48161 for reference. 1314 if (delayEventUntilAfterImeCompostion) { 1315 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event 1316 // with windowsKeyCode == 0xE5 has already been sent to webkit. 1317 // So before sending the real key down event, we need to send a fake key up 1318 // event to balance it. 1319 NativeWebKeyboardEvent fakeEvent = event; 1320 fakeEvent.type = WebKit::WebInputEvent::KeyUp; 1321 fakeEvent.skip_in_browser = true; 1322 widgetHost->ForwardKeyboardEvent(fakeEvent); 1323 // Not checking |renderWidgetHostView_->render_widget_host_| here because 1324 // a key event with |skip_in_browser| == true won't be handled by browser, 1325 // thus it won't destroy the widget. 1326 1327 if (!editCommands_.empty()) 1328 widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands_); 1329 widgetHost->ForwardKeyboardEvent(event); 1330 1331 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the 1332 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will 1333 // be set to NULL. So we check it here and return immediately if it's NULL. 1334 if (!renderWidgetHostView_->render_widget_host_) 1335 return; 1336 } 1337 1338 const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask; 1339 // Only send a corresponding key press event if there is no marked text. 1340 if (!hasMarkedText_) { 1341 if (!textInserted && textToBeInserted_.length() == 1) { 1342 // If a single character was inserted, then we just send it as a keypress 1343 // event. 1344 event.type = WebKit::WebInputEvent::Char; 1345 event.text[0] = textToBeInserted_[0]; 1346 event.text[1] = 0; 1347 event.skip_in_browser = true; 1348 widgetHost->ForwardKeyboardEvent(event); 1349 } else if ((!textInserted || delayEventUntilAfterImeCompostion) && 1350 [[theEvent characters] length] > 0 && 1351 (([theEvent modifierFlags] & kCtrlCmdKeyMask) || 1352 (hasEditCommands_ && editCommands_.empty()))) { 1353 // We don't get insertText: calls if ctrl or cmd is down, or the key event 1354 // generates an insert command. So synthesize a keypress event for these 1355 // cases, unless the key event generated any other command. 1356 event.type = WebKit::WebInputEvent::Char; 1357 event.skip_in_browser = true; 1358 widgetHost->ForwardKeyboardEvent(event); 1359 } 1360 } 1361 1362 // Possibly autohide the cursor. 1363 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent]) 1364 [NSCursor setHiddenUntilMouseMoves:YES]; 1365} 1366 1367- (void)scrollWheel:(NSEvent *)theEvent { 1368 [self cancelChildPopups]; 1369 1370 const WebMouseWheelEvent& event = 1371 WebInputEventFactory::mouseWheelEvent(theEvent, self); 1372 if (renderWidgetHostView_->render_widget_host_) 1373 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(event); 1374} 1375 1376// See the comment in RenderWidgetHostViewMac::Destroy() about cancellation 1377// events. On the Mac we must kill popups on outside events, thus this lovely 1378// case of filicide caused by events on parent views. 1379- (void)cancelChildPopups { 1380 // If this view can be the key view, it is not a popup. Therefore, if it has 1381 // any children, they are popups that need to be canceled. 1382 if (canBeKeyView_) { 1383 for (NSView* subview in [self subviews]) { 1384 if (![subview isKindOfClass:[RenderWidgetHostViewCocoa class]]) 1385 continue; // Skip plugin views. 1386 1387 [static_cast<RenderWidgetHostViewCocoa*>(subview) 1388 renderWidgetHostViewMac]->KillSelf(); 1389 } 1390 } 1391} 1392 1393- (void)setFrameSize:(NSSize)newSize { 1394 [super setFrameSize:newSize]; 1395 if (renderWidgetHostView_->render_widget_host_) 1396 renderWidgetHostView_->render_widget_host_->WasResized(); 1397} 1398 1399- (void)setFrame:(NSRect)frameRect { 1400 [super setFrame:frameRect]; 1401 if (renderWidgetHostView_->render_widget_host_) 1402 renderWidgetHostView_->render_widget_host_->WasResized(); 1403} 1404 1405- (void)setFrameWithDeferredUpdate:(NSRect)frameRect { 1406 [super setFrame:frameRect]; 1407 [self performSelector:@selector(renderWidgetHostWasResized) 1408 withObject:nil 1409 afterDelay:0]; 1410} 1411 1412- (void)renderWidgetHostWasResized { 1413 if (renderWidgetHostView_->render_widget_host_) 1414 renderWidgetHostView_->render_widget_host_->WasResized(); 1415} 1416 1417- (void)callSetNeedsDisplayInRect { 1418 DCHECK([NSThread isMainThread]); 1419 DCHECK(renderWidgetHostView_->call_set_needs_display_in_rect_pending_); 1420 [self setNeedsDisplayInRect:renderWidgetHostView_->invalid_rect_]; 1421 renderWidgetHostView_->call_set_needs_display_in_rect_pending_ = false; 1422 renderWidgetHostView_->invalid_rect_ = NSZeroRect; 1423 1424 renderWidgetHostView_->HandleDelayedGpuViewHiding(); 1425} 1426 1427// Fills with white the parts of the area to the right and bottom for |rect| 1428// that intersect |damagedRect|. 1429- (void)fillBottomRightRemainderOfRect:(gfx::Rect)rect 1430 dirtyRect:(gfx::Rect)damagedRect { 1431 if (damagedRect.right() > rect.right()) { 1432 int x = std::max(rect.right(), damagedRect.x()); 1433 int y = std::min(rect.bottom(), damagedRect.bottom()); 1434 int width = damagedRect.right() - x; 1435 int height = damagedRect.y() - y; 1436 1437 // Extra fun to get around the fact that gfx::Rects can't have 1438 // negative sizes. 1439 if (width < 0) { 1440 x += width; 1441 width = -width; 1442 } 1443 if (height < 0) { 1444 y += height; 1445 height = -height; 1446 } 1447 1448 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)]; 1449 [[NSColor whiteColor] set]; 1450 NSRectFill(r); 1451 } 1452 if (damagedRect.bottom() > rect.bottom()) { 1453 int x = damagedRect.x(); 1454 int y = damagedRect.bottom(); 1455 int width = damagedRect.right() - x; 1456 int height = std::max(rect.bottom(), damagedRect.y()) - y; 1457 1458 // Extra fun to get around the fact that gfx::Rects can't have 1459 // negative sizes. 1460 if (width < 0) { 1461 x += width; 1462 width = -width; 1463 } 1464 if (height < 0) { 1465 y += height; 1466 height = -height; 1467 } 1468 1469 NSRect r = [self flipRectToNSRect:gfx::Rect(x, y, width, height)]; 1470 [[NSColor whiteColor] set]; 1471 NSRectFill(r); 1472 } 1473} 1474 1475- (void)drawRect:(NSRect)dirtyRect { 1476 if (!renderWidgetHostView_->render_widget_host_) { 1477 // TODO(shess): Consider using something more noticable? 1478 [[NSColor whiteColor] set]; 1479 NSRectFill(dirtyRect); 1480 return; 1481 } 1482 1483 const gfx::Rect damagedRect([self flipNSRectToRect:dirtyRect]); 1484 1485 if (renderWidgetHostView_->render_widget_host_-> 1486 is_accelerated_compositing_active()) { 1487 gfx::Rect gpuRect; 1488 1489 gfx::PluginWindowHandle root_handle = 1490 renderWidgetHostView_->plugin_container_manager_.root_container_handle(); 1491 if (root_handle != gfx::kNullPluginWindow) { 1492 AcceleratedPluginView* view = 1493 renderWidgetHostView_->ViewForPluginWindowHandle(root_handle); 1494 DCHECK(view); 1495 if (view && ![view isHidden]) { 1496 NSRect frame = [view frame]; 1497 frame.size = [view cachedSize]; 1498 gpuRect = [self flipNSRectToRect:frame]; 1499 } 1500 } 1501 1502 [self fillBottomRightRemainderOfRect:gpuRect dirtyRect:damagedRect]; 1503 return; 1504 } 1505 1506 DCHECK(!renderWidgetHostView_->about_to_validate_and_paint_); 1507 1508 renderWidgetHostView_->about_to_validate_and_paint_ = true; 1509 BackingStoreMac* backingStore = static_cast<BackingStoreMac*>( 1510 renderWidgetHostView_->render_widget_host_->GetBackingStore(true)); 1511 renderWidgetHostView_->about_to_validate_and_paint_ = false; 1512 1513 if (backingStore) { 1514 gfx::Rect bitmapRect(0, 0, 1515 backingStore->size().width(), 1516 backingStore->size().height()); 1517 1518 // Specify the proper y offset to ensure that the view is rooted to the 1519 // upper left corner. This can be negative, if the window was resized 1520 // smaller and the renderer hasn't yet repainted. 1521 int yOffset = NSHeight([self bounds]) - backingStore->size().height(); 1522 1523 gfx::Rect paintRect = bitmapRect.Intersect(damagedRect); 1524 if (!paintRect.IsEmpty()) { 1525 // if we have a CGLayer, draw that into the window 1526 if (backingStore->cg_layer()) { 1527 CGContextRef context = static_cast<CGContextRef>( 1528 [[NSGraphicsContext currentContext] graphicsPort]); 1529 1530 // TODO: add clipping to dirtyRect if it improves drawing performance. 1531 CGContextDrawLayerAtPoint(context, CGPointMake(0.0, yOffset), 1532 backingStore->cg_layer()); 1533 } else { 1534 // if we haven't created a layer yet, draw the cached bitmap into 1535 // the window. The CGLayer will be created the next time the renderer 1536 // paints. 1537 CGContextRef context = static_cast<CGContextRef>( 1538 [[NSGraphicsContext currentContext] graphicsPort]); 1539 base::mac::ScopedCFTypeRef<CGImageRef> image( 1540 CGBitmapContextCreateImage(backingStore->cg_bitmap())); 1541 CGRect imageRect = bitmapRect.ToCGRect(); 1542 imageRect.origin.y = yOffset; 1543 CGContextDrawImage(context, imageRect, image); 1544 } 1545 } 1546 1547 // Fill the remaining portion of the damagedRect with white 1548 [self fillBottomRightRemainderOfRect:bitmapRect dirtyRect:damagedRect]; 1549 1550 if (!renderWidgetHostView_->whiteout_start_time_.is_null()) { 1551 base::TimeDelta whiteout_duration = base::TimeTicks::Now() - 1552 renderWidgetHostView_->whiteout_start_time_; 1553 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration); 1554 1555 // Reset the start time to 0 so that we start recording again the next 1556 // time the backing store is NULL... 1557 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks(); 1558 } 1559 if (!renderWidgetHostView_->tab_switch_paint_time_.is_null()) { 1560 base::TimeDelta tab_switch_paint_duration = base::TimeTicks::Now() - 1561 renderWidgetHostView_->tab_switch_paint_time_; 1562 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration", 1563 tab_switch_paint_duration); 1564 // Reset tab_switch_paint_time_ to 0 so future tab selections are 1565 // recorded. 1566 renderWidgetHostView_->tab_switch_paint_time_ = base::TimeTicks(); 1567 } 1568 } else { 1569 [[NSColor whiteColor] set]; 1570 NSRectFill(dirtyRect); 1571 if (renderWidgetHostView_->whiteout_start_time_.is_null()) 1572 renderWidgetHostView_->whiteout_start_time_ = base::TimeTicks::Now(); 1573 } 1574} 1575 1576- (BOOL)canBecomeKeyView { 1577 if (!renderWidgetHostView_->render_widget_host_) 1578 return NO; 1579 1580 return canBeKeyView_; 1581} 1582 1583- (BOOL)acceptsFirstResponder { 1584 if (!renderWidgetHostView_->render_widget_host_) 1585 return NO; 1586 1587 return canBeKeyView_ && !takesFocusOnlyOnMouseDown_; 1588} 1589 1590- (BOOL)becomeFirstResponder { 1591 if (!renderWidgetHostView_->render_widget_host_) 1592 return NO; 1593 1594 renderWidgetHostView_->render_widget_host_->Focus(); 1595 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(true); 1596 renderWidgetHostView_->SetTextInputActive(true); 1597 1598 // Cancel any onging composition text which was left before we lost focus. 1599 // TODO(suzhe): We should do it in -resignFirstResponder: method, but 1600 // somehow that method won't be called when switching among different tabs. 1601 // See http://crbug.com/47209 1602 [self cancelComposition]; 1603 1604 NSNumber* direction = [NSNumber numberWithUnsignedInteger: 1605 [[self window] keyViewSelectionDirection]]; 1606 NSDictionary* userInfo = 1607 [NSDictionary dictionaryWithObject:direction 1608 forKey:kSelectionDirection]; 1609 [[NSNotificationCenter defaultCenter] 1610 postNotificationName:kViewDidBecomeFirstResponder 1611 object:self 1612 userInfo:userInfo]; 1613 1614 return YES; 1615} 1616 1617- (BOOL)resignFirstResponder { 1618 renderWidgetHostView_->SetTextInputActive(false); 1619 if (!renderWidgetHostView_->render_widget_host_) 1620 return YES; 1621 1622 if (closeOnDeactivate_) 1623 renderWidgetHostView_->KillSelf(); 1624 1625 renderWidgetHostView_->render_widget_host_->SetInputMethodActive(false); 1626 renderWidgetHostView_->render_widget_host_->Blur(); 1627 1628 // We should cancel any onging composition whenever RWH's Blur() method gets 1629 // called, because in this case, webkit will confirm the ongoing composition 1630 // internally. 1631 [self cancelComposition]; 1632 1633 return YES; 1634} 1635 1636- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { 1637 SEL action = [item action]; 1638 1639 // For now, these actions are always enabled for render view, 1640 // this is sub-optimal. 1641 // TODO(suzhe): Plumb the "can*" methods up from WebCore. 1642 if (action == @selector(undo:) || 1643 action == @selector(redo:) || 1644 action == @selector(cut:) || 1645 action == @selector(copy:) || 1646 action == @selector(copyToFindPboard:) || 1647 action == @selector(paste:) || 1648 action == @selector(pasteAsPlainText:) || 1649 action == @selector(checkSpelling:)) { 1650 return renderWidgetHostView_->render_widget_host_->IsRenderView(); 1651 } 1652 1653 if (action == @selector(toggleContinuousSpellChecking:)) { 1654 RenderViewHost::CommandState state; 1655 state.is_enabled = false; 1656 state.checked_state = RENDER_VIEW_COMMAND_CHECKED_STATE_UNCHECKED; 1657 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 1658 state = static_cast<RenderViewHost*>( 1659 renderWidgetHostView_->render_widget_host_)-> 1660 GetStateForCommand(RENDER_VIEW_COMMAND_TOGGLE_SPELL_CHECK); 1661 } 1662 if ([(id)item respondsToSelector:@selector(setState:)]) { 1663 NSCellStateValue checked_state = 1664 RENDER_VIEW_COMMAND_CHECKED_STATE_UNCHECKED; 1665 switch (state.checked_state) { 1666 case RENDER_VIEW_COMMAND_CHECKED_STATE_UNCHECKED: 1667 checked_state = NSOffState; 1668 break; 1669 case RENDER_VIEW_COMMAND_CHECKED_STATE_CHECKED: 1670 checked_state = NSOnState; 1671 break; 1672 case RENDER_VIEW_COMMAND_CHECKED_STATE_MIXED: 1673 checked_state = NSMixedState; 1674 break; 1675 } 1676 [(id)item setState:checked_state]; 1677 } 1678 return state.is_enabled; 1679 } 1680 1681 return editCommand_helper_->IsMenuItemEnabled(action, self); 1682} 1683 1684- (RenderWidgetHostViewMac*)renderWidgetHostViewMac { 1685 return renderWidgetHostView_.get(); 1686} 1687 1688// Determine whether we should autohide the cursor (i.e., hide it until mouse 1689// move) for the given event. Customize here to be more selective about which 1690// key presses to autohide on. 1691+ (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event { 1692 return ([event type] == NSKeyDown) ? YES : NO; 1693} 1694 1695- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute 1696 index:(NSUInteger)index 1697 maxCount:(NSUInteger)maxCount { 1698 NSArray* fullArray = [self accessibilityAttributeValue:attribute]; 1699 NSUInteger totalLength = [fullArray count]; 1700 if (index >= totalLength) 1701 return nil; 1702 NSUInteger length = MIN(totalLength - index, maxCount); 1703 return [fullArray subarrayWithRange:NSMakeRange(index, length)]; 1704} 1705 1706- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute { 1707 NSArray* fullArray = [self accessibilityAttributeValue:attribute]; 1708 return [fullArray count]; 1709} 1710 1711- (id)accessibilityAttributeValue:(NSString *)attribute { 1712 BrowserAccessibilityManager* manager = 1713 renderWidgetHostView_->browser_accessibility_manager_.get(); 1714 1715 // Contents specifies document view of RenderWidgetHostViewCocoa provided by 1716 // BrowserAccessibilityManager. Children includes all subviews in addition to 1717 // contents. Currently we do not have subviews besides the document view. 1718 if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] || 1719 [attribute isEqualToString:NSAccessibilityContentsAttribute]) && 1720 manager) { 1721 return [NSArray arrayWithObjects:manager-> 1722 GetRoot()->toBrowserAccessibilityCocoa(), nil]; 1723 } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 1724 return NSAccessibilityScrollAreaRole; 1725 } 1726 id ret = [super accessibilityAttributeValue:attribute]; 1727 return ret; 1728} 1729 1730- (NSArray*)accessibilityAttributeNames { 1731 NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; 1732 [ret addObject:NSAccessibilityContentsAttribute]; 1733 [ret addObjectsFromArray:[super accessibilityAttributeNames]]; 1734 return ret; 1735} 1736 1737- (id)accessibilityHitTest:(NSPoint)point { 1738 if (!renderWidgetHostView_->browser_accessibility_manager_.get()) 1739 return self; 1740 NSPoint pointInWindow = [[self window] convertScreenToBase:point]; 1741 NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil]; 1742 localPoint.y = NSHeight([self bounds]) - localPoint.y; 1743 BrowserAccessibilityCocoa* root = renderWidgetHostView_-> 1744 browser_accessibility_manager_-> 1745 GetRoot()->toBrowserAccessibilityCocoa(); 1746 id obj = [root accessibilityHitTest:localPoint]; 1747 return obj; 1748} 1749 1750- (BOOL)accessibilityIsIgnored { 1751 return NO; 1752} 1753 1754- (NSUInteger)accessibilityGetIndexOf:(id)child { 1755 BrowserAccessibilityManager* manager = 1756 renderWidgetHostView_->browser_accessibility_manager_.get(); 1757 // Only child is root. 1758 if (manager && 1759 manager->GetRoot()->toBrowserAccessibilityCocoa() == child) { 1760 return 0; 1761 } else { 1762 return NSNotFound; 1763 } 1764} 1765 1766- (id)accessibilityFocusedUIElement { 1767 BrowserAccessibilityManager* manager = 1768 renderWidgetHostView_->browser_accessibility_manager_.get(); 1769 if (manager) { 1770 BrowserAccessibility* focused_item = manager->GetFocus(NULL); 1771 DCHECK(focused_item); 1772 if (focused_item) { 1773 BrowserAccessibilityCocoa* focused_item_cocoa = 1774 focused_item->toBrowserAccessibilityCocoa(); 1775 DCHECK(focused_item_cocoa); 1776 if (focused_item_cocoa) 1777 return focused_item_cocoa; 1778 } 1779 } 1780 return [super accessibilityFocusedUIElement]; 1781} 1782 1783- (void)doDefaultAction:(int32)accessibilityObjectId { 1784 renderWidgetHostView_->render_widget_host_-> 1785 AccessibilityDoDefaultAction(accessibilityObjectId); 1786} 1787 1788// Convert a web accessibility's location in web coordinates into a cocoa 1789// screen coordinate. 1790- (NSPoint)accessibilityPointInScreen: 1791 (BrowserAccessibilityCocoa*)accessibility { 1792 NSPoint origin = [accessibility origin]; 1793 NSSize size = [accessibility size]; 1794 origin.y = NSHeight([self bounds]) - origin.y; 1795 NSPoint originInWindow = [self convertPoint:origin toView:nil]; 1796 NSPoint originInScreen = [[self window] convertBaseToScreen:originInWindow]; 1797 originInScreen.y = originInScreen.y - size.height; 1798 return originInScreen; 1799} 1800 1801- (void)setAccessibilityFocus:(BOOL)focus 1802 accessibilityId:(int32)accessibilityObjectId { 1803 if (focus) { 1804 renderWidgetHostView_->render_widget_host_-> 1805 SetAccessibilityFocus(accessibilityObjectId); 1806 } 1807} 1808 1809// Spellchecking methods 1810// The next three methods are implemented here since this class is the first 1811// responder for anything in the browser. 1812 1813// This message is sent whenever the user specifies that a word should be 1814// changed from the spellChecker. 1815- (void)changeSpelling:(id)sender { 1816 // Grab the currently selected word from the spell panel, as this is the word 1817 // that we want to replace the selected word in the text with. 1818 NSString* newWord = [[sender selectedCell] stringValue]; 1819 if (newWord != nil) { 1820 RenderWidgetHostViewMac* thisHostView = [self renderWidgetHostViewMac]; 1821 thisHostView->GetRenderWidgetHost()->Replace( 1822 base::SysNSStringToUTF16(newWord)); 1823 } 1824} 1825 1826// This message is sent by NSSpellChecker whenever the next word should be 1827// advanced to, either after a correction or clicking the "Find Next" button. 1828// This isn't documented anywhere useful, like in NSSpellProtocol.h with the 1829// other spelling panel methods. This is probably because Apple assumes that the 1830// the spelling panel will be used with an NSText, which will automatically 1831// catch this and advance to the next word for you. Thanks Apple. 1832// This is also called from the Edit -> Spelling -> Check Spelling menu item. 1833- (void)checkSpelling:(id)sender { 1834 RenderWidgetHostViewMac* thisHostView = [self renderWidgetHostViewMac]; 1835 thisHostView->GetRenderWidgetHost()->AdvanceToNextMisspelling(); 1836} 1837 1838// This message is sent by the spelling panel whenever a word is ignored. 1839- (void)ignoreSpelling:(id)sender { 1840 // Ideally, we would ask the current RenderView for its tag, but that would 1841 // mean making a blocking IPC call from the browser. Instead, 1842 // SpellCheckerPlatform::CheckSpelling remembers the last tag and 1843 // SpellCheckerPlatform::IgnoreWord assumes that is the correct tag. 1844 NSString* wordToIgnore = [sender stringValue]; 1845 if (wordToIgnore != nil) 1846 SpellCheckerPlatform::IgnoreWord(base::SysNSStringToUTF16(wordToIgnore)); 1847} 1848 1849- (void)showGuessPanel:(id)sender { 1850 RenderWidgetHostViewMac* thisHostView = [self renderWidgetHostViewMac]; 1851 thisHostView->GetRenderWidgetHost()->ToggleSpellPanel( 1852 SpellCheckerPlatform::SpellingPanelVisible()); 1853} 1854 1855- (void)toggleContinuousSpellChecking:(id)sender { 1856 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 1857 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 1858 ToggleSpellCheck(); 1859 } 1860} 1861 1862// END Spellchecking methods 1863 1864// Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm 1865// with minor modifications for code style and commenting. 1866// 1867// The 'public' interface is -setToolTipAtMousePoint:. This differs from 1868// -setToolTip: in that the updated tooltip takes effect immediately, 1869// without the user's having to move the mouse out of and back into the view. 1870// 1871// Unfortunately, doing this requires sending fake mouseEnter/Exit events to 1872// the view, which in turn requires overriding some internal tracking-rect 1873// methods (to keep track of its owner & userdata, which need to be filled out 1874// in the fake events.) --snej 7/6/09 1875 1876 1877/* 1878 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 1879 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com) 1880 * 1881 * Redistribution and use in source and binary forms, with or without 1882 * modification, are permitted provided that the following conditions 1883 * are met: 1884 * 1885 * 1. Redistributions of source code must retain the above copyright 1886 * notice, this list of conditions and the following disclaimer. 1887 * 2. Redistributions in binary form must reproduce the above copyright 1888 * notice, this list of conditions and the following disclaimer in the 1889 * documentation and/or other materials provided with the distribution. 1890 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 1891 * its contributors may be used to endorse or promote products derived 1892 * from this software without specific prior written permission. 1893 * 1894 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 1895 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 1896 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 1897 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 1898 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 1899 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 1900 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 1901 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 1902 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 1903 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1904 */ 1905 1906// Any non-zero value will do, but using something recognizable might help us 1907// debug some day. 1908static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE; 1909 1910// Override of a public NSView method, replacing the inherited functionality. 1911// See above for rationale. 1912- (NSTrackingRectTag)addTrackingRect:(NSRect)rect 1913 owner:(id)owner 1914 userData:(void *)data 1915 assumeInside:(BOOL)assumeInside { 1916 DCHECK(trackingRectOwner_ == nil); 1917 trackingRectOwner_ = owner; 1918 trackingRectUserData_ = data; 1919 return kTrackingRectTag; 1920} 1921 1922// Override of (apparently) a private NSView method(!) See above for rationale. 1923- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect 1924 owner:(id)owner 1925 userData:(void *)data 1926 assumeInside:(BOOL)assumeInside 1927 useTrackingNum:(int)tag { 1928 DCHECK(tag == 0 || tag == kTrackingRectTag); 1929 DCHECK(trackingRectOwner_ == nil); 1930 trackingRectOwner_ = owner; 1931 trackingRectUserData_ = data; 1932 return kTrackingRectTag; 1933} 1934 1935// Override of (apparently) a private NSView method(!) See above for rationale. 1936- (void)_addTrackingRects:(NSRect *)rects 1937 owner:(id)owner 1938 userDataList:(void **)userDataList 1939 assumeInsideList:(BOOL *)assumeInsideList 1940 trackingNums:(NSTrackingRectTag *)trackingNums 1941 count:(int)count { 1942 DCHECK(count == 1); 1943 DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag); 1944 DCHECK(trackingRectOwner_ == nil); 1945 trackingRectOwner_ = owner; 1946 trackingRectUserData_ = userDataList[0]; 1947 trackingNums[0] = kTrackingRectTag; 1948} 1949 1950// Override of a public NSView method, replacing the inherited functionality. 1951// See above for rationale. 1952- (void)removeTrackingRect:(NSTrackingRectTag)tag { 1953 if (tag == 0) 1954 return; 1955 1956 if (tag == kTrackingRectTag) { 1957 trackingRectOwner_ = nil; 1958 return; 1959 } 1960 1961 if (tag == lastToolTipTag_) { 1962 [super removeTrackingRect:tag]; 1963 lastToolTipTag_ = 0; 1964 return; 1965 } 1966 1967 // If any other tracking rect is being removed, we don't know how it was 1968 // created and it's possible there's a leak involved (see Radar 3500217). 1969 NOTREACHED(); 1970} 1971 1972// Override of (apparently) a private NSView method(!) 1973- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count { 1974 for (int i = 0; i < count; ++i) { 1975 int tag = tags[i]; 1976 if (tag == 0) 1977 continue; 1978 DCHECK(tag == kTrackingRectTag); 1979 trackingRectOwner_ = nil; 1980 } 1981} 1982 1983// Sends a fake NSMouseExited event to the view for its current tracking rect. 1984- (void)_sendToolTipMouseExited { 1985 // Nothing matters except window, trackingNumber, and userData. 1986 int windowNumber = [[self window] windowNumber]; 1987 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited 1988 location:NSMakePoint(0, 0) 1989 modifierFlags:0 1990 timestamp:0 1991 windowNumber:windowNumber 1992 context:NULL 1993 eventNumber:0 1994 trackingNumber:kTrackingRectTag 1995 userData:trackingRectUserData_]; 1996 [trackingRectOwner_ mouseExited:fakeEvent]; 1997} 1998 1999// Sends a fake NSMouseEntered event to the view for its current tracking rect. 2000- (void)_sendToolTipMouseEntered { 2001 // Nothing matters except window, trackingNumber, and userData. 2002 int windowNumber = [[self window] windowNumber]; 2003 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered 2004 location:NSMakePoint(0, 0) 2005 modifierFlags:0 2006 timestamp:0 2007 windowNumber:windowNumber 2008 context:NULL 2009 eventNumber:0 2010 trackingNumber:kTrackingRectTag 2011 userData:trackingRectUserData_]; 2012 [trackingRectOwner_ mouseEntered:fakeEvent]; 2013} 2014 2015// Sets the view's current tooltip, to be displayed at the current mouse 2016// location. (This does not make the tooltip appear -- as usual, it only 2017// appears after a delay.) Pass null to remove the tooltip. 2018- (void)setToolTipAtMousePoint:(NSString *)string { 2019 NSString *toolTip = [string length] == 0 ? nil : string; 2020 if ((toolTip && toolTip_ && [toolTip isEqualToString:toolTip_]) || 2021 (!toolTip && !toolTip_)) { 2022 return; 2023 } 2024 2025 if (toolTip_) { 2026 [self _sendToolTipMouseExited]; 2027 } 2028 2029 toolTip_.reset([toolTip copy]); 2030 2031 if (toolTip) { 2032 // See radar 3500217 for why we remove all tooltips 2033 // rather than just the single one we created. 2034 [self removeAllToolTips]; 2035 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000); 2036 lastToolTipTag_ = [self addToolTipRect:wideOpenRect 2037 owner:self 2038 userData:NULL]; 2039 [self _sendToolTipMouseEntered]; 2040 } 2041} 2042 2043// NSView calls this to get the text when displaying the tooltip. 2044- (NSString *)view:(NSView *)view 2045 stringForToolTip:(NSToolTipTag)tag 2046 point:(NSPoint)point 2047 userData:(void *)data { 2048 return [[toolTip_ copy] autorelease]; 2049} 2050 2051// Below is our NSTextInput implementation. 2052// 2053// When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following 2054// functions to process this event. 2055// 2056// [WebHTMLView keyDown] -> 2057// EventHandler::keyEvent() -> 2058// ... 2059// [WebEditorClient handleKeyboardEvent] -> 2060// [WebHTMLView _interceptEditingKeyEvent] -> 2061// [NSResponder interpretKeyEvents] -> 2062// [WebHTMLView insertText] -> 2063// Editor::insertText() 2064// 2065// Unfortunately, it is hard for Chromium to use this implementation because 2066// it causes key-typing jank. 2067// RenderWidgetHostViewMac is running in a browser process. On the other 2068// hand, Editor and EventHandler are running in a renderer process. 2069// So, if we used this implementation, a NSKeyDown event is dispatched to 2070// the following functions of Chromium. 2071// 2072// [RenderWidgetHostViewMac keyEvent] (browser) -> 2073// |Sync IPC (KeyDown)| (*1) -> 2074// EventHandler::keyEvent() (renderer) -> 2075// ... 2076// EditorClientImpl::handleKeyboardEvent() (renderer) -> 2077// |Sync IPC| (*2) -> 2078// [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) -> 2079// [self interpretKeyEvents] -> 2080// [RenderWidgetHostViewMac insertText] (browser) -> 2081// |Async IPC| -> 2082// Editor::insertText() (renderer) 2083// 2084// (*1) we need to wait until this call finishes since WebHTMLView uses the 2085// result of EventHandler::keyEvent(). 2086// (*2) we need to wait until this call finishes since WebEditorClient uses 2087// the result of [WebHTMLView _interceptEditingKeyEvent]. 2088// 2089// This needs many sync IPC messages sent between a browser and a renderer for 2090// each key event, which would probably result in key-typing jank. 2091// To avoid this problem, this implementation processes key events (and input 2092// method events) totally in a browser process and sends asynchronous input 2093// events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a 2094// renderer process. 2095// 2096// [RenderWidgetHostViewMac keyEvent] (browser) -> 2097// |Async IPC (RawKeyDown)| -> 2098// [self interpretKeyEvents] -> 2099// [RenderWidgetHostViewMac insertText] (browser) -> 2100// |Async IPC (Char)| -> 2101// Editor::insertText() (renderer) 2102// 2103// Since this implementation doesn't have to wait any IPC calls, this doesn't 2104// make any key-typing jank. --hbono 7/23/09 2105// 2106extern "C" { 2107extern NSString *NSTextInputReplacementRangeAttributeName; 2108} 2109 2110- (NSArray *)validAttributesForMarkedText { 2111 // This code is just copied from WebKit except renaming variables. 2112 if (!validAttributesForMarkedText_) { 2113 validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects: 2114 NSUnderlineStyleAttributeName, 2115 NSUnderlineColorAttributeName, 2116 NSMarkedClauseSegmentAttributeName, 2117 NSTextInputReplacementRangeAttributeName, 2118 nil]); 2119 } 2120 return validAttributesForMarkedText_.get(); 2121} 2122 2123- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { 2124 NOTIMPLEMENTED(); 2125 return NSNotFound; 2126} 2127 2128- (NSRect)firstRectForCharacterRange:(NSRange)theRange { 2129 // An input method requests a cursor rectangle to display its candidate 2130 // window. 2131 // Calculate the screen coordinate of the cursor rectangle saved in 2132 // RenderWidgetHostViewMac::ImeUpdateTextInputState() and send it to the 2133 // input method. 2134 // Since this window may be moved since we receive the cursor rectangle last 2135 // time we sent the cursor rectangle to the input method, so we should map 2136 // from the view coordinate to the screen coordinate every time when an input 2137 // method need it. 2138 NSRect resultRect = [self convertRect:caretRect_ toView:nil]; 2139 NSWindow* window = [self window]; 2140 if (window) 2141 resultRect.origin = [window convertBaseToScreen:resultRect.origin]; 2142 2143 return resultRect; 2144} 2145 2146- (NSRange)selectedRange { 2147 // Return the selected range saved in the setMarkedText method. 2148 return hasMarkedText_ ? selectedRange_ : NSMakeRange(NSNotFound, 0); 2149} 2150 2151- (NSRange)markedRange { 2152 // An input method calls this method to check if an application really has 2153 // a text being composed when hasMarkedText call returns true. 2154 // Returns the range saved in the setMarkedText method so the input method 2155 // calls the setMarkedText method and we can update the composition node 2156 // there. (When this method returns an empty range, the input method doesn't 2157 // call the setMarkedText method.) 2158 return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0); 2159} 2160 2161- (NSAttributedString *)attributedSubstringFromRange:(NSRange)range { 2162 // TODO(hbono): Even though many input method works without implementing 2163 // this method, we need to save a copy of the string in the setMarkedText 2164 // method and create a NSAttributedString with the given range. 2165 // http://crbug.com/37715 2166 return nil; 2167} 2168 2169- (NSInteger)conversationIdentifier { 2170 return reinterpret_cast<NSInteger>(self); 2171} 2172 2173// Each RenderWidgetHostViewCocoa has its own input context, but we return 2174// nil when the caret is in non-editable content or password box to avoid 2175// making input methods do their work. 2176- (NSTextInputContext *)inputContext { 2177 if (focusedPluginIdentifier_ != -1) 2178 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext]; 2179 2180 switch(renderWidgetHostView_->text_input_type_) { 2181 case WebKit::WebTextInputTypeNone: 2182 case WebKit::WebTextInputTypePassword: 2183 return nil; 2184 default: 2185 return [super inputContext]; 2186 } 2187} 2188 2189- (BOOL)hasMarkedText { 2190 // An input method calls this function to figure out whether or not an 2191 // application is really composing a text. If it is composing, it calls 2192 // the markedRange method, and maybe calls the setMarkedText method. 2193 // It seems an input method usually calls this function when it is about to 2194 // cancel an ongoing composition. If an application has a non-empty marked 2195 // range, it calls the setMarkedText method to delete the range. 2196 return hasMarkedText_; 2197} 2198 2199- (void)unmarkText { 2200 // Delete the composition node of the renderer and finish an ongoing 2201 // composition. 2202 // It seems an input method calls the setMarkedText method and set an empty 2203 // text when it cancels an ongoing composition, i.e. I have never seen an 2204 // input method calls this method. 2205 hasMarkedText_ = NO; 2206 markedText_.clear(); 2207 underlines_.clear(); 2208 2209 // If we are handling a key down event, then ConfirmComposition() will be 2210 // called in keyEvent: method. 2211 if (!handlingKeyDown_) 2212 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(); 2213 else 2214 unmarkTextCalled_ = YES; 2215} 2216 2217- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange { 2218 // An input method updates the composition string. 2219 // We send the given text and range to the renderer so it can update the 2220 // composition node of WebKit. 2221 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; 2222 NSString* im_text = isAttributedString ? [string string] : string; 2223 int length = [im_text length]; 2224 2225 markedRange_ = NSMakeRange(0, length); 2226 selectedRange_ = newSelRange; 2227 markedText_ = base::SysNSStringToUTF16(im_text); 2228 hasMarkedText_ = (length > 0); 2229 2230 underlines_.clear(); 2231 if (isAttributedString) { 2232 ExtractUnderlines(string, &underlines_); 2233 } else { 2234 // Use a thin black underline by default. 2235 underlines_.push_back( 2236 WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false)); 2237 } 2238 2239 // If we are handling a key down event, then SetComposition() will be 2240 // called in keyEvent: method. 2241 // Input methods of Mac use setMarkedText calls with an empty text to cancel 2242 // an ongoing composition. So, we should check whether or not the given text 2243 // is empty to update the input method state. (Our input method backend can 2244 // automatically cancels an ongoing composition when we send an empty text. 2245 // So, it is OK to send an empty text to the renderer.) 2246 if (!handlingKeyDown_) { 2247 renderWidgetHostView_->render_widget_host_->ImeSetComposition( 2248 markedText_, underlines_, 2249 newSelRange.location, NSMaxRange(newSelRange)); 2250 } 2251} 2252 2253- (void)doCommandBySelector:(SEL)selector { 2254 // An input method calls this function to dispatch an editing command to be 2255 // handled by this view. 2256 if (selector == @selector(noop:)) 2257 return; 2258 2259 std::string command( 2260 [RWHVMEditCommandHelper::CommandNameForSelector(selector) UTF8String]); 2261 2262 // If this method is called when handling a key down event, then we need to 2263 // handle the command in the key event handler. Otherwise we can just handle 2264 // it here. 2265 if (handlingKeyDown_) { 2266 hasEditCommands_ = YES; 2267 // We ignore commands that insert characters, because this was causing 2268 // strange behavior (e.g. tab always inserted a tab rather than moving to 2269 // the next field on the page). 2270 if (!StartsWithASCII(command, "insert", false)) 2271 editCommands_.push_back(EditCommand(command, "")); 2272 } else { 2273 renderWidgetHostView_->render_widget_host_->ForwardEditCommand(command, ""); 2274 } 2275} 2276 2277- (void)insertText:(id)string { 2278 // An input method has characters to be inserted. 2279 // Same as Linux, Mac calls this method not only: 2280 // * when an input method finishs composing text, but also; 2281 // * when we type an ASCII character (without using input methods). 2282 // When we aren't using input methods, we should send the given character as 2283 // a Char event so it is dispatched to an onkeypress() event handler of 2284 // JavaScript. 2285 // On the other hand, when we are using input methods, we should send the 2286 // given characters as an input method event and prevent the characters from 2287 // being dispatched to onkeypress() event handlers. 2288 // Text inserting might be initiated by other source instead of keyboard 2289 // events, such as the Characters dialog. In this case the text should be 2290 // sent as an input method event as well. 2291 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; 2292 NSString* im_text = isAttributedString ? [string string] : string; 2293 if (handlingKeyDown_) { 2294 textToBeInserted_.append(base::SysNSStringToUTF16(im_text)); 2295 } else { 2296 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition( 2297 base::SysNSStringToUTF16(im_text)); 2298 } 2299 2300 // Inserting text will delete all marked text automatically. 2301 hasMarkedText_ = NO; 2302} 2303 2304- (void)viewDidMoveToWindow { 2305 if (canBeKeyView_) { 2306 NSWindow* newWindow = [self window]; 2307 // Pointer comparison only, since we don't know if lastWindow_ is still 2308 // valid. 2309 if (newWindow) { 2310 // If we move into a new window, refresh the frame information. We 2311 // don't need to do it if it was the same window as it used to be in, 2312 // since that case is covered by DidBecomeSelected. We only want to 2313 // do this for real browser views, not popups. 2314 if (newWindow != lastWindow_) { 2315 lastWindow_ = newWindow; 2316 renderWidgetHostView_->WindowFrameChanged(); 2317 } 2318 renderWidgetHostView_->ForceTextureReload(); 2319 } 2320 } 2321 2322 // If we switch windows (or are removed from the view hierarchy), cancel any 2323 // open mouse-downs. 2324 if (hasOpenMouseDown_) { 2325 WebMouseEvent event; 2326 event.type = WebInputEvent::MouseUp; 2327 event.button = WebMouseEvent::ButtonLeft; 2328 if (renderWidgetHostView_->render_widget_host_) 2329 renderWidgetHostView_->render_widget_host_->ForwardMouseEvent(event); 2330 2331 hasOpenMouseDown_ = NO; 2332 } 2333} 2334 2335- (void)undo:(id)sender { 2336 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2337 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2338 Undo(); 2339 } 2340} 2341 2342- (void)redo:(id)sender { 2343 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2344 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2345 Redo(); 2346 } 2347} 2348 2349- (void)cut:(id)sender { 2350 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2351 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2352 Cut(); 2353 } 2354} 2355 2356- (void)copy:(id)sender { 2357 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2358 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2359 Copy(); 2360 } 2361} 2362 2363- (void)copyToFindPboard:(id)sender { 2364 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2365 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2366 CopyToFindPboard(); 2367 } 2368} 2369 2370- (void)paste:(id)sender { 2371 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2372 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2373 Paste(); 2374 } 2375} 2376 2377- (void)pasteAsPlainText:(id)sender { 2378 if (renderWidgetHostView_->render_widget_host_->IsRenderView()) { 2379 static_cast<RenderViewHost*>(renderWidgetHostView_->render_widget_host_)-> 2380 ForwardEditCommand("PasteAndMatchStyle", ""); 2381 } 2382} 2383 2384- (void)cancelComposition { 2385 if (!hasMarkedText_) 2386 return; 2387 2388 // Cancel the ongoing composition. [NSInputManager markedTextAbandoned:] 2389 // doesn't call any NSTextInput functions, such as setMarkedText or 2390 // insertText. So, we need to send an IPC message to a renderer so it can 2391 // delete the composition node. 2392 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; 2393 [currentInputManager markedTextAbandoned:self]; 2394 2395 hasMarkedText_ = NO; 2396 // Should not call [self unmarkText] here, because it'll send unnecessary 2397 // cancel composition IPC message to the renderer. 2398} 2399 2400- (void)confirmComposition { 2401 if (!hasMarkedText_) 2402 return; 2403 2404 if (renderWidgetHostView_->render_widget_host_) 2405 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(); 2406 2407 [self cancelComposition]; 2408} 2409 2410- (void)setPluginImeActive:(BOOL)active { 2411 if (active == pluginImeActive_) 2412 return; 2413 2414 pluginImeActive_ = active; 2415 if (!active) { 2416 [[ComplexTextInputPanel sharedComplexTextInputPanel] cancelComposition]; 2417 renderWidgetHostView_->PluginImeCompositionCompleted( 2418 string16(), focusedPluginIdentifier_); 2419 } 2420} 2421 2422- (void)pluginFocusChanged:(BOOL)focused forPlugin:(int)pluginId { 2423 if (focused) 2424 focusedPluginIdentifier_ = pluginId; 2425 else if (focusedPluginIdentifier_ == pluginId) 2426 focusedPluginIdentifier_ = -1; 2427 2428 // Whenever plugin focus changes, plugin IME resets. 2429 [self setPluginImeActive:NO]; 2430} 2431 2432- (BOOL)postProcessEventForPluginIme:(NSEvent*)event { 2433 if (!pluginImeActive_) 2434 return false; 2435 2436 ComplexTextInputPanel* inputPanel = 2437 [ComplexTextInputPanel sharedComplexTextInputPanel]; 2438 NSString* composited_string = nil; 2439 BOOL handled = [inputPanel interpretKeyEvent:event 2440 string:&composited_string]; 2441 if (composited_string) { 2442 renderWidgetHostView_->PluginImeCompositionCompleted( 2443 base::SysNSStringToUTF16(composited_string), focusedPluginIdentifier_); 2444 pluginImeActive_ = NO; 2445 } 2446 return handled; 2447} 2448 2449- (void)checkForPluginImeCancellation { 2450 if (pluginImeActive_ && 2451 ![[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition]) { 2452 renderWidgetHostView_->PluginImeCompositionCompleted( 2453 string16(), focusedPluginIdentifier_); 2454 pluginImeActive_ = NO; 2455 } 2456} 2457 2458- (ViewID)viewID { 2459 return VIEW_ID_TAB_CONTAINER_FOCUS_VIEW; 2460} 2461 2462// Overriding a NSResponder method to support application services. 2463 2464- (id)validRequestorForSendType:(NSString*)sendType 2465 returnType:(NSString*)returnType { 2466 id requestor = nil; 2467 BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType]; 2468 BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType]; 2469 BOOL hasText = !renderWidgetHostView_->selected_text().empty(); 2470 BOOL takesText = 2471 renderWidgetHostView_->text_input_type_ != WebKit::WebTextInputTypeNone; 2472 2473 if (sendTypeIsString && hasText && !returnType) { 2474 requestor = self; 2475 } else if (!sendType && returnTypeIsString && takesText) { 2476 requestor = self; 2477 } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) { 2478 requestor = self; 2479 } else { 2480 requestor = [super validRequestorForSendType:sendType 2481 returnType:returnType]; 2482 } 2483 return requestor; 2484} 2485 2486@end 2487 2488// 2489// Supporting application services 2490// 2491@implementation RenderWidgetHostViewCocoa(NSServicesRequests) 2492 2493- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard 2494 types:(NSArray*)types { 2495 const std::string& str = renderWidgetHostView_->selected_text(); 2496 if (![types containsObject:NSStringPboardType] || str.empty()) return NO; 2497 2498 scoped_nsobject<NSString> text([[NSString alloc] 2499 initWithUTF8String:str.c_str()]); 2500 NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType]; 2501 [pboard declareTypes:toDeclare owner:nil]; 2502 return [pboard setString:text forType:NSStringPboardType]; 2503} 2504 2505- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard { 2506 NSString *string = [pboard stringForType:NSStringPboardType]; 2507 if (!string) return NO; 2508 2509 // If the user is currently using an IME, confirm the IME input, 2510 // and then insert the text from the service, the same as TextEdit and Safari. 2511 [self confirmComposition]; 2512 [self insertText:string]; 2513 return YES; 2514} 2515 2516@end 2517