1// Copyright 2014 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#import "components/remote_cocoa/app_shim/bridged_content_view.h" 6 7#include "base/logging.h" 8#import "base/mac/foundation_util.h" 9#import "base/mac/mac_util.h" 10#import "base/mac/scoped_nsobject.h" 11#include "base/metrics/histogram_macros.h" 12#include "base/strings/sys_string_conversions.h" 13#import "components/remote_cocoa/app_shim/drag_drop_client.h" 14#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h" 15#include "components/remote_cocoa/app_shim/native_widget_ns_window_host_helper.h" 16#include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h" 17#include "ui/base/clipboard/clipboard_constants.h" 18#import "ui/base/cocoa/appkit_utils.h" 19#include "ui/base/cocoa/cocoa_base_utils.h" 20#import "ui/base/dragdrop/cocoa_dnd_util.h" 21#include "ui/base/dragdrop/drag_drop_types.h" 22#include "ui/base/dragdrop/os_exchange_data_provider_mac.h" 23#include "ui/base/ime/input_method.h" 24#include "ui/base/ime/text_edit_commands.h" 25#include "ui/base/ime/text_input_client.h" 26#import "ui/events/cocoa/cocoa_event_utils.h" 27#include "ui/events/event_utils.h" 28#include "ui/events/keycodes/dom/dom_code.h" 29#import "ui/events/keycodes/keyboard_code_conversion_mac.h" 30#include "ui/gfx/canvas_paint_mac.h" 31#include "ui/gfx/decorated_text.h" 32#import "ui/gfx/decorated_text_mac.h" 33#include "ui/gfx/geometry/rect.h" 34#import "ui/gfx/mac/coordinate_conversion.h" 35#import "ui/gfx/path_mac.h" 36#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 37 38namespace { 39 40NSString* const kFullKeyboardAccessChangedNotification = 41 @"com.apple.KeyboardUIModeDidChange"; 42 43// Convert a |point| in |source_window|'s AppKit coordinate system (origin at 44// the bottom left of the window) to |target_window|'s content rect, with the 45// origin at the top left of the content area. 46// If |source_window| is nil, |point| will be treated as screen coordinates. 47gfx::Point MovePointToWindow(const NSPoint& point, 48 NSWindow* source_window, 49 NSWindow* target_window) { 50 NSPoint point_in_screen = 51 source_window ? ui::ConvertPointFromWindowToScreen(source_window, point) 52 : point; 53 54 NSPoint point_in_window = 55 ui::ConvertPointFromScreenToWindow(target_window, point_in_screen); 56 NSRect content_rect = 57 [target_window contentRectForFrameRect:[target_window frame]]; 58 return gfx::Point(point_in_window.x, 59 NSHeight(content_rect) - point_in_window.y); 60} 61 62// Returns true if |event| may have triggered dismissal of an IME and would 63// otherwise be ignored by a ui::TextInputClient when inserted. 64bool IsImeTriggerEvent(NSEvent* event) { 65 ui::KeyboardCode key = ui::KeyboardCodeFromNSEvent(event); 66 return key == ui::VKEY_RETURN || key == ui::VKEY_TAB || 67 key == ui::VKEY_ESCAPE; 68} 69 70ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { 71 if (action == @selector(undo:)) 72 return ui::TextEditCommand::UNDO; 73 if (action == @selector(redo:)) 74 return ui::TextEditCommand::REDO; 75 if (action == @selector(cut:)) 76 return ui::TextEditCommand::CUT; 77 if (action == @selector(copy:)) 78 return ui::TextEditCommand::COPY; 79 if (action == @selector(paste:)) 80 return ui::TextEditCommand::PASTE; 81 if (action == @selector(pasteAndMatchStyle:)) 82 return ui::TextEditCommand::PASTE; 83 if (action == @selector(selectAll:)) 84 return ui::TextEditCommand::SELECT_ALL; 85 return ui::TextEditCommand::INVALID_COMMAND; 86} 87 88#if defined(TOOLKIT_QT) 89// Copy of ui::OSExchangeDataProviderMac::SupportedPasteboardTypes() (ui/base/dragdrop/os_exchange_data_provider_mac.mm) 90NSArray* SupportedPasteboardTypes() { 91 return @[ 92 ui::kWebCustomDataPboardType, ui::ClipboardUtil::UTIForWebURLsAndTitles(), 93 NSURLPboardType, NSFilenamesPboardType, ui::kChromeDragDummyPboardType, 94 NSStringPboardType, NSHTMLPboardType, NSRTFPboardType, 95 NSFilenamesPboardType, ui::kWebCustomDataPboardType, NSPasteboardTypeString 96 ]; 97} 98#endif 99 100} // namespace 101 102@interface BridgedContentView () 103 104// Dispatch |event| to |bridge_|'s host. 105- (void)dispatchKeyEvent:(ui::KeyEvent*)event; 106 107// Returns true if active menu controller corresponds to this widget. Note that 108// this will synchronously call into the browser process. 109- (BOOL)hasActiveMenuController; 110 111// Dispatch |event| to |menu_controller| and return true if |event| is 112// swallowed. 113- (BOOL)dispatchKeyEventToMenuController:(ui::KeyEvent*)event; 114 115// Passes |event| to the InputMethod for dispatch. 116- (void)handleKeyEvent:(ui::KeyEvent*)event; 117 118// Allows accelerators to be handled at different points in AppKit key event 119// dispatch. Checks for an unhandled event passed in to -keyDown: and passes it 120// to the Widget for processing. Returns YES if the Widget handles it. 121- (BOOL)handleUnhandledKeyDownAsKeyEvent; 122 123// Handles an NSResponder Action Message by mapping it to a corresponding text 124// editing command from ui_strings.grd and, when not being sent to a 125// TextInputClient, the keyCode that toolkit-views expects internally. 126// For example, moveToLeftEndOfLine: would pass ui::VKEY_HOME in non-RTL locales 127// even though the Home key on Mac defaults to moveToBeginningOfDocument:. 128// This approach also allows action messages a user 129// may have remapped in ~/Library/KeyBindings/DefaultKeyBinding.dict to be 130// catered for. 131// Note: default key bindings in Mac can be read from StandardKeyBinding.dict 132// which lives in /System/Library/Frameworks/AppKit.framework/Resources. Do 133// `plutil -convert xml1 -o StandardKeyBinding.xml StandardKeyBinding.dict` to 134// get something readable. 135- (void)handleAction:(ui::TextEditCommand)command 136 keyCode:(ui::KeyboardCode)keyCode 137 domCode:(ui::DomCode)domCode 138 eventFlags:(int)eventFlags; 139 140// ui::EventLocationFromNative() assumes the event hit the contentView. 141// Adjust |event| if that's not the case (e.g. for reparented views). 142- (void)adjustUiEventLocation:(ui::LocatedEvent*)event 143 fromNativeEvent:(NSEvent*)nativeEvent; 144 145// Notification handler invoked when the Full Keyboard Access mode is changed. 146- (void)onFullKeyboardAccessModeChanged:(NSNotification*)notification; 147 148// Helper method which forwards |text| to the active menu or |textInputClient_|. 149- (void)insertTextInternal:(id)text; 150 151// Returns the native Widget's drag drop client. Possibly null. 152- (remote_cocoa::DragDropClient*)dragDropClient NS_RETURNS_INNER_POINTER; 153 154// Returns true if there exists a ui::TextInputClient for the currently focused 155// views::View. 156- (BOOL)hasTextInputClient; 157 158// Returns true if there exists a ui::TextInputClient for the currently focused 159// views::View and that client is right-to-left. 160- (BOOL)isTextRTL; 161 162// Menu action handlers. 163- (void)undo:(id)sender; 164- (void)redo:(id)sender; 165- (void)cut:(id)sender; 166- (void)copy:(id)sender; 167- (void)paste:(id)sender; 168- (void)pasteAndMatchStyle:(id)sender; 169- (void)selectAll:(id)sender; 170 171@end 172 173@implementation BridgedContentView 174 175@synthesize bridge = _bridge; 176@synthesize drawMenuBackgroundForBlur = _drawMenuBackgroundForBlur; 177@synthesize keyDownEventForTesting = _keyDownEvent; 178 179- (instancetype)initWithBridge:(remote_cocoa::NativeWidgetNSWindowBridge*)bridge 180 bounds:(gfx::Rect)bounds { 181 // To keep things simple, assume the origin is (0, 0) until there exists a use 182 // case for something other than that. 183 DCHECK(bounds.origin().IsOrigin()); 184 NSRect initialFrame = NSMakeRect(0, 0, bounds.width(), bounds.height()); 185 if ((self = [super initWithFrame:initialFrame])) { 186 _bridge = bridge; 187 188 // Apple's documentation says that NSTrackingActiveAlways is incompatible 189 // with NSTrackingCursorUpdate, so use NSTrackingActiveInActiveApp. 190 _cursorTrackingArea.reset([[CrTrackingArea alloc] 191 initWithRect:NSZeroRect 192 options:NSTrackingMouseMoved | NSTrackingCursorUpdate | 193 NSTrackingActiveInActiveApp | NSTrackingInVisibleRect | 194 NSTrackingMouseEnteredAndExited 195 owner:self 196 userInfo:nil]); 197 [self addTrackingArea:_cursorTrackingArea.get()]; 198 199 // Get notified whenever Full Keyboard Access mode is changed. 200 [[NSDistributedNotificationCenter defaultCenter] 201 addObserver:self 202 selector:@selector(onFullKeyboardAccessModeChanged:) 203 name:kFullKeyboardAccessChangedNotification 204 object:nil]; 205 206 // Initialize the focus manager with the correct keyboard accessibility 207 // setting. 208 [self updateFullKeyboardAccess]; 209#if !defined(TOOLKIT_QT) 210 [self registerForDraggedTypes:ui::OSExchangeDataProviderMac:: 211 SupportedPasteboardTypes()]; 212#else 213 [self registerForDraggedTypes:SupportedPasteboardTypes()]; 214#endif 215 } 216 return self; 217} 218 219- (ui::TextInputClient*)textInputClient { 220 return _bridge ? _bridge->host_helper()->GetTextInputClient() : nullptr; 221} 222 223- (BOOL)hasTextInputClient { 224 bool hasTextInputClient = NO; 225 if (_bridge) 226 _bridge->text_input_host()->HasClient(&hasTextInputClient); 227 return hasTextInputClient; 228} 229 230- (BOOL)isTextRTL { 231 bool isRTL = NO; 232 if (_bridge) 233 _bridge->text_input_host()->IsRTL(&isRTL); 234 return isRTL; 235} 236 237- (void)dealloc { 238 // By the time |self| is dealloc'd, it should never be in an NSWindow, and it 239 // should never be the current input context. 240 DCHECK_EQ(nil, [self window]); 241 [super dealloc]; 242} 243 244- (void)clearView { 245 _bridge = nullptr; 246 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; 247 [_cursorTrackingArea.get() clearOwner]; 248 [self removeTrackingArea:_cursorTrackingArea.get()]; 249} 250 251- (bool)needsUpdateWindows { 252 // If |self| was being used for the input context, and would now report a 253 // different input context, manually invoke [NSApp updateWindows]. This is 254 // necessary because AppKit holds on to a raw pointer to a NSTextInputContext 255 // (which may have been the one returned by [self inputContext]) that is only 256 // updated by -updateWindows. And although AppKit invokes that on each 257 // iteration through most runloop modes, it does not call it when running 258 // NSEventTrackingRunLoopMode, and not _within_ a run loop iteration, where 259 // the inputContext may change before further event processing. 260 NSTextInputContext* current = [NSTextInputContext currentInputContext]; 261 if (!current) 262 return false; 263 264 NSTextInputContext* newContext = [self inputContext]; 265 // If the newContext is non-nil, then it can only be [super inputContext]. So 266 // the input context is either not changing, or it was not from |self|. In 267 // both cases, there's no need to call -updateWindows. 268 if (newContext) { 269 DCHECK_EQ(newContext, [super inputContext]); 270 return false; 271 } 272 273 return current == [super inputContext]; 274} 275 276// If |point| is classified as a draggable background (HTCAPTION), return nil so 277// that it can lead to a window drag or double-click in the title bar. Dragging 278// could be optimized by telling the window server which regions should be 279// instantly draggable without asking (tracked at https://crbug.com/830962). 280- (NSView*)hitTest:(NSPoint)point { 281 gfx::Point flippedPoint(point.x, NSHeight(self.superview.bounds) - point.y); 282 bool isDraggableBackground = false; 283 _bridge->host()->GetIsDraggableBackgroundAt(flippedPoint, 284 &isDraggableBackground); 285 if (isDraggableBackground) 286 return nil; 287 return [super hitTest:point]; 288} 289 290- (void)processCapturedMouseEvent:(NSEvent*)theEvent { 291 if (!_bridge) 292 return; 293 294 NSWindow* source = [theEvent window]; 295 NSWindow* target = [self window]; 296 DCHECK(target); 297 298 BOOL isScrollEvent = [theEvent type] == NSScrollWheel; 299 300 // If it's the view's window, process normally. 301 if ([target isEqual:source]) { 302 if (isScrollEvent) { 303 [self scrollWheel:theEvent]; 304 } else { 305 [self mouseEvent:theEvent]; 306 if ([theEvent type] == NSLeftMouseUp) 307 [self handleLeftMouseUp:theEvent]; 308 } 309 return; 310 } 311 312 gfx::Point event_location = 313 MovePointToWindow([theEvent locationInWindow], source, target); 314 [self updateTooltipIfRequiredAt:event_location]; 315 316 if (isScrollEvent) { 317 auto event = std::make_unique<ui::ScrollEvent>(theEvent); 318 event->set_location(event_location); 319 _bridge->host()->OnScrollEvent(std::move(event)); 320 } else { 321 auto event = std::make_unique<ui::MouseEvent>(theEvent); 322 event->set_location(event_location); 323 _bridge->host()->OnMouseEvent(std::move(event)); 324 } 325} 326 327- (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent { 328 DCHECK(_bridge); 329 base::string16 newTooltipText; 330 331 _bridge->host()->GetTooltipTextAt(locationInContent, &newTooltipText); 332 if (newTooltipText != _lastTooltipText) { 333 std::swap(newTooltipText, _lastTooltipText); 334 [self setToolTipAtMousePoint:base::SysUTF16ToNSString(_lastTooltipText)]; 335 } 336} 337 338- (void)updateFullKeyboardAccess { 339 if (!_bridge) 340 return; 341 _bridge->host()->SetKeyboardAccessible([NSApp isFullKeyboardAccessEnabled]); 342} 343 344// BridgedContentView private implementation. 345 346- (void)dispatchKeyEvent:(ui::KeyEvent*)event { 347 if (_bridge) 348 _bridge->host_helper()->DispatchKeyEvent(event); 349} 350 351- (BOOL)hasActiveMenuController { 352 bool hasMenuController = false; 353 if (_bridge) 354 _bridge->host()->GetHasMenuController(&hasMenuController); 355 return hasMenuController; 356} 357 358- (BOOL)dispatchKeyEventToMenuController:(ui::KeyEvent*)event { 359 if (_bridge) 360 return _bridge->host_helper()->DispatchKeyEventToMenuController(event); 361 return false; 362} 363 364- (void)handleKeyEvent:(ui::KeyEvent*)event { 365 DCHECK(event); 366 if ([self dispatchKeyEventToMenuController:event]) 367 return; 368 369 [self dispatchKeyEvent:event]; 370} 371 372- (BOOL)handleUnhandledKeyDownAsKeyEvent { 373 if (!_hasUnhandledKeyDownEvent) 374 return NO; 375 376 ui::KeyEvent event(_keyDownEvent); 377 [self handleKeyEvent:&event]; 378 _hasUnhandledKeyDownEvent = NO; 379 return event.handled(); 380} 381 382- (void)handleAction:(ui::TextEditCommand)command 383 keyCode:(ui::KeyboardCode)keyCode 384 domCode:(ui::DomCode)domCode 385 eventFlags:(int)eventFlags { 386 if (!_bridge) 387 return; 388 389 // Always propagate the shift modifier if present. Shift doesn't always alter 390 // the command selector, but should always be passed along. Control and Alt 391 // have different meanings on Mac, so they do not propagate automatically. 392 if ([_keyDownEvent modifierFlags] & NSShiftKeyMask) 393 eventFlags |= ui::EF_SHIFT_DOWN; 394 395 // Generate a synthetic event with the keycode toolkit-views expects. 396 ui::KeyEvent event(ui::ET_KEY_PRESSED, keyCode, domCode, eventFlags); 397 398 if ([self dispatchKeyEventToMenuController:&event]) 399 return; 400 401 // If there's an active TextInputClient, schedule the editing command to be 402 // performed. 403 // TODO(https://crbug.com/901490): Add mojo support for ui::TextEditCommand. 404 if ([self textInputClient] && 405 [self textInputClient] -> IsTextEditCommandEnabled(command)) { 406 [self textInputClient] -> SetTextEditCommandForNextKeyEvent(command); 407 } 408 409 [self dispatchKeyEvent:&event]; 410} 411 412- (void)adjustUiEventLocation:(ui::LocatedEvent*)event 413 fromNativeEvent:(NSEvent*)nativeEvent { 414 if ([nativeEvent window] && [[self window] contentView] != self) { 415 NSPoint p = [self convertPoint:[nativeEvent locationInWindow] fromView:nil]; 416 event->set_location(gfx::Point(p.x, NSHeight([self frame]) - p.y)); 417 } 418} 419 420- (void)onFullKeyboardAccessModeChanged:(NSNotification*)notification { 421 DCHECK([[notification name] 422 isEqualToString:kFullKeyboardAccessChangedNotification]); 423 [self updateFullKeyboardAccess]; 424} 425 426- (void)insertTextInternal:(id)text { 427 if (!_bridge) 428 return; 429 430 if ([text isKindOfClass:[NSAttributedString class]]) 431 text = [text string]; 432 433 bool isCharacterEvent = _keyDownEvent && [text length] == 1; 434 // Pass "character" events to the View hierarchy. Cases this handles (non- 435 // exhaustive)- 436 // - Space key press on controls. Unlike Tab and newline which have 437 // corresponding action messages, an insertText: message is generated for 438 // the Space key (insertText:replacementRange: when there's an active 439 // input context). 440 // - Menu mnemonic selection. 441 // Note we create a custom character ui::KeyEvent (and not use the 442 // ui::KeyEvent(NSEvent*) constructor) since we can't just rely on the event 443 // key code to get the actual characters from the ui::KeyEvent. This for 444 // example is necessary for menu mnemonic selection of non-latin text. 445 446 // Don't generate a key event when there is marked composition text. These key 447 // down events should be consumed by the IME and not reach the Views layer. 448 // For example, on pressing Return to commit composition text, if we passed a 449 // synthetic key event to the View hierarchy, it will have the effect of 450 // performing the default action on the current dialog. We do not want this 451 // when there is marked text (Return should only confirm the IME). 452 453 // However, IME for phonetic languages such as Korean do not always _mark_ 454 // text when a composition is active. For these, correct behaviour is to 455 // handle the final -keyDown: that caused the composition to be committed, but 456 // only _after_ the sequence of insertText: messages coming from IME have been 457 // sent to the TextInputClient. Detect this by comparing to -[NSEvent 458 // characters]. Note we do not use -charactersIgnoringModifiers: so that, 459 // e.g., ß (Alt+s) will match mnemonics with ß rather than s. 460 bool isFinalInsertForKeyEvent = 461 isCharacterEvent && [text isEqualToString:[_keyDownEvent characters]]; 462 463 // Also note that a single, non-IME key down event can also cause multiple 464 // insertText:replacementRange: action messages being generated from within 465 // -keyDown:'s call to -interpretKeyEvents:. One example, on pressing Alt+e, 466 // the accent (´) character is composed via setMarkedText:. Now on pressing 467 // the character 'r', two insertText:replacementRange: action messages are 468 // generated with the text value of accent (´) and 'r' respectively. The key 469 // down event will have characters field of length 2. The first of these 470 // insertText messages won't generate a KeyEvent since there'll be active 471 // marked text. However, a KeyEvent will be generated corresponding to 'r'. 472 473 // Currently there seems to be no use case to pass non-character events routed 474 // from insertText: handlers to the View hierarchy. 475 if (isFinalInsertForKeyEvent && ![self hasMarkedText]) { 476 ui::KeyEvent charEvent([text characterAtIndex:0], 477 ui::KeyboardCodeFromNSEvent(_keyDownEvent), 478 ui::DomCodeFromNSEvent(_keyDownEvent), ui::EF_NONE); 479 [self handleKeyEvent:&charEvent]; 480 _hasUnhandledKeyDownEvent = NO; 481 if (charEvent.handled()) 482 return; 483 } 484 485 // Forward the |text| to |textInputClient_| if no menu is active. 486 if ([self hasTextInputClient] && ![self hasActiveMenuController]) { 487 // Note we don't check isFinalInsertForKeyEvent, nor use |keyDownEvent_| 488 // to generate the synthetic ui::KeyEvent since: For composed text, 489 // [keyDownEvent_ characters] might not be the same as |text|. This is 490 // because |keyDownEvent_| will correspond to the event that caused the 491 // composition text to be confirmed, say, Return key press. 492 _bridge->text_input_host()->InsertText(base::SysNSStringToUTF16(text), 493 isCharacterEvent); 494 // Suppress accelerators that may be bound to this key, since it inserted 495 // text instead. But note that IME may follow with -insertNewLine:, which 496 // will resurrect the keyEvent for accelerator handling. 497 _hasUnhandledKeyDownEvent = NO; 498 } 499} 500 501- (remote_cocoa::DragDropClient*)dragDropClient { 502 return _bridge ? _bridge->drag_drop_client() : nullptr; 503} 504 505- (void)undo:(id)sender { 506 [self handleAction:ui::TextEditCommand::UNDO 507 keyCode:ui::VKEY_Z 508 domCode:ui::DomCode::US_Z 509 eventFlags:ui::EF_CONTROL_DOWN]; 510} 511 512- (void)redo:(id)sender { 513 [self handleAction:ui::TextEditCommand::REDO 514 keyCode:ui::VKEY_Z 515 domCode:ui::DomCode::US_Z 516 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 517} 518 519- (void)cut:(id)sender { 520 [self handleAction:ui::TextEditCommand::CUT 521 keyCode:ui::VKEY_X 522 domCode:ui::DomCode::US_X 523 eventFlags:ui::EF_CONTROL_DOWN]; 524} 525 526- (void)copy:(id)sender { 527 [self handleAction:ui::TextEditCommand::COPY 528 keyCode:ui::VKEY_C 529 domCode:ui::DomCode::US_C 530 eventFlags:ui::EF_CONTROL_DOWN]; 531} 532 533- (void)paste:(id)sender { 534 [self handleAction:ui::TextEditCommand::PASTE 535 keyCode:ui::VKEY_V 536 domCode:ui::DomCode::US_V 537 eventFlags:ui::EF_CONTROL_DOWN]; 538} 539 540- (void)pasteAndMatchStyle:(id)sender { 541 [self handleAction:ui::TextEditCommand::PASTE 542 keyCode:ui::VKEY_V 543 domCode:ui::DomCode::US_V 544 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 545} 546 547- (void)selectAll:(id)sender { 548 [self handleAction:ui::TextEditCommand::SELECT_ALL 549 keyCode:ui::VKEY_A 550 domCode:ui::DomCode::US_A 551 eventFlags:ui::EF_CONTROL_DOWN]; 552} 553 554// BaseView implementation. 555 556// Don't use tracking areas from BaseView. BridgedContentView's tracks 557// NSTrackingCursorUpdate and Apple's documentation suggests it's incompatible. 558- (void)enableTracking { 559} 560 561// Translates the location of |theEvent| to toolkit-views coordinates and passes 562// the event to NativeWidgetMac for handling. 563- (void)mouseEvent:(NSEvent*)theEvent { 564 if (!_bridge) 565 return; 566 567 DCHECK([theEvent type] != NSScrollWheel); 568 auto event = std::make_unique<ui::MouseEvent>(theEvent); 569 [self adjustUiEventLocation:event.get() fromNativeEvent:theEvent]; 570 571 // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). 572 // Mac hooks in here. 573 [self updateTooltipIfRequiredAt:event->location()]; 574 _bridge->host()->OnMouseEvent(std::move(event)); 575} 576 577- (void)forceTouchEvent:(NSEvent*)theEvent { 578 if (ui::ForceClickInvokesQuickLook()) 579 [self quickLookWithEvent:theEvent]; 580} 581 582// NSView implementation. 583 584// Refuse first responder so that clicking a blank area of the view don't take 585// first responder away from another view. This does not prevent the view 586// becoming first responder via -[NSWindow makeFirstResponder:] when invoked 587// during Init or by FocusManager. 588// 589// The condition is to work around an AppKit quirk. When a window is being 590// ordered front, if its current first responder returns |NO| for this method, 591// it resigns it if it can find another responder in the key loop that replies 592// |YES|. 593- (BOOL)acceptsFirstResponder { 594 return self.window.firstResponder == self; 595} 596 597// This undocumented method determines which parts of the view prevent 598// server-side window dragging (i.e. aren't draggable without asking the app 599// first). Since Views decides click-by-click whether to handle an event, the 600// whole view is off limits but, since the view's content is rendered out of 601// process and the view is locally transparent, AppKit won't guess that. 602- (NSRect)_opaqueRectForWindowMoveWhenInTitlebar { 603 return self.bounds; 604} 605 606- (BOOL)becomeFirstResponder { 607 BOOL result = [super becomeFirstResponder]; 608 if (result && _bridge) 609 _bridge->host()->OnIsFirstResponderChanged(true); 610 return result; 611} 612 613- (BOOL)resignFirstResponder { 614 BOOL result = [super resignFirstResponder]; 615 if (result && _bridge) 616 _bridge->host()->OnIsFirstResponderChanged(false); 617 return result; 618} 619 620- (void)viewDidMoveToWindow { 621 // When this view is added to a window, AppKit calls setFrameSize before it is 622 // added to the window, so the behavior in setFrameSize is not triggered. 623 NSWindow* window = [self window]; 624 if (window) 625 [self setFrameSize:NSZeroSize]; 626} 627 628- (void)setFrameSize:(NSSize)newSize { 629 // The size passed in here does not always use 630 // -[NSWindow contentRectForFrameRect]. The following ensures that the 631 // contentView for a frameless window can extend over the titlebar of the new 632 // window containing it, since AppKit requires a titlebar to give frameless 633 // windows correct shadows and rounded corners. 634 NSWindow* window = [self window]; 635 if (window && [window contentView] == self) { 636 newSize = [window contentRectForFrameRect:[window frame]].size; 637 // Ensure that the window geometry be updated on the host side before the 638 // view size is updated. 639 // TODO(ccameron): Consider updating the view size and window size and 640 // position together in UpdateWindowGeometry. 641 // https://crbug.com/875776, https://crbug.com/875731 642 if (_bridge) 643 _bridge->UpdateWindowGeometry(); 644 } 645 646 [super setFrameSize:newSize]; 647 648 if (_bridge) 649 _bridge->host()->OnViewSizeChanged( 650 gfx::Size(newSize.width, newSize.height)); 651} 652 653- (BOOL)isOpaque { 654 return _bridge ? !_bridge->is_translucent_window() : NO; 655} 656 657// To maximize consistency with the Cocoa browser (mac_views_browser=0), accept 658// mouse clicks immediately so that clicking on Chrome from an inactive window 659// will allow the event to be processed, rather than merely activate the window. 660- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent { 661 return YES; 662} 663 664// NSDraggingDestination protocol overrides. 665 666- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { 667 return [self draggingUpdated:sender]; 668} 669 670- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { 671 remote_cocoa::DragDropClient* client = [self dragDropClient]; 672 const auto drag_operation = 673 client ? client->DragUpdate(sender) : ui::DragDropTypes::DRAG_NONE; 674 UMA_HISTOGRAM_BOOLEAN("Event.DragDrop.AcceptDragUpdate", 675 drag_operation != ui::DragDropTypes::DRAG_NONE); 676 return drag_operation; 677} 678 679- (void)draggingExited:(id<NSDraggingInfo>)sender { 680 remote_cocoa::DragDropClient* client = [self dragDropClient]; 681 if (client) 682 client->DragExit(); 683} 684 685- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { 686 remote_cocoa::DragDropClient* client = [self dragDropClient]; 687 return client && client->Drop(sender) != NSDragOperationNone; 688} 689 690- (NSTextInputContext*)inputContext { 691 if (!_bridge) 692 return nil; 693 bool hasTextInputContext = false; 694 _bridge->text_input_host()->HasInputContext(&hasTextInputContext); 695 return hasTextInputContext ? [super inputContext] : nil; 696} 697 698// NSResponder implementation. 699 700- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event { 701 // This is a SPI that AppKit apparently calls after |performKeyEquivalent:| 702 // returned NO. If this function returns |YES|, Cocoa sends the event to 703 // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent 704 // to us instead of doing key view loop control, ctrl-left/right get handled 705 // correctly, etc. 706 // (However, there are still some keys that Cocoa swallows, e.g. the key 707 // equivalent that Cocoa uses for toggling the input language. In this case, 708 // that's actually a good thing, though -- see http://crbug.com/26115 .) 709 return YES; 710} 711 712- (void)keyDown:(NSEvent*)theEvent { 713 BOOL hadMarkedTextAtKeyDown = [self hasMarkedText]; 714 715 // Convert the event into an action message, according to OSX key mappings. 716 _keyDownEvent = theEvent; 717 _hasUnhandledKeyDownEvent = YES; 718 _wantsKeyHandledForInsert = NO; 719 [self interpretKeyEvents:@[ theEvent ]]; 720 721 // When there is marked text, -[NSView interpretKeyEvents:] may handle the 722 // event by dismissing the IME window in a way that neither unmarks text, nor 723 // updates any composition. That is, no signal is given either to the 724 // NSTextInputClient or the NSTextInputContext that the IME changed state. 725 // However, we must ensure this key down is not processed as an accelerator. 726 // TODO(tapted): Investigate removing the IsImeTriggerEvent() check - it's 727 // probably not required, but helps tests that expect some events to always 728 // get processed (i.e. TextfieldTest.TextInputClientTest). 729 if (hadMarkedTextAtKeyDown && IsImeTriggerEvent(theEvent)) 730 _hasUnhandledKeyDownEvent = NO; 731 732 // Even with marked text, some IMEs may follow with -insertNewLine:; 733 // simultaneously confirming the composition. In this case, always generate 734 // the corresponding ui::KeyEvent. Note this is done even if there was no 735 // marked text, so it is orthogonal to the case above. 736 if (_wantsKeyHandledForInsert) 737 _hasUnhandledKeyDownEvent = YES; 738 739 // If |hasUnhandledKeyDownEvent_| wasn't set to NO during 740 // -interpretKeyEvents:, it wasn't handled. Give Widget accelerators a chance 741 // to handle it. 742 [self handleUnhandledKeyDownAsKeyEvent]; 743 DCHECK(!_hasUnhandledKeyDownEvent); 744 _keyDownEvent = nil; 745} 746 747- (void)keyUp:(NSEvent*)theEvent { 748 ui::KeyEvent event(theEvent); 749 [self handleKeyEvent:&event]; 750} 751 752- (void)flagsChanged:(NSEvent*)theEvent { 753 if (theEvent.keyCode == 0) { 754 // An event like this gets sent when sending some key commands via 755 // AppleScript. Since 0 is VKEY_A, we end up interpreting this as Cmd+A 756 // which is incorrect. The correct event for command up/down (keyCode = 55) 757 // is also sent, so we should drop this one. See https://crbug.com/889618 758 return; 759 } 760 ui::KeyEvent event(theEvent); 761 [self handleKeyEvent:&event]; 762} 763 764- (void)scrollWheel:(NSEvent*)theEvent { 765 if (!_bridge) 766 return; 767 768 auto event = std::make_unique<ui::ScrollEvent>(theEvent); 769 [self adjustUiEventLocation:event.get() fromNativeEvent:theEvent]; 770 771 // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). 772 // Mac hooks in here. 773 [self updateTooltipIfRequiredAt:event->location()]; 774 _bridge->host()->OnScrollEvent(std::move(event)); 775} 776 777// Called when we get a three-finger swipe, and they're enabled in System 778// Preferences. 779- (void)swipeWithEvent:(NSEvent*)event { 780 if (!_bridge) 781 return; 782 783 // themblsha: In my testing all three-finger swipes send only a single event 784 // with a value of +/-1 on either axis. Diagonal swipes are not handled by 785 // AppKit. 786 787 // We need to invert deltas in order to match GestureEventDetails's 788 // directions. 789 ui::GestureEventDetails swipeDetails(ui::ET_GESTURE_SWIPE, -[event deltaX], 790 -[event deltaY]); 791 swipeDetails.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD); 792 swipeDetails.set_touch_points(3); 793 794 gfx::PointF location = ui::EventLocationFromNative(event); 795 // Note this uses the default unique_touch_event_id of 0 (Swipe events do not 796 // support -[NSEvent eventNumber]). This doesn't seem like a real omission 797 // because the three-finger swipes are instant and can't be tracked anyway. 798 auto gestureEvent = std::make_unique<ui::GestureEvent>( 799 location.x(), location.y(), ui::EventFlagsFromNative(event), 800 ui::EventTimeFromNative(event), swipeDetails); 801 _bridge->host()->OnGestureEvent(std::move(gestureEvent)); 802} 803 804- (void)quickLookWithEvent:(NSEvent*)theEvent { 805 if (!_bridge) 806 return; 807 808 const gfx::Point locationInContent = 809 gfx::ToFlooredPoint(ui::EventLocationFromNative(theEvent)); 810 811 bool foundWord = false; 812 gfx::DecoratedText decoratedWord; 813 gfx::Point baselinePoint; 814 _bridge->host_helper()->GetWordAt(locationInContent, &foundWord, 815 &decoratedWord, &baselinePoint); 816 if (!foundWord) 817 return; 818 819 NSPoint baselinePointAppKit = NSMakePoint( 820 baselinePoint.x(), NSHeight([self frame]) - baselinePoint.y()); 821 [self showDefinitionForAttributedString: 822 gfx::GetAttributedStringFromDecoratedText(decoratedWord) 823 atPoint:baselinePointAppKit]; 824} 825 826//////////////////////////////////////////////////////////////////////////////// 827// NSResponder Action Messages. Keep sorted according NSResponder.h (from the 828// 10.9 SDK). The list should eventually be complete. Anything not defined will 829// beep when interpretKeyEvents: would otherwise call it. 830// TODO(tapted): Make this list complete, except for insert* methods which are 831// dispatched as regular key events in doCommandBySelector:. 832 833// views::Textfields are single-line only, map Paragraph and Document commands 834// to Line. Also, Up/Down commands correspond to beginning/end of line. 835 836// The insertText action message forwards to the TextInputClient unless a menu 837// is active. Note that NSResponder's interpretKeyEvents: implementation doesn't 838// direct insertText: through doCommandBySelector:, so this is still needed to 839// handle the case when inputContext: is nil. When inputContext: returns non-nil 840// text goes directly to insertText:replacementRange:. 841- (void)insertText:(id)text { 842 DCHECK_EQ(nil, [self inputContext]); 843 [self insertTextInternal:text]; 844} 845 846// Selection movement and scrolling. 847 848- (void)moveForward:(id)sender { 849 [self handleAction:ui::TextEditCommand::MOVE_FORWARD 850 keyCode:ui::VKEY_UNKNOWN 851 domCode:ui::DomCode::NONE 852 eventFlags:0]; 853} 854 855- (void)moveRight:(id)sender { 856 [self handleAction:ui::TextEditCommand::MOVE_RIGHT 857 keyCode:ui::VKEY_RIGHT 858 domCode:ui::DomCode::ARROW_RIGHT 859 eventFlags:0]; 860} 861 862- (void)moveBackward:(id)sender { 863 [self handleAction:ui::TextEditCommand::MOVE_BACKWARD 864 keyCode:ui::VKEY_UNKNOWN 865 domCode:ui::DomCode::NONE 866 eventFlags:0]; 867} 868 869- (void)moveLeft:(id)sender { 870 [self handleAction:ui::TextEditCommand::MOVE_LEFT 871 keyCode:ui::VKEY_LEFT 872 domCode:ui::DomCode::ARROW_LEFT 873 eventFlags:0]; 874} 875 876- (void)moveUp:(id)sender { 877 [self handleAction:ui::TextEditCommand::MOVE_UP 878 keyCode:ui::VKEY_UP 879 domCode:ui::DomCode::ARROW_UP 880 eventFlags:0]; 881} 882 883- (void)moveDown:(id)sender { 884 [self handleAction:ui::TextEditCommand::MOVE_DOWN 885 keyCode:ui::VKEY_DOWN 886 domCode:ui::DomCode::ARROW_DOWN 887 eventFlags:0]; 888} 889 890- (void)moveWordForward:(id)sender { 891 [self handleAction:ui::TextEditCommand::MOVE_WORD_FORWARD 892 keyCode:ui::VKEY_UNKNOWN 893 domCode:ui::DomCode::NONE 894 eventFlags:0]; 895} 896 897- (void)moveWordBackward:(id)sender { 898 [self handleAction:ui::TextEditCommand::MOVE_WORD_BACKWARD 899 keyCode:ui::VKEY_UNKNOWN 900 domCode:ui::DomCode::NONE 901 eventFlags:0]; 902} 903 904- (void)moveToBeginningOfLine:(id)sender { 905 [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE 906 keyCode:ui::VKEY_HOME 907 domCode:ui::DomCode::HOME 908 eventFlags:0]; 909} 910 911- (void)moveToEndOfLine:(id)sender { 912 [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_LINE 913 keyCode:ui::VKEY_END 914 domCode:ui::DomCode::END 915 eventFlags:0]; 916} 917 918- (void)moveToBeginningOfParagraph:(id)sender { 919 [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_PARAGRAPH 920 keyCode:ui::VKEY_UNKNOWN 921 domCode:ui::DomCode::NONE 922 eventFlags:0]; 923} 924 925- (void)moveToEndOfParagraph:(id)sender { 926 [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH 927 keyCode:ui::VKEY_UNKNOWN 928 domCode:ui::DomCode::NONE 929 eventFlags:0]; 930} 931 932- (void)moveToEndOfDocument:(id)sender { 933 [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT 934 keyCode:ui::VKEY_END 935 domCode:ui::DomCode::END 936 eventFlags:ui::EF_CONTROL_DOWN]; 937} 938 939- (void)moveToBeginningOfDocument:(id)sender { 940 [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT 941 keyCode:ui::VKEY_HOME 942 domCode:ui::DomCode::HOME 943 eventFlags:ui::EF_CONTROL_DOWN]; 944} 945 946- (void)pageDown:(id)sender { 947 // The pageDown: action message is bound to the key combination 948 // [Option+PageDown]. 949 [self handleAction:ui::TextEditCommand::MOVE_PAGE_DOWN 950 keyCode:ui::VKEY_NEXT 951 domCode:ui::DomCode::PAGE_DOWN 952 eventFlags:ui::EF_ALT_DOWN]; 953} 954 955- (void)pageUp:(id)sender { 956 // The pageUp: action message is bound to the key combination [Option+PageUp]. 957 [self handleAction:ui::TextEditCommand::MOVE_PAGE_UP 958 keyCode:ui::VKEY_PRIOR 959 domCode:ui::DomCode::PAGE_UP 960 eventFlags:ui::EF_ALT_DOWN]; 961} 962 963- (void)moveBackwardAndModifySelection:(id)sender { 964 [self handleAction:ui::TextEditCommand::MOVE_BACKWARD_AND_MODIFY_SELECTION 965 keyCode:ui::VKEY_UNKNOWN 966 domCode:ui::DomCode::NONE 967 eventFlags:0]; 968} 969 970- (void)moveForwardAndModifySelection:(id)sender { 971 [self handleAction:ui::TextEditCommand::MOVE_FORWARD_AND_MODIFY_SELECTION 972 keyCode:ui::VKEY_UNKNOWN 973 domCode:ui::DomCode::NONE 974 eventFlags:0]; 975} 976 977- (void)moveWordForwardAndModifySelection:(id)sender { 978 [self handleAction:ui::TextEditCommand::MOVE_WORD_FORWARD_AND_MODIFY_SELECTION 979 keyCode:ui::VKEY_UNKNOWN 980 domCode:ui::DomCode::NONE 981 eventFlags:0]; 982} 983 984- (void)moveWordBackwardAndModifySelection:(id)sender { 985 [self 986 handleAction:ui::TextEditCommand::MOVE_WORD_BACKWARD_AND_MODIFY_SELECTION 987 keyCode:ui::VKEY_UNKNOWN 988 domCode:ui::DomCode::NONE 989 eventFlags:0]; 990} 991 992- (void)moveUpAndModifySelection:(id)sender { 993 [self handleAction:ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION 994 keyCode:ui::VKEY_UP 995 domCode:ui::DomCode::ARROW_UP 996 eventFlags:ui::EF_SHIFT_DOWN]; 997} 998 999- (void)moveDownAndModifySelection:(id)sender { 1000 [self handleAction:ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION 1001 keyCode:ui::VKEY_DOWN 1002 domCode:ui::DomCode::ARROW_DOWN 1003 eventFlags:ui::EF_SHIFT_DOWN]; 1004} 1005 1006- (void)moveToBeginningOfLineAndModifySelection:(id)sender { 1007 [self handleAction:ui::TextEditCommand:: 1008 MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION 1009 keyCode:ui::VKEY_HOME 1010 domCode:ui::DomCode::HOME 1011 eventFlags:ui::EF_SHIFT_DOWN]; 1012} 1013 1014- (void)moveToEndOfLineAndModifySelection:(id)sender { 1015 [self 1016 handleAction:ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION 1017 keyCode:ui::VKEY_END 1018 domCode:ui::DomCode::END 1019 eventFlags:ui::EF_SHIFT_DOWN]; 1020} 1021 1022- (void)moveToBeginningOfParagraphAndModifySelection:(id)sender { 1023 [self handleAction:ui::TextEditCommand:: 1024 MOVE_TO_BEGINNING_OF_PARAGRAPH_AND_MODIFY_SELECTION 1025 keyCode:ui::VKEY_UNKNOWN 1026 domCode:ui::DomCode::NONE 1027 eventFlags:0]; 1028} 1029 1030- (void)moveToEndOfParagraphAndModifySelection:(id)sender { 1031 [self handleAction:ui::TextEditCommand:: 1032 MOVE_TO_END_OF_PARAGRAPH_AND_MODIFY_SELECTION 1033 keyCode:ui::VKEY_UNKNOWN 1034 domCode:ui::DomCode::NONE 1035 eventFlags:0]; 1036} 1037 1038- (void)moveToEndOfDocumentAndModifySelection:(id)sender { 1039 [self handleAction:ui::TextEditCommand:: 1040 MOVE_TO_END_OF_DOCUMENT_AND_MODIFY_SELECTION 1041 keyCode:ui::VKEY_END 1042 domCode:ui::DomCode::END 1043 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 1044} 1045 1046- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender { 1047 [self handleAction:ui::TextEditCommand:: 1048 MOVE_TO_BEGINNING_OF_DOCUMENT_AND_MODIFY_SELECTION 1049 keyCode:ui::VKEY_HOME 1050 domCode:ui::DomCode::HOME 1051 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 1052} 1053 1054- (void)pageDownAndModifySelection:(id)sender { 1055 [self handleAction:ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION 1056 keyCode:ui::VKEY_NEXT 1057 domCode:ui::DomCode::PAGE_DOWN 1058 eventFlags:ui::EF_SHIFT_DOWN]; 1059} 1060 1061- (void)pageUpAndModifySelection:(id)sender { 1062 [self handleAction:ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION 1063 keyCode:ui::VKEY_PRIOR 1064 domCode:ui::DomCode::PAGE_UP 1065 eventFlags:ui::EF_SHIFT_DOWN]; 1066} 1067 1068- (void)moveParagraphForwardAndModifySelection:(id)sender { 1069 [self handleAction:ui::TextEditCommand:: 1070 MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION 1071 keyCode:ui::VKEY_DOWN 1072 domCode:ui::DomCode::ARROW_DOWN 1073 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 1074} 1075 1076- (void)moveParagraphBackwardAndModifySelection:(id)sender { 1077 [self handleAction:ui::TextEditCommand:: 1078 MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION 1079 keyCode:ui::VKEY_UP 1080 domCode:ui::DomCode::ARROW_UP 1081 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 1082} 1083 1084- (void)moveWordRight:(id)sender { 1085 [self handleAction:ui::TextEditCommand::MOVE_WORD_RIGHT 1086 keyCode:ui::VKEY_RIGHT 1087 domCode:ui::DomCode::ARROW_RIGHT 1088 eventFlags:ui::EF_CONTROL_DOWN]; 1089} 1090 1091- (void)moveWordLeft:(id)sender { 1092 [self handleAction:ui::TextEditCommand::MOVE_WORD_LEFT 1093 keyCode:ui::VKEY_LEFT 1094 domCode:ui::DomCode::ARROW_LEFT 1095 eventFlags:ui::EF_CONTROL_DOWN]; 1096} 1097 1098- (void)moveRightAndModifySelection:(id)sender { 1099 [self handleAction:ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION 1100 keyCode:ui::VKEY_RIGHT 1101 domCode:ui::DomCode::ARROW_RIGHT 1102 eventFlags:ui::EF_SHIFT_DOWN]; 1103} 1104 1105- (void)moveLeftAndModifySelection:(id)sender { 1106 [self handleAction:ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION 1107 keyCode:ui::VKEY_LEFT 1108 domCode:ui::DomCode::ARROW_LEFT 1109 eventFlags:ui::EF_SHIFT_DOWN]; 1110} 1111 1112- (void)moveWordRightAndModifySelection:(id)sender { 1113 [self handleAction:ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION 1114 keyCode:ui::VKEY_RIGHT 1115 domCode:ui::DomCode::ARROW_RIGHT 1116 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 1117} 1118 1119- (void)moveWordLeftAndModifySelection:(id)sender { 1120 [self handleAction:ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION 1121 keyCode:ui::VKEY_LEFT 1122 domCode:ui::DomCode::ARROW_LEFT 1123 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 1124} 1125 1126- (void)moveToLeftEndOfLine:(id)sender { 1127 [self isTextRTL] ? [self moveToEndOfLine:sender] 1128 : [self moveToBeginningOfLine:sender]; 1129} 1130 1131- (void)moveToRightEndOfLine:(id)sender { 1132 [self isTextRTL] ? [self moveToBeginningOfLine:sender] 1133 : [self moveToEndOfLine:sender]; 1134} 1135 1136- (void)moveToLeftEndOfLineAndModifySelection:(id)sender { 1137 [self isTextRTL] ? [self moveToEndOfLineAndModifySelection:sender] 1138 : [self moveToBeginningOfLineAndModifySelection:sender]; 1139} 1140 1141- (void)moveToRightEndOfLineAndModifySelection:(id)sender { 1142 [self isTextRTL] ? [self moveToBeginningOfLineAndModifySelection:sender] 1143 : [self moveToEndOfLineAndModifySelection:sender]; 1144} 1145 1146// Graphical Element transposition 1147 1148- (void)transpose:(id)sender { 1149 [self handleAction:ui::TextEditCommand::TRANSPOSE 1150 keyCode:ui::VKEY_T 1151 domCode:ui::DomCode::US_T 1152 eventFlags:ui::EF_CONTROL_DOWN]; 1153} 1154 1155// Deletions. 1156 1157- (void)deleteForward:(id)sender { 1158 [self handleAction:ui::TextEditCommand::DELETE_FORWARD 1159 keyCode:ui::VKEY_DELETE 1160 domCode:ui::DomCode::DEL 1161 eventFlags:0]; 1162} 1163 1164- (void)deleteBackward:(id)sender { 1165 [self handleAction:ui::TextEditCommand::DELETE_BACKWARD 1166 keyCode:ui::VKEY_BACK 1167 domCode:ui::DomCode::BACKSPACE 1168 eventFlags:0]; 1169} 1170 1171- (void)deleteWordForward:(id)sender { 1172 [self handleAction:ui::TextEditCommand::DELETE_WORD_FORWARD 1173 keyCode:ui::VKEY_DELETE 1174 domCode:ui::DomCode::DEL 1175 eventFlags:ui::EF_CONTROL_DOWN]; 1176} 1177 1178- (void)deleteWordBackward:(id)sender { 1179 [self handleAction:ui::TextEditCommand::DELETE_WORD_BACKWARD 1180 keyCode:ui::VKEY_BACK 1181 domCode:ui::DomCode::BACKSPACE 1182 eventFlags:ui::EF_CONTROL_DOWN]; 1183} 1184 1185- (void)deleteToBeginningOfLine:(id)sender { 1186 [self handleAction:ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE 1187 keyCode:ui::VKEY_BACK 1188 domCode:ui::DomCode::BACKSPACE 1189 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 1190} 1191 1192- (void)deleteToEndOfLine:(id)sender { 1193 [self handleAction:ui::TextEditCommand::DELETE_TO_END_OF_LINE 1194 keyCode:ui::VKEY_DELETE 1195 domCode:ui::DomCode::DEL 1196 eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; 1197} 1198 1199- (void)deleteToBeginningOfParagraph:(id)sender { 1200 [self handleAction:ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH 1201 keyCode:ui::VKEY_UNKNOWN 1202 domCode:ui::DomCode::NONE 1203 eventFlags:0]; 1204} 1205 1206- (void)deleteToEndOfParagraph:(id)sender { 1207 [self handleAction:ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH 1208 keyCode:ui::VKEY_UNKNOWN 1209 domCode:ui::DomCode::NONE 1210 eventFlags:0]; 1211} 1212 1213- (void)yank:(id)sender { 1214 [self handleAction:ui::TextEditCommand::YANK 1215 keyCode:ui::VKEY_Y 1216 domCode:ui::DomCode::US_Y 1217 eventFlags:ui::EF_CONTROL_DOWN]; 1218} 1219 1220// Cancellation. 1221 1222- (void)cancelOperation:(id)sender { 1223 [self handleAction:ui::TextEditCommand::INVALID_COMMAND 1224 keyCode:ui::VKEY_ESCAPE 1225 domCode:ui::DomCode::ESCAPE 1226 eventFlags:0]; 1227} 1228 1229// Support for Services in context menus. 1230// Currently we only support reading and writing plain strings. 1231- (id)validRequestorForSendType:(NSString*)sendType 1232 returnType:(NSString*)returnType { 1233 NSString* const utf8Type = base::mac::CFToNSCast(kUTTypeUTF8PlainText); 1234 BOOL canWrite = 1235 [sendType isEqualToString:utf8Type] && [self selectedRange].length > 0; 1236 BOOL canRead = [returnType isEqualToString:utf8Type]; 1237 // Valid if (sendType, returnType) is either (string, nil), (nil, string), 1238 // or (string, string). 1239 BOOL valid = 1240 [self hasTextInputClient] && ((canWrite && (canRead || !returnType)) || 1241 (canRead && (canWrite || !sendType))); 1242 return valid 1243 ? self 1244 : [super validRequestorForSendType:sendType returnType:returnType]; 1245} 1246 1247// NSServicesMenuRequestor protocol 1248 1249- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard types:(NSArray*)types { 1250 // NB: The NSServicesMenuRequestor protocol has not (as of 10.14) been 1251 // upgraded to request UTIs rather than obsolete PboardType constants. Handle 1252 // either for when it is upgraded. 1253 DCHECK([types containsObject:NSStringPboardType] || 1254 [types containsObject:base::mac::CFToNSCast(kUTTypeUTF8PlainText)]); 1255 1256 bool result = NO; 1257 base::string16 text; 1258 if (_bridge) 1259 _bridge->text_input_host()->GetSelectionText(&result, &text); 1260 if (!result) 1261 return NO; 1262 return [pboard writeObjects:@[ base::SysUTF16ToNSString(text) ]]; 1263} 1264 1265- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard { 1266 NSArray* objects = [pboard readObjectsForClasses:@ [[NSString class]] 1267 options:0]; 1268 DCHECK([objects count] == 1); 1269 [self insertText:[objects lastObject]]; 1270 return YES; 1271} 1272 1273// NSTextInputClient protocol implementation. 1274 1275// IMPORTANT: Always null-check |[self textInputClient]|. It can change (or be 1276// cleared) in -setTextInputClient:, which requires informing AppKit that the 1277// -inputContext has changed and to update its raw pointer. However, the AppKit 1278// method which does that may also spin a nested run loop communicating with an 1279// IME window and cause it to *use* the exact same NSTextInputClient (i.e., 1280// |self|) that we're trying to invalidate in -setTextInputClient:. 1281// See https://crbug.com/817097#c12 for further details on this atrocity. 1282 1283- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range 1284 actualRange: 1285 (NSRangePointer)actualRange { 1286 // On TouchBar Macs, the IME subsystem sometimes sends an invalid range with a 1287 // non-zero length. This will cause a DCHECK in gfx::Range, so repair it here. 1288 // See https://crbug.com/888782. 1289 if (range.location == NSNotFound) 1290 range.length = 0; 1291 base::string16 substring; 1292 gfx::Range actual_range = gfx::Range::InvalidRange(); 1293 if (_bridge) { 1294 _bridge->text_input_host()->GetAttributedSubstringForRange( 1295 gfx::Range(range), &substring, &actual_range); 1296 } 1297 if (actualRange) { 1298 // To maintain consistency with NSTextView, return range {0,0} for an out of 1299 // bounds requested range. 1300 *actualRange = 1301 actual_range.IsValid() ? actual_range.ToNSRange() : NSMakeRange(0, 0); 1302 } 1303 return substring.empty() 1304 ? nil 1305 : [[[NSAttributedString alloc] 1306 initWithString:base::SysUTF16ToNSString(substring)] 1307 autorelease]; 1308} 1309 1310- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { 1311 NOTIMPLEMENTED(); 1312 return 0; 1313} 1314 1315- (void)doCommandBySelector:(SEL)selector { 1316 // Like the renderer, handle insert action messages as a regular key dispatch. 1317 // This ensures, e.g., insertTab correctly changes focus between fields. This 1318 // handles: 1319 // -insertTab:(id)sender 1320 // -insertBacktab: 1321 // -insertNewline: 1322 // -insertParagraphSeparator: 1323 // -insertNewlineIgnoringFieldEditor: 1324 // -insertTabIgnoringFieldEditor: 1325 // -insertLineBreak: 1326 // -insertContainerBreak: 1327 // -insertSingleQuoteIgnoringSubstitution: 1328 // -insertDoubleQuoteIgnoringSubstitution: 1329 // It does not handle |-insertText:(id)insertString|, which is not a command. 1330 // I.e. AppKit invokes _either_ -insertText: or -doCommandBySelector:. Also 1331 // note -insertText: is only invoked if -inputContext: has returned nil. 1332 DCHECK_NE(@selector(insertText:), selector); 1333 if (_keyDownEvent && [NSStringFromSelector(selector) hasPrefix:@"insert"]) { 1334 // When return is pressed during IME composition, engines typically first 1335 // confirm the composition with a series of -insertText:replacementRange: 1336 // calls. Then, some also invoke -insertNewLine: (some do not). If an engine 1337 // DOES invokes -insertNewLine:, we always want a corresponding VKEY_RETURN 1338 // ui::KeyEvent generated. If it does NOT follow with -insertNewLine:, the 1339 // VKEY_RETURN must be suppressed in keyDown:, since it typically will have 1340 // merely dismissed the IME window: the composition is still ongoing. 1341 // Setting this ensures keyDown: always generates a ui::KeyEvent. 1342 _wantsKeyHandledForInsert = YES; 1343 return; // Handle in -keyDown:. 1344 } 1345 1346 if ([self respondsToSelector:selector]) { 1347 [self performSelector:selector withObject:nil]; 1348 _hasUnhandledKeyDownEvent = NO; 1349 return; 1350 } 1351 1352 // For events that AppKit sends via doCommandBySelector:, first attempt to 1353 // handle as a Widget accelerator. Forward along the responder chain only if 1354 // the Widget doesn't handle it. 1355 if (![self handleUnhandledKeyDownAsKeyEvent]) 1356 [[self nextResponder] doCommandBySelector:selector]; 1357} 1358 1359- (NSRect)firstRectForCharacterRange:(NSRange)range 1360 actualRange:(NSRangePointer)actualNSRange { 1361 gfx::Rect rect; 1362 gfx::Range actualRange = gfx::Range::InvalidRange(); 1363 if (_bridge) { 1364 _bridge->text_input_host()->GetFirstRectForRange(gfx::Range(range), &rect, 1365 &actualRange); 1366 } 1367 if (actualNSRange) 1368 *actualNSRange = actualRange.ToNSRange(); 1369 return gfx::ScreenRectToNSRect(rect); 1370} 1371 1372- (BOOL)hasMarkedText { 1373 bool hasCompositionText = NO; 1374 if (_bridge) 1375 _bridge->text_input_host()->HasCompositionText(&hasCompositionText); 1376 return hasCompositionText; 1377} 1378 1379- (void)insertText:(id)text replacementRange:(NSRange)replacementRange { 1380 if (!_bridge) 1381 return; 1382 _bridge->text_input_host()->DeleteRange(gfx::Range(replacementRange)); 1383 [self insertTextInternal:text]; 1384} 1385 1386- (NSRange)markedRange { 1387 gfx::Range range = gfx::Range::InvalidRange(); 1388 if (_bridge) 1389 _bridge->text_input_host()->GetCompositionTextRange(&range); 1390 return range.ToNSRange(); 1391} 1392 1393- (NSRange)selectedRange { 1394 gfx::Range range = gfx::Range::InvalidRange(); 1395 if (_bridge) 1396 _bridge->text_input_host()->GetSelectionRange(&range); 1397 return range.ToNSRange(); 1398} 1399 1400- (void)setMarkedText:(id)text 1401 selectedRange:(NSRange)selectedRange 1402 replacementRange:(NSRange)replacementRange { 1403 if (![self hasTextInputClient]) 1404 return; 1405 1406 if ([text isKindOfClass:[NSAttributedString class]]) 1407 text = [text string]; 1408 _bridge->text_input_host()->SetCompositionText(base::SysNSStringToUTF16(text), 1409 gfx::Range(selectedRange), 1410 gfx::Range(replacementRange)); 1411 _hasUnhandledKeyDownEvent = NO; 1412} 1413 1414- (void)unmarkText { 1415 if (![self hasTextInputClient]) 1416 return; 1417 1418 _bridge->text_input_host()->ConfirmCompositionText(); 1419 _hasUnhandledKeyDownEvent = NO; 1420} 1421 1422- (NSArray*)validAttributesForMarkedText { 1423 return @[]; 1424} 1425 1426// NSUserInterfaceValidations protocol implementation. 1427 1428- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { 1429 ui::TextEditCommand command = GetTextEditCommandForMenuAction([item action]); 1430 1431 if (command == ui::TextEditCommand::INVALID_COMMAND) 1432 return NO; 1433 1434 // TODO(https://crbug.com/901490): Add mojo support for ui::TextEditCommand. 1435 if ([self textInputClient]) 1436 return [self textInputClient] -> IsTextEditCommandEnabled(command); 1437 1438 // views::Label does not implement the TextInputClient interface but still 1439 // needs to intercept the Copy and Select All menu actions. 1440 if (command != ui::TextEditCommand::COPY && 1441 command != ui::TextEditCommand::SELECT_ALL) 1442 return NO; 1443 1444 bool is_textual = false; 1445 _bridge->host()->GetIsFocusedViewTextual(&is_textual); 1446 return is_textual; 1447} 1448 1449// NSDraggingSource protocol implementation. 1450 1451- (NSDragOperation)draggingSession:(NSDraggingSession*)session 1452 sourceOperationMaskForDraggingContext:(NSDraggingContext)context { 1453 return NSDragOperationEvery; 1454} 1455 1456- (void)draggingSession:(NSDraggingSession*)session 1457 endedAtPoint:(NSPoint)screenPoint 1458 operation:(NSDragOperation)operation { 1459 remote_cocoa::DragDropClient* client = [self dragDropClient]; 1460 if (client) 1461 client->EndDrag(); 1462} 1463 1464// NSAccessibility formal protocol implementation: 1465 1466- (NSArray*)accessibilityChildren { 1467 if (id accessible = _bridge->host_helper()->GetNativeViewAccessible()) 1468 return @[ accessible ]; 1469 return [super accessibilityChildren]; 1470} 1471 1472// NSAccessibility informal protocol implementation: 1473 1474- (id)accessibilityHitTest:(NSPoint)point { 1475 return [_bridge->host_helper()->GetNativeViewAccessible() 1476 accessibilityHitTest:point]; 1477} 1478 1479- (id)accessibilityFocusedUIElement { 1480 // This function should almost-never be called because when |self| is the 1481 // first responder for the key NSWindow, NativeWidgetMacNSWindowHost's 1482 // AccessibilityFocusOverrider will override the accessibility focus query. 1483 if (!_bridge) 1484 return nil; 1485 return [_bridge->host_helper()->GetNativeViewAccessible() 1486 accessibilityFocusedUIElement]; 1487} 1488 1489@end 1490