// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "components/remote_cocoa/app_shim/bridged_content_view.h" #include "base/logging.h" #import "base/mac/foundation_util.h" #import "base/mac/mac_util.h" #import "base/mac/scoped_nsobject.h" #include "base/metrics/histogram_macros.h" #include "base/strings/sys_string_conversions.h" #import "components/remote_cocoa/app_shim/drag_drop_client.h" #import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h" #include "components/remote_cocoa/app_shim/native_widget_ns_window_host_helper.h" #include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h" #include "ui/base/clipboard/clipboard_constants.h" #import "ui/base/cocoa/appkit_utils.h" #include "ui/base/cocoa/cocoa_base_utils.h" #import "ui/base/dragdrop/cocoa_dnd_util.h" #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/dragdrop/os_exchange_data_provider_mac.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/text_edit_commands.h" #include "ui/base/ime/text_input_client.h" #import "ui/events/cocoa/cocoa_event_utils.h" #include "ui/events/event_utils.h" #include "ui/events/keycodes/dom/dom_code.h" #import "ui/events/keycodes/keyboard_code_conversion_mac.h" #include "ui/gfx/canvas_paint_mac.h" #include "ui/gfx/decorated_text.h" #import "ui/gfx/decorated_text_mac.h" #include "ui/gfx/geometry/rect.h" #import "ui/gfx/mac/coordinate_conversion.h" #import "ui/gfx/path_mac.h" #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" namespace { NSString* const kFullKeyboardAccessChangedNotification = @"com.apple.KeyboardUIModeDidChange"; // Convert a |point| in |source_window|'s AppKit coordinate system (origin at // the bottom left of the window) to |target_window|'s content rect, with the // origin at the top left of the content area. // If |source_window| is nil, |point| will be treated as screen coordinates. gfx::Point MovePointToWindow(const NSPoint& point, NSWindow* source_window, NSWindow* target_window) { NSPoint point_in_screen = source_window ? ui::ConvertPointFromWindowToScreen(source_window, point) : point; NSPoint point_in_window = ui::ConvertPointFromScreenToWindow(target_window, point_in_screen); NSRect content_rect = [target_window contentRectForFrameRect:[target_window frame]]; return gfx::Point(point_in_window.x, NSHeight(content_rect) - point_in_window.y); } // Returns true if |event| may have triggered dismissal of an IME and would // otherwise be ignored by a ui::TextInputClient when inserted. bool IsImeTriggerEvent(NSEvent* event) { ui::KeyboardCode key = ui::KeyboardCodeFromNSEvent(event); return key == ui::VKEY_RETURN || key == ui::VKEY_TAB || key == ui::VKEY_ESCAPE; } ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { if (action == @selector(undo:)) return ui::TextEditCommand::UNDO; if (action == @selector(redo:)) return ui::TextEditCommand::REDO; if (action == @selector(cut:)) return ui::TextEditCommand::CUT; if (action == @selector(copy:)) return ui::TextEditCommand::COPY; if (action == @selector(paste:)) return ui::TextEditCommand::PASTE; if (action == @selector(pasteAndMatchStyle:)) return ui::TextEditCommand::PASTE; if (action == @selector(selectAll:)) return ui::TextEditCommand::SELECT_ALL; return ui::TextEditCommand::INVALID_COMMAND; } #if defined(TOOLKIT_QT) // Copy of ui::OSExchangeDataProviderMac::SupportedPasteboardTypes() (ui/base/dragdrop/os_exchange_data_provider_mac.mm) NSArray* SupportedPasteboardTypes() { return @[ ui::kWebCustomDataPboardType, ui::ClipboardUtil::UTIForWebURLsAndTitles(), NSURLPboardType, NSFilenamesPboardType, ui::kChromeDragDummyPboardType, NSStringPboardType, NSHTMLPboardType, NSRTFPboardType, NSFilenamesPboardType, ui::kWebCustomDataPboardType, NSPasteboardTypeString ]; } #endif } // namespace @interface BridgedContentView () // Dispatch |event| to |bridge_|'s host. - (void)dispatchKeyEvent:(ui::KeyEvent*)event; // Returns true if active menu controller corresponds to this widget. Note that // this will synchronously call into the browser process. - (BOOL)hasActiveMenuController; // Dispatch |event| to |menu_controller| and return true if |event| is // swallowed. - (BOOL)dispatchKeyEventToMenuController:(ui::KeyEvent*)event; // Passes |event| to the InputMethod for dispatch. - (void)handleKeyEvent:(ui::KeyEvent*)event; // Allows accelerators to be handled at different points in AppKit key event // dispatch. Checks for an unhandled event passed in to -keyDown: and passes it // to the Widget for processing. Returns YES if the Widget handles it. - (BOOL)handleUnhandledKeyDownAsKeyEvent; // Handles an NSResponder Action Message by mapping it to a corresponding text // editing command from ui_strings.grd and, when not being sent to a // TextInputClient, the keyCode that toolkit-views expects internally. // For example, moveToLeftEndOfLine: would pass ui::VKEY_HOME in non-RTL locales // even though the Home key on Mac defaults to moveToBeginningOfDocument:. // This approach also allows action messages a user // may have remapped in ~/Library/KeyBindings/DefaultKeyBinding.dict to be // catered for. // Note: default key bindings in Mac can be read from StandardKeyBinding.dict // which lives in /System/Library/Frameworks/AppKit.framework/Resources. Do // `plutil -convert xml1 -o StandardKeyBinding.xml StandardKeyBinding.dict` to // get something readable. - (void)handleAction:(ui::TextEditCommand)command keyCode:(ui::KeyboardCode)keyCode domCode:(ui::DomCode)domCode eventFlags:(int)eventFlags; // ui::EventLocationFromNative() assumes the event hit the contentView. // Adjust |event| if that's not the case (e.g. for reparented views). - (void)adjustUiEventLocation:(ui::LocatedEvent*)event fromNativeEvent:(NSEvent*)nativeEvent; // Notification handler invoked when the Full Keyboard Access mode is changed. - (void)onFullKeyboardAccessModeChanged:(NSNotification*)notification; // Helper method which forwards |text| to the active menu or |textInputClient_|. - (void)insertTextInternal:(id)text; // Returns the native Widget's drag drop client. Possibly null. - (remote_cocoa::DragDropClient*)dragDropClient NS_RETURNS_INNER_POINTER; // Returns true if there exists a ui::TextInputClient for the currently focused // views::View. - (BOOL)hasTextInputClient; // Returns true if there exists a ui::TextInputClient for the currently focused // views::View and that client is right-to-left. - (BOOL)isTextRTL; // Menu action handlers. - (void)undo:(id)sender; - (void)redo:(id)sender; - (void)cut:(id)sender; - (void)copy:(id)sender; - (void)paste:(id)sender; - (void)pasteAndMatchStyle:(id)sender; - (void)selectAll:(id)sender; @end @implementation BridgedContentView @synthesize bridge = _bridge; @synthesize drawMenuBackgroundForBlur = _drawMenuBackgroundForBlur; @synthesize keyDownEventForTesting = _keyDownEvent; - (instancetype)initWithBridge:(remote_cocoa::NativeWidgetNSWindowBridge*)bridge bounds:(gfx::Rect)bounds { // To keep things simple, assume the origin is (0, 0) until there exists a use // case for something other than that. DCHECK(bounds.origin().IsOrigin()); NSRect initialFrame = NSMakeRect(0, 0, bounds.width(), bounds.height()); if ((self = [super initWithFrame:initialFrame])) { _bridge = bridge; // Apple's documentation says that NSTrackingActiveAlways is incompatible // with NSTrackingCursorUpdate, so use NSTrackingActiveInActiveApp. _cursorTrackingArea.reset([[CrTrackingArea alloc] initWithRect:NSZeroRect options:NSTrackingMouseMoved | NSTrackingCursorUpdate | NSTrackingActiveInActiveApp | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited owner:self userInfo:nil]); [self addTrackingArea:_cursorTrackingArea.get()]; // Get notified whenever Full Keyboard Access mode is changed. [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onFullKeyboardAccessModeChanged:) name:kFullKeyboardAccessChangedNotification object:nil]; // Initialize the focus manager with the correct keyboard accessibility // setting. [self updateFullKeyboardAccess]; #if !defined(TOOLKIT_QT) [self registerForDraggedTypes:ui::OSExchangeDataProviderMac:: SupportedPasteboardTypes()]; #else [self registerForDraggedTypes:SupportedPasteboardTypes()]; #endif } return self; } - (ui::TextInputClient*)textInputClient { return _bridge ? _bridge->host_helper()->GetTextInputClient() : nullptr; } - (BOOL)hasTextInputClient { bool hasTextInputClient = NO; if (_bridge) _bridge->text_input_host()->HasClient(&hasTextInputClient); return hasTextInputClient; } - (BOOL)isTextRTL { bool isRTL = NO; if (_bridge) _bridge->text_input_host()->IsRTL(&isRTL); return isRTL; } - (void)dealloc { // By the time |self| is dealloc'd, it should never be in an NSWindow, and it // should never be the current input context. DCHECK_EQ(nil, [self window]); [super dealloc]; } - (void)clearView { _bridge = nullptr; [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; [_cursorTrackingArea.get() clearOwner]; [self removeTrackingArea:_cursorTrackingArea.get()]; } - (bool)needsUpdateWindows { // If |self| was being used for the input context, and would now report a // different input context, manually invoke [NSApp updateWindows]. This is // necessary because AppKit holds on to a raw pointer to a NSTextInputContext // (which may have been the one returned by [self inputContext]) that is only // updated by -updateWindows. And although AppKit invokes that on each // iteration through most runloop modes, it does not call it when running // NSEventTrackingRunLoopMode, and not _within_ a run loop iteration, where // the inputContext may change before further event processing. NSTextInputContext* current = [NSTextInputContext currentInputContext]; if (!current) return false; NSTextInputContext* newContext = [self inputContext]; // If the newContext is non-nil, then it can only be [super inputContext]. So // the input context is either not changing, or it was not from |self|. In // both cases, there's no need to call -updateWindows. if (newContext) { DCHECK_EQ(newContext, [super inputContext]); return false; } return current == [super inputContext]; } // If |point| is classified as a draggable background (HTCAPTION), return nil so // that it can lead to a window drag or double-click in the title bar. Dragging // could be optimized by telling the window server which regions should be // instantly draggable without asking (tracked at https://crbug.com/830962). - (NSView*)hitTest:(NSPoint)point { gfx::Point flippedPoint(point.x, NSHeight(self.superview.bounds) - point.y); bool isDraggableBackground = false; _bridge->host()->GetIsDraggableBackgroundAt(flippedPoint, &isDraggableBackground); if (isDraggableBackground) return nil; return [super hitTest:point]; } - (void)processCapturedMouseEvent:(NSEvent*)theEvent { if (!_bridge) return; NSWindow* source = [theEvent window]; NSWindow* target = [self window]; DCHECK(target); BOOL isScrollEvent = [theEvent type] == NSScrollWheel; // If it's the view's window, process normally. if ([target isEqual:source]) { if (isScrollEvent) { [self scrollWheel:theEvent]; } else { [self mouseEvent:theEvent]; if ([theEvent type] == NSLeftMouseUp) [self handleLeftMouseUp:theEvent]; } return; } gfx::Point event_location = MovePointToWindow([theEvent locationInWindow], source, target); [self updateTooltipIfRequiredAt:event_location]; if (isScrollEvent) { auto event = std::make_unique(theEvent); event->set_location(event_location); _bridge->host()->OnScrollEvent(std::move(event)); } else { auto event = std::make_unique(theEvent); event->set_location(event_location); _bridge->host()->OnMouseEvent(std::move(event)); } } - (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent { DCHECK(_bridge); base::string16 newTooltipText; _bridge->host()->GetTooltipTextAt(locationInContent, &newTooltipText); if (newTooltipText != _lastTooltipText) { std::swap(newTooltipText, _lastTooltipText); [self setToolTipAtMousePoint:base::SysUTF16ToNSString(_lastTooltipText)]; } } - (void)updateFullKeyboardAccess { if (!_bridge) return; _bridge->host()->SetKeyboardAccessible([NSApp isFullKeyboardAccessEnabled]); } // BridgedContentView private implementation. - (void)dispatchKeyEvent:(ui::KeyEvent*)event { if (_bridge) _bridge->host_helper()->DispatchKeyEvent(event); } - (BOOL)hasActiveMenuController { bool hasMenuController = false; if (_bridge) _bridge->host()->GetHasMenuController(&hasMenuController); return hasMenuController; } - (BOOL)dispatchKeyEventToMenuController:(ui::KeyEvent*)event { if (_bridge) return _bridge->host_helper()->DispatchKeyEventToMenuController(event); return false; } - (void)handleKeyEvent:(ui::KeyEvent*)event { DCHECK(event); if ([self dispatchKeyEventToMenuController:event]) return; [self dispatchKeyEvent:event]; } - (BOOL)handleUnhandledKeyDownAsKeyEvent { if (!_hasUnhandledKeyDownEvent) return NO; ui::KeyEvent event(_keyDownEvent); [self handleKeyEvent:&event]; _hasUnhandledKeyDownEvent = NO; return event.handled(); } - (void)handleAction:(ui::TextEditCommand)command keyCode:(ui::KeyboardCode)keyCode domCode:(ui::DomCode)domCode eventFlags:(int)eventFlags { if (!_bridge) return; // Always propagate the shift modifier if present. Shift doesn't always alter // the command selector, but should always be passed along. Control and Alt // have different meanings on Mac, so they do not propagate automatically. if ([_keyDownEvent modifierFlags] & NSShiftKeyMask) eventFlags |= ui::EF_SHIFT_DOWN; // Generate a synthetic event with the keycode toolkit-views expects. ui::KeyEvent event(ui::ET_KEY_PRESSED, keyCode, domCode, eventFlags); if ([self dispatchKeyEventToMenuController:&event]) return; // If there's an active TextInputClient, schedule the editing command to be // performed. // TODO(https://crbug.com/901490): Add mojo support for ui::TextEditCommand. if ([self textInputClient] && [self textInputClient] -> IsTextEditCommandEnabled(command)) { [self textInputClient] -> SetTextEditCommandForNextKeyEvent(command); } [self dispatchKeyEvent:&event]; } - (void)adjustUiEventLocation:(ui::LocatedEvent*)event fromNativeEvent:(NSEvent*)nativeEvent { if ([nativeEvent window] && [[self window] contentView] != self) { NSPoint p = [self convertPoint:[nativeEvent locationInWindow] fromView:nil]; event->set_location(gfx::Point(p.x, NSHeight([self frame]) - p.y)); } } - (void)onFullKeyboardAccessModeChanged:(NSNotification*)notification { DCHECK([[notification name] isEqualToString:kFullKeyboardAccessChangedNotification]); [self updateFullKeyboardAccess]; } - (void)insertTextInternal:(id)text { if (!_bridge) return; if ([text isKindOfClass:[NSAttributedString class]]) text = [text string]; bool isCharacterEvent = _keyDownEvent && [text length] == 1; // Pass "character" events to the View hierarchy. Cases this handles (non- // exhaustive)- // - Space key press on controls. Unlike Tab and newline which have // corresponding action messages, an insertText: message is generated for // the Space key (insertText:replacementRange: when there's an active // input context). // - Menu mnemonic selection. // Note we create a custom character ui::KeyEvent (and not use the // ui::KeyEvent(NSEvent*) constructor) since we can't just rely on the event // key code to get the actual characters from the ui::KeyEvent. This for // example is necessary for menu mnemonic selection of non-latin text. // Don't generate a key event when there is marked composition text. These key // down events should be consumed by the IME and not reach the Views layer. // For example, on pressing Return to commit composition text, if we passed a // synthetic key event to the View hierarchy, it will have the effect of // performing the default action on the current dialog. We do not want this // when there is marked text (Return should only confirm the IME). // However, IME for phonetic languages such as Korean do not always _mark_ // text when a composition is active. For these, correct behaviour is to // handle the final -keyDown: that caused the composition to be committed, but // only _after_ the sequence of insertText: messages coming from IME have been // sent to the TextInputClient. Detect this by comparing to -[NSEvent // characters]. Note we do not use -charactersIgnoringModifiers: so that, // e.g., ß (Alt+s) will match mnemonics with ß rather than s. bool isFinalInsertForKeyEvent = isCharacterEvent && [text isEqualToString:[_keyDownEvent characters]]; // Also note that a single, non-IME key down event can also cause multiple // insertText:replacementRange: action messages being generated from within // -keyDown:'s call to -interpretKeyEvents:. One example, on pressing Alt+e, // the accent (´) character is composed via setMarkedText:. Now on pressing // the character 'r', two insertText:replacementRange: action messages are // generated with the text value of accent (´) and 'r' respectively. The key // down event will have characters field of length 2. The first of these // insertText messages won't generate a KeyEvent since there'll be active // marked text. However, a KeyEvent will be generated corresponding to 'r'. // Currently there seems to be no use case to pass non-character events routed // from insertText: handlers to the View hierarchy. if (isFinalInsertForKeyEvent && ![self hasMarkedText]) { ui::KeyEvent charEvent([text characterAtIndex:0], ui::KeyboardCodeFromNSEvent(_keyDownEvent), ui::DomCodeFromNSEvent(_keyDownEvent), ui::EF_NONE); [self handleKeyEvent:&charEvent]; _hasUnhandledKeyDownEvent = NO; if (charEvent.handled()) return; } // Forward the |text| to |textInputClient_| if no menu is active. if ([self hasTextInputClient] && ![self hasActiveMenuController]) { // Note we don't check isFinalInsertForKeyEvent, nor use |keyDownEvent_| // to generate the synthetic ui::KeyEvent since: For composed text, // [keyDownEvent_ characters] might not be the same as |text|. This is // because |keyDownEvent_| will correspond to the event that caused the // composition text to be confirmed, say, Return key press. _bridge->text_input_host()->InsertText(base::SysNSStringToUTF16(text), isCharacterEvent); // Suppress accelerators that may be bound to this key, since it inserted // text instead. But note that IME may follow with -insertNewLine:, which // will resurrect the keyEvent for accelerator handling. _hasUnhandledKeyDownEvent = NO; } } - (remote_cocoa::DragDropClient*)dragDropClient { return _bridge ? _bridge->drag_drop_client() : nullptr; } - (void)undo:(id)sender { [self handleAction:ui::TextEditCommand::UNDO keyCode:ui::VKEY_Z domCode:ui::DomCode::US_Z eventFlags:ui::EF_CONTROL_DOWN]; } - (void)redo:(id)sender { [self handleAction:ui::TextEditCommand::REDO keyCode:ui::VKEY_Z domCode:ui::DomCode::US_Z eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)cut:(id)sender { [self handleAction:ui::TextEditCommand::CUT keyCode:ui::VKEY_X domCode:ui::DomCode::US_X eventFlags:ui::EF_CONTROL_DOWN]; } - (void)copy:(id)sender { [self handleAction:ui::TextEditCommand::COPY keyCode:ui::VKEY_C domCode:ui::DomCode::US_C eventFlags:ui::EF_CONTROL_DOWN]; } - (void)paste:(id)sender { [self handleAction:ui::TextEditCommand::PASTE keyCode:ui::VKEY_V domCode:ui::DomCode::US_V eventFlags:ui::EF_CONTROL_DOWN]; } - (void)pasteAndMatchStyle:(id)sender { [self handleAction:ui::TextEditCommand::PASTE keyCode:ui::VKEY_V domCode:ui::DomCode::US_V eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)selectAll:(id)sender { [self handleAction:ui::TextEditCommand::SELECT_ALL keyCode:ui::VKEY_A domCode:ui::DomCode::US_A eventFlags:ui::EF_CONTROL_DOWN]; } // BaseView implementation. // Don't use tracking areas from BaseView. BridgedContentView's tracks // NSTrackingCursorUpdate and Apple's documentation suggests it's incompatible. - (void)enableTracking { } // Translates the location of |theEvent| to toolkit-views coordinates and passes // the event to NativeWidgetMac for handling. - (void)mouseEvent:(NSEvent*)theEvent { if (!_bridge) return; DCHECK([theEvent type] != NSScrollWheel); auto event = std::make_unique(theEvent); [self adjustUiEventLocation:event.get() fromNativeEvent:theEvent]; // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). // Mac hooks in here. [self updateTooltipIfRequiredAt:event->location()]; _bridge->host()->OnMouseEvent(std::move(event)); } - (void)forceTouchEvent:(NSEvent*)theEvent { if (ui::ForceClickInvokesQuickLook()) [self quickLookWithEvent:theEvent]; } // NSView implementation. // Refuse first responder so that clicking a blank area of the view don't take // first responder away from another view. This does not prevent the view // becoming first responder via -[NSWindow makeFirstResponder:] when invoked // during Init or by FocusManager. // // The condition is to work around an AppKit quirk. When a window is being // ordered front, if its current first responder returns |NO| for this method, // it resigns it if it can find another responder in the key loop that replies // |YES|. - (BOOL)acceptsFirstResponder { return self.window.firstResponder == self; } // This undocumented method determines which parts of the view prevent // server-side window dragging (i.e. aren't draggable without asking the app // first). Since Views decides click-by-click whether to handle an event, the // whole view is off limits but, since the view's content is rendered out of // process and the view is locally transparent, AppKit won't guess that. - (NSRect)_opaqueRectForWindowMoveWhenInTitlebar { return self.bounds; } - (BOOL)becomeFirstResponder { BOOL result = [super becomeFirstResponder]; if (result && _bridge) _bridge->host()->OnIsFirstResponderChanged(true); return result; } - (BOOL)resignFirstResponder { BOOL result = [super resignFirstResponder]; if (result && _bridge) _bridge->host()->OnIsFirstResponderChanged(false); return result; } - (void)viewDidMoveToWindow { // When this view is added to a window, AppKit calls setFrameSize before it is // added to the window, so the behavior in setFrameSize is not triggered. NSWindow* window = [self window]; if (window) [self setFrameSize:NSZeroSize]; } - (void)setFrameSize:(NSSize)newSize { // The size passed in here does not always use // -[NSWindow contentRectForFrameRect]. The following ensures that the // contentView for a frameless window can extend over the titlebar of the new // window containing it, since AppKit requires a titlebar to give frameless // windows correct shadows and rounded corners. NSWindow* window = [self window]; if (window && [window contentView] == self) { newSize = [window contentRectForFrameRect:[window frame]].size; // Ensure that the window geometry be updated on the host side before the // view size is updated. // TODO(ccameron): Consider updating the view size and window size and // position together in UpdateWindowGeometry. // https://crbug.com/875776, https://crbug.com/875731 if (_bridge) _bridge->UpdateWindowGeometry(); } [super setFrameSize:newSize]; if (_bridge) _bridge->host()->OnViewSizeChanged( gfx::Size(newSize.width, newSize.height)); } - (BOOL)isOpaque { return _bridge ? !_bridge->is_translucent_window() : NO; } // To maximize consistency with the Cocoa browser (mac_views_browser=0), accept // mouse clicks immediately so that clicking on Chrome from an inactive window // will allow the event to be processed, rather than merely activate the window. - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent { return YES; } // NSDraggingDestination protocol overrides. - (NSDragOperation)draggingEntered:(id)sender { return [self draggingUpdated:sender]; } - (NSDragOperation)draggingUpdated:(id)sender { remote_cocoa::DragDropClient* client = [self dragDropClient]; const auto drag_operation = client ? client->DragUpdate(sender) : ui::DragDropTypes::DRAG_NONE; UMA_HISTOGRAM_BOOLEAN("Event.DragDrop.AcceptDragUpdate", drag_operation != ui::DragDropTypes::DRAG_NONE); return drag_operation; } - (void)draggingExited:(id)sender { remote_cocoa::DragDropClient* client = [self dragDropClient]; if (client) client->DragExit(); } - (BOOL)performDragOperation:(id)sender { remote_cocoa::DragDropClient* client = [self dragDropClient]; return client && client->Drop(sender) != NSDragOperationNone; } - (NSTextInputContext*)inputContext { if (!_bridge) return nil; bool hasTextInputContext = false; _bridge->text_input_host()->HasInputContext(&hasTextInputContext); return hasTextInputContext ? [super inputContext] : nil; } // NSResponder implementation. - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event { // This is a SPI that AppKit apparently calls after |performKeyEquivalent:| // returned NO. If this function returns |YES|, Cocoa sends the event to // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent // to us instead of doing key view loop control, ctrl-left/right get handled // correctly, etc. // (However, there are still some keys that Cocoa swallows, e.g. the key // equivalent that Cocoa uses for toggling the input language. In this case, // that's actually a good thing, though -- see http://crbug.com/26115 .) return YES; } - (void)keyDown:(NSEvent*)theEvent { BOOL hadMarkedTextAtKeyDown = [self hasMarkedText]; // Convert the event into an action message, according to OSX key mappings. _keyDownEvent = theEvent; _hasUnhandledKeyDownEvent = YES; _wantsKeyHandledForInsert = NO; [self interpretKeyEvents:@[ theEvent ]]; // When there is marked text, -[NSView interpretKeyEvents:] may handle the // event by dismissing the IME window in a way that neither unmarks text, nor // updates any composition. That is, no signal is given either to the // NSTextInputClient or the NSTextInputContext that the IME changed state. // However, we must ensure this key down is not processed as an accelerator. // TODO(tapted): Investigate removing the IsImeTriggerEvent() check - it's // probably not required, but helps tests that expect some events to always // get processed (i.e. TextfieldTest.TextInputClientTest). if (hadMarkedTextAtKeyDown && IsImeTriggerEvent(theEvent)) _hasUnhandledKeyDownEvent = NO; // Even with marked text, some IMEs may follow with -insertNewLine:; // simultaneously confirming the composition. In this case, always generate // the corresponding ui::KeyEvent. Note this is done even if there was no // marked text, so it is orthogonal to the case above. if (_wantsKeyHandledForInsert) _hasUnhandledKeyDownEvent = YES; // If |hasUnhandledKeyDownEvent_| wasn't set to NO during // -interpretKeyEvents:, it wasn't handled. Give Widget accelerators a chance // to handle it. [self handleUnhandledKeyDownAsKeyEvent]; DCHECK(!_hasUnhandledKeyDownEvent); _keyDownEvent = nil; } - (void)keyUp:(NSEvent*)theEvent { ui::KeyEvent event(theEvent); [self handleKeyEvent:&event]; } - (void)flagsChanged:(NSEvent*)theEvent { if (theEvent.keyCode == 0) { // An event like this gets sent when sending some key commands via // AppleScript. Since 0 is VKEY_A, we end up interpreting this as Cmd+A // which is incorrect. The correct event for command up/down (keyCode = 55) // is also sent, so we should drop this one. See https://crbug.com/889618 return; } ui::KeyEvent event(theEvent); [self handleKeyEvent:&event]; } - (void)scrollWheel:(NSEvent*)theEvent { if (!_bridge) return; auto event = std::make_unique(theEvent); [self adjustUiEventLocation:event.get() fromNativeEvent:theEvent]; // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). // Mac hooks in here. [self updateTooltipIfRequiredAt:event->location()]; _bridge->host()->OnScrollEvent(std::move(event)); } // Called when we get a three-finger swipe, and they're enabled in System // Preferences. - (void)swipeWithEvent:(NSEvent*)event { if (!_bridge) return; // themblsha: In my testing all three-finger swipes send only a single event // with a value of +/-1 on either axis. Diagonal swipes are not handled by // AppKit. // We need to invert deltas in order to match GestureEventDetails's // directions. ui::GestureEventDetails swipeDetails(ui::ET_GESTURE_SWIPE, -[event deltaX], -[event deltaY]); swipeDetails.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD); swipeDetails.set_touch_points(3); gfx::PointF location = ui::EventLocationFromNative(event); // Note this uses the default unique_touch_event_id of 0 (Swipe events do not // support -[NSEvent eventNumber]). This doesn't seem like a real omission // because the three-finger swipes are instant and can't be tracked anyway. auto gestureEvent = std::make_unique( location.x(), location.y(), ui::EventFlagsFromNative(event), ui::EventTimeFromNative(event), swipeDetails); _bridge->host()->OnGestureEvent(std::move(gestureEvent)); } - (void)quickLookWithEvent:(NSEvent*)theEvent { if (!_bridge) return; const gfx::Point locationInContent = gfx::ToFlooredPoint(ui::EventLocationFromNative(theEvent)); bool foundWord = false; gfx::DecoratedText decoratedWord; gfx::Point baselinePoint; _bridge->host_helper()->GetWordAt(locationInContent, &foundWord, &decoratedWord, &baselinePoint); if (!foundWord) return; NSPoint baselinePointAppKit = NSMakePoint( baselinePoint.x(), NSHeight([self frame]) - baselinePoint.y()); [self showDefinitionForAttributedString: gfx::GetAttributedStringFromDecoratedText(decoratedWord) atPoint:baselinePointAppKit]; } //////////////////////////////////////////////////////////////////////////////// // NSResponder Action Messages. Keep sorted according NSResponder.h (from the // 10.9 SDK). The list should eventually be complete. Anything not defined will // beep when interpretKeyEvents: would otherwise call it. // TODO(tapted): Make this list complete, except for insert* methods which are // dispatched as regular key events in doCommandBySelector:. // views::Textfields are single-line only, map Paragraph and Document commands // to Line. Also, Up/Down commands correspond to beginning/end of line. // The insertText action message forwards to the TextInputClient unless a menu // is active. Note that NSResponder's interpretKeyEvents: implementation doesn't // direct insertText: through doCommandBySelector:, so this is still needed to // handle the case when inputContext: is nil. When inputContext: returns non-nil // text goes directly to insertText:replacementRange:. - (void)insertText:(id)text { DCHECK_EQ(nil, [self inputContext]); [self insertTextInternal:text]; } // Selection movement and scrolling. - (void)moveForward:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_FORWARD keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveRight:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_RIGHT keyCode:ui::VKEY_RIGHT domCode:ui::DomCode::ARROW_RIGHT eventFlags:0]; } - (void)moveBackward:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_BACKWARD keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveLeft:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_LEFT keyCode:ui::VKEY_LEFT domCode:ui::DomCode::ARROW_LEFT eventFlags:0]; } - (void)moveUp:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_UP keyCode:ui::VKEY_UP domCode:ui::DomCode::ARROW_UP eventFlags:0]; } - (void)moveDown:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_DOWN keyCode:ui::VKEY_DOWN domCode:ui::DomCode::ARROW_DOWN eventFlags:0]; } - (void)moveWordForward:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_WORD_FORWARD keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveWordBackward:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_WORD_BACKWARD keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveToBeginningOfLine:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE keyCode:ui::VKEY_HOME domCode:ui::DomCode::HOME eventFlags:0]; } - (void)moveToEndOfLine:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_LINE keyCode:ui::VKEY_END domCode:ui::DomCode::END eventFlags:0]; } - (void)moveToBeginningOfParagraph:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_PARAGRAPH keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveToEndOfParagraph:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveToEndOfDocument:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT keyCode:ui::VKEY_END domCode:ui::DomCode::END eventFlags:ui::EF_CONTROL_DOWN]; } - (void)moveToBeginningOfDocument:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT keyCode:ui::VKEY_HOME domCode:ui::DomCode::HOME eventFlags:ui::EF_CONTROL_DOWN]; } - (void)pageDown:(id)sender { // The pageDown: action message is bound to the key combination // [Option+PageDown]. [self handleAction:ui::TextEditCommand::MOVE_PAGE_DOWN keyCode:ui::VKEY_NEXT domCode:ui::DomCode::PAGE_DOWN eventFlags:ui::EF_ALT_DOWN]; } - (void)pageUp:(id)sender { // The pageUp: action message is bound to the key combination [Option+PageUp]. [self handleAction:ui::TextEditCommand::MOVE_PAGE_UP keyCode:ui::VKEY_PRIOR domCode:ui::DomCode::PAGE_UP eventFlags:ui::EF_ALT_DOWN]; } - (void)moveBackwardAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_BACKWARD_AND_MODIFY_SELECTION keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveForwardAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_FORWARD_AND_MODIFY_SELECTION keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveWordForwardAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_WORD_FORWARD_AND_MODIFY_SELECTION keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveWordBackwardAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_WORD_BACKWARD_AND_MODIFY_SELECTION keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveUpAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION keyCode:ui::VKEY_UP domCode:ui::DomCode::ARROW_UP eventFlags:ui::EF_SHIFT_DOWN]; } - (void)moveDownAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION keyCode:ui::VKEY_DOWN domCode:ui::DomCode::ARROW_DOWN eventFlags:ui::EF_SHIFT_DOWN]; } - (void)moveToBeginningOfLineAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand:: MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION keyCode:ui::VKEY_HOME domCode:ui::DomCode::HOME eventFlags:ui::EF_SHIFT_DOWN]; } - (void)moveToEndOfLineAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION keyCode:ui::VKEY_END domCode:ui::DomCode::END eventFlags:ui::EF_SHIFT_DOWN]; } - (void)moveToBeginningOfParagraphAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand:: MOVE_TO_BEGINNING_OF_PARAGRAPH_AND_MODIFY_SELECTION keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveToEndOfParagraphAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand:: MOVE_TO_END_OF_PARAGRAPH_AND_MODIFY_SELECTION keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)moveToEndOfDocumentAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand:: MOVE_TO_END_OF_DOCUMENT_AND_MODIFY_SELECTION keyCode:ui::VKEY_END domCode:ui::DomCode::END eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)moveToBeginningOfDocumentAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand:: MOVE_TO_BEGINNING_OF_DOCUMENT_AND_MODIFY_SELECTION keyCode:ui::VKEY_HOME domCode:ui::DomCode::HOME eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)pageDownAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION keyCode:ui::VKEY_NEXT domCode:ui::DomCode::PAGE_DOWN eventFlags:ui::EF_SHIFT_DOWN]; } - (void)pageUpAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION keyCode:ui::VKEY_PRIOR domCode:ui::DomCode::PAGE_UP eventFlags:ui::EF_SHIFT_DOWN]; } - (void)moveParagraphForwardAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand:: MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION keyCode:ui::VKEY_DOWN domCode:ui::DomCode::ARROW_DOWN eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)moveParagraphBackwardAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand:: MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION keyCode:ui::VKEY_UP domCode:ui::DomCode::ARROW_UP eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)moveWordRight:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_WORD_RIGHT keyCode:ui::VKEY_RIGHT domCode:ui::DomCode::ARROW_RIGHT eventFlags:ui::EF_CONTROL_DOWN]; } - (void)moveWordLeft:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_WORD_LEFT keyCode:ui::VKEY_LEFT domCode:ui::DomCode::ARROW_LEFT eventFlags:ui::EF_CONTROL_DOWN]; } - (void)moveRightAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION keyCode:ui::VKEY_RIGHT domCode:ui::DomCode::ARROW_RIGHT eventFlags:ui::EF_SHIFT_DOWN]; } - (void)moveLeftAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION keyCode:ui::VKEY_LEFT domCode:ui::DomCode::ARROW_LEFT eventFlags:ui::EF_SHIFT_DOWN]; } - (void)moveWordRightAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION keyCode:ui::VKEY_RIGHT domCode:ui::DomCode::ARROW_RIGHT eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)moveWordLeftAndModifySelection:(id)sender { [self handleAction:ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION keyCode:ui::VKEY_LEFT domCode:ui::DomCode::ARROW_LEFT eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)moveToLeftEndOfLine:(id)sender { [self isTextRTL] ? [self moveToEndOfLine:sender] : [self moveToBeginningOfLine:sender]; } - (void)moveToRightEndOfLine:(id)sender { [self isTextRTL] ? [self moveToBeginningOfLine:sender] : [self moveToEndOfLine:sender]; } - (void)moveToLeftEndOfLineAndModifySelection:(id)sender { [self isTextRTL] ? [self moveToEndOfLineAndModifySelection:sender] : [self moveToBeginningOfLineAndModifySelection:sender]; } - (void)moveToRightEndOfLineAndModifySelection:(id)sender { [self isTextRTL] ? [self moveToBeginningOfLineAndModifySelection:sender] : [self moveToEndOfLineAndModifySelection:sender]; } // Graphical Element transposition - (void)transpose:(id)sender { [self handleAction:ui::TextEditCommand::TRANSPOSE keyCode:ui::VKEY_T domCode:ui::DomCode::US_T eventFlags:ui::EF_CONTROL_DOWN]; } // Deletions. - (void)deleteForward:(id)sender { [self handleAction:ui::TextEditCommand::DELETE_FORWARD keyCode:ui::VKEY_DELETE domCode:ui::DomCode::DEL eventFlags:0]; } - (void)deleteBackward:(id)sender { [self handleAction:ui::TextEditCommand::DELETE_BACKWARD keyCode:ui::VKEY_BACK domCode:ui::DomCode::BACKSPACE eventFlags:0]; } - (void)deleteWordForward:(id)sender { [self handleAction:ui::TextEditCommand::DELETE_WORD_FORWARD keyCode:ui::VKEY_DELETE domCode:ui::DomCode::DEL eventFlags:ui::EF_CONTROL_DOWN]; } - (void)deleteWordBackward:(id)sender { [self handleAction:ui::TextEditCommand::DELETE_WORD_BACKWARD keyCode:ui::VKEY_BACK domCode:ui::DomCode::BACKSPACE eventFlags:ui::EF_CONTROL_DOWN]; } - (void)deleteToBeginningOfLine:(id)sender { [self handleAction:ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE keyCode:ui::VKEY_BACK domCode:ui::DomCode::BACKSPACE eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)deleteToEndOfLine:(id)sender { [self handleAction:ui::TextEditCommand::DELETE_TO_END_OF_LINE keyCode:ui::VKEY_DELETE domCode:ui::DomCode::DEL eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; } - (void)deleteToBeginningOfParagraph:(id)sender { [self handleAction:ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)deleteToEndOfParagraph:(id)sender { [self handleAction:ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH keyCode:ui::VKEY_UNKNOWN domCode:ui::DomCode::NONE eventFlags:0]; } - (void)yank:(id)sender { [self handleAction:ui::TextEditCommand::YANK keyCode:ui::VKEY_Y domCode:ui::DomCode::US_Y eventFlags:ui::EF_CONTROL_DOWN]; } // Cancellation. - (void)cancelOperation:(id)sender { [self handleAction:ui::TextEditCommand::INVALID_COMMAND keyCode:ui::VKEY_ESCAPE domCode:ui::DomCode::ESCAPE eventFlags:0]; } // Support for Services in context menus. // Currently we only support reading and writing plain strings. - (id)validRequestorForSendType:(NSString*)sendType returnType:(NSString*)returnType { NSString* const utf8Type = base::mac::CFToNSCast(kUTTypeUTF8PlainText); BOOL canWrite = [sendType isEqualToString:utf8Type] && [self selectedRange].length > 0; BOOL canRead = [returnType isEqualToString:utf8Type]; // Valid if (sendType, returnType) is either (string, nil), (nil, string), // or (string, string). BOOL valid = [self hasTextInputClient] && ((canWrite && (canRead || !returnType)) || (canRead && (canWrite || !sendType))); return valid ? self : [super validRequestorForSendType:sendType returnType:returnType]; } // NSServicesMenuRequestor protocol - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard types:(NSArray*)types { // NB: The NSServicesMenuRequestor protocol has not (as of 10.14) been // upgraded to request UTIs rather than obsolete PboardType constants. Handle // either for when it is upgraded. DCHECK([types containsObject:NSStringPboardType] || [types containsObject:base::mac::CFToNSCast(kUTTypeUTF8PlainText)]); bool result = NO; base::string16 text; if (_bridge) _bridge->text_input_host()->GetSelectionText(&result, &text); if (!result) return NO; return [pboard writeObjects:@[ base::SysUTF16ToNSString(text) ]]; } - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard { NSArray* objects = [pboard readObjectsForClasses:@ [[NSString class]] options:0]; DCHECK([objects count] == 1); [self insertText:[objects lastObject]]; return YES; } // NSTextInputClient protocol implementation. // IMPORTANT: Always null-check |[self textInputClient]|. It can change (or be // cleared) in -setTextInputClient:, which requires informing AppKit that the // -inputContext has changed and to update its raw pointer. However, the AppKit // method which does that may also spin a nested run loop communicating with an // IME window and cause it to *use* the exact same NSTextInputClient (i.e., // |self|) that we're trying to invalidate in -setTextInputClient:. // See https://crbug.com/817097#c12 for further details on this atrocity. - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range actualRange: (NSRangePointer)actualRange { // On TouchBar Macs, the IME subsystem sometimes sends an invalid range with a // non-zero length. This will cause a DCHECK in gfx::Range, so repair it here. // See https://crbug.com/888782. if (range.location == NSNotFound) range.length = 0; base::string16 substring; gfx::Range actual_range = gfx::Range::InvalidRange(); if (_bridge) { _bridge->text_input_host()->GetAttributedSubstringForRange( gfx::Range(range), &substring, &actual_range); } if (actualRange) { // To maintain consistency with NSTextView, return range {0,0} for an out of // bounds requested range. *actualRange = actual_range.IsValid() ? actual_range.ToNSRange() : NSMakeRange(0, 0); } return substring.empty() ? nil : [[[NSAttributedString alloc] initWithString:base::SysUTF16ToNSString(substring)] autorelease]; } - (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { NOTIMPLEMENTED(); return 0; } - (void)doCommandBySelector:(SEL)selector { // Like the renderer, handle insert action messages as a regular key dispatch. // This ensures, e.g., insertTab correctly changes focus between fields. This // handles: // -insertTab:(id)sender // -insertBacktab: // -insertNewline: // -insertParagraphSeparator: // -insertNewlineIgnoringFieldEditor: // -insertTabIgnoringFieldEditor: // -insertLineBreak: // -insertContainerBreak: // -insertSingleQuoteIgnoringSubstitution: // -insertDoubleQuoteIgnoringSubstitution: // It does not handle |-insertText:(id)insertString|, which is not a command. // I.e. AppKit invokes _either_ -insertText: or -doCommandBySelector:. Also // note -insertText: is only invoked if -inputContext: has returned nil. DCHECK_NE(@selector(insertText:), selector); if (_keyDownEvent && [NSStringFromSelector(selector) hasPrefix:@"insert"]) { // When return is pressed during IME composition, engines typically first // confirm the composition with a series of -insertText:replacementRange: // calls. Then, some also invoke -insertNewLine: (some do not). If an engine // DOES invokes -insertNewLine:, we always want a corresponding VKEY_RETURN // ui::KeyEvent generated. If it does NOT follow with -insertNewLine:, the // VKEY_RETURN must be suppressed in keyDown:, since it typically will have // merely dismissed the IME window: the composition is still ongoing. // Setting this ensures keyDown: always generates a ui::KeyEvent. _wantsKeyHandledForInsert = YES; return; // Handle in -keyDown:. } if ([self respondsToSelector:selector]) { [self performSelector:selector withObject:nil]; _hasUnhandledKeyDownEvent = NO; return; } // For events that AppKit sends via doCommandBySelector:, first attempt to // handle as a Widget accelerator. Forward along the responder chain only if // the Widget doesn't handle it. if (![self handleUnhandledKeyDownAsKeyEvent]) [[self nextResponder] doCommandBySelector:selector]; } - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualNSRange { gfx::Rect rect; gfx::Range actualRange = gfx::Range::InvalidRange(); if (_bridge) { _bridge->text_input_host()->GetFirstRectForRange(gfx::Range(range), &rect, &actualRange); } if (actualNSRange) *actualNSRange = actualRange.ToNSRange(); return gfx::ScreenRectToNSRect(rect); } - (BOOL)hasMarkedText { bool hasCompositionText = NO; if (_bridge) _bridge->text_input_host()->HasCompositionText(&hasCompositionText); return hasCompositionText; } - (void)insertText:(id)text replacementRange:(NSRange)replacementRange { if (!_bridge) return; _bridge->text_input_host()->DeleteRange(gfx::Range(replacementRange)); [self insertTextInternal:text]; } - (NSRange)markedRange { gfx::Range range = gfx::Range::InvalidRange(); if (_bridge) _bridge->text_input_host()->GetCompositionTextRange(&range); return range.ToNSRange(); } - (NSRange)selectedRange { gfx::Range range = gfx::Range::InvalidRange(); if (_bridge) _bridge->text_input_host()->GetSelectionRange(&range); return range.ToNSRange(); } - (void)setMarkedText:(id)text selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { if (![self hasTextInputClient]) return; if ([text isKindOfClass:[NSAttributedString class]]) text = [text string]; _bridge->text_input_host()->SetCompositionText(base::SysNSStringToUTF16(text), gfx::Range(selectedRange), gfx::Range(replacementRange)); _hasUnhandledKeyDownEvent = NO; } - (void)unmarkText { if (![self hasTextInputClient]) return; _bridge->text_input_host()->ConfirmCompositionText(); _hasUnhandledKeyDownEvent = NO; } - (NSArray*)validAttributesForMarkedText { return @[]; } // NSUserInterfaceValidations protocol implementation. - (BOOL)validateUserInterfaceItem:(id)item { ui::TextEditCommand command = GetTextEditCommandForMenuAction([item action]); if (command == ui::TextEditCommand::INVALID_COMMAND) return NO; // TODO(https://crbug.com/901490): Add mojo support for ui::TextEditCommand. if ([self textInputClient]) return [self textInputClient] -> IsTextEditCommandEnabled(command); // views::Label does not implement the TextInputClient interface but still // needs to intercept the Copy and Select All menu actions. if (command != ui::TextEditCommand::COPY && command != ui::TextEditCommand::SELECT_ALL) return NO; bool is_textual = false; _bridge->host()->GetIsFocusedViewTextual(&is_textual); return is_textual; } // NSDraggingSource protocol implementation. - (NSDragOperation)draggingSession:(NSDraggingSession*)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context { return NSDragOperationEvery; } - (void)draggingSession:(NSDraggingSession*)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation { remote_cocoa::DragDropClient* client = [self dragDropClient]; if (client) client->EndDrag(); } // NSAccessibility formal protocol implementation: - (NSArray*)accessibilityChildren { if (id accessible = _bridge->host_helper()->GetNativeViewAccessible()) return @[ accessible ]; return [super accessibilityChildren]; } // NSAccessibility informal protocol implementation: - (id)accessibilityHitTest:(NSPoint)point { return [_bridge->host_helper()->GetNativeViewAccessible() accessibilityHitTest:point]; } - (id)accessibilityFocusedUIElement { // This function should almost-never be called because when |self| is the // first responder for the key NSWindow, NativeWidgetMacNSWindowHost's // AccessibilityFocusOverrider will override the accessibility focus query. if (!_bridge) return nil; return [_bridge->host_helper()->GetNativeViewAccessible() accessibilityFocusedUIElement]; } @end