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