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