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