native_widget_mac_nswindow.mm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  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. #import "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
  5. #include "base/auto_reset.h"
  6. #include "base/debug/dump_without_crashing.h"
  7. #include "base/mac/foundation_util.h"
  8. #include "base/mac/mac_util.h"
  9. #include "base/trace_event/trace_event.h"
  10. #import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
  11. #include "components/remote_cocoa/app_shim/native_widget_ns_window_host_helper.h"
  12. #import "components/remote_cocoa/app_shim/views_nswindow_delegate.h"
  13. #import "components/remote_cocoa/app_shim/window_touch_bar_delegate.h"
  14. #include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h"
  15. #import "ui/base/cocoa/user_interface_item_command_handler.h"
  16. #import "ui/base/cocoa/window_size_constants.h"
  17. namespace {
  18. // AppKit quirk: -[NSWindow orderWindow] does not handle reordering for children
  19. // windows. Their order is fixed to the attachment order (the last attached
  20. // window is on the top). Therefore, work around it by re-parenting in our
  21. // desired order.
  22. void OrderChildWindow(NSWindow* child_window,
  23. NSWindow* other_window,
  24. NSWindowOrderingMode ordering_mode) {
  25. NSWindow* parent = [child_window parentWindow];
  26. DCHECK(parent);
  27. // `ordered_children` sorts children windows back to front.
  28. NSArray<NSWindow*>* children = [[child_window parentWindow] childWindows];
  29. std::vector<std::pair<NSInteger, NSWindow*>> ordered_children;
  30. for (NSWindow* child in children)
  31. ordered_children.push_back({[child orderedIndex], child});
  32. std::sort(ordered_children.begin(), ordered_children.end(), std::greater<>());
  33. // If `other_window` is nullptr, place `child_window` in front of (or behind)
  34. // all other children windows.
  35. if (other_window == nullptr) {
  36. other_window = ordering_mode == NSWindowAbove
  37. ? ordered_children.back().second
  38. : parent;
  39. }
  40. if (child_window == other_window)
  41. return;
  42. const bool relative_to_parent = parent == other_window;
  43. DCHECK(ordering_mode != NSWindowBelow || !relative_to_parent)
  44. << "Placing a child window behind its parent is not supported.";
  45. for (NSWindow* child in children)
  46. [parent removeChildWindow:child];
  47. // If `relative_to_parent` is true, `child_window` is the first child of its
  48. // parent.
  49. if (relative_to_parent)
  50. [parent addChildWindow:child_window ordered:NSWindowAbove];
  51. // Re-parent children windows in the desired order.
  52. for (auto [ordered_index, child] : ordered_children) {
  53. if (child != child_window && child != other_window) {
  54. [parent addChildWindow:child ordered:NSWindowAbove];
  55. } else if (child == other_window && !relative_to_parent) {
  56. if (ordering_mode == NSWindowAbove) {
  57. [parent addChildWindow:other_window ordered:NSWindowAbove];
  58. [parent addChildWindow:child_window ordered:NSWindowAbove];
  59. } else {
  60. [parent addChildWindow:child_window ordered:NSWindowAbove];
  61. [parent addChildWindow:other_window ordered:NSWindowAbove];
  62. }
  63. }
  64. }
  65. }
  66. } // namespace
  67. @interface NSWindow (Private)
  68. + (Class)frameViewClassForStyleMask:(NSWindowStyleMask)windowStyle;
  69. - (BOOL)hasKeyAppearance;
  70. - (long long)_resizeDirectionForMouseLocation:(CGPoint)location;
  71. - (BOOL)_isConsideredOpenForPersistentState;
  72. @end
  73. @interface NativeWidgetMacNSWindow () <NSKeyedArchiverDelegate>
  74. - (ViewsNSWindowDelegate*)viewsNSWindowDelegate;
  75. - (BOOL)hasViewsMenuActive;
  76. - (id<NSAccessibility>)rootAccessibilityObject;
  77. // Private API on NSWindow, determines whether the title is drawn on the title
  78. // bar. The title is still visible in menus, Expose, etc.
  79. - (BOOL)_isTitleHidden;
  80. @end
  81. // Use this category to implement mouseDown: on multiple frame view classes
  82. // with different superclasses.
  83. @interface NSView (CRFrameViewAdditions)
  84. - (void)cr_mouseDownOnFrameView:(NSEvent*)event;
  85. @end
  86. @implementation NSView (CRFrameViewAdditions)
  87. // If a mouseDown: falls through to the frame view, turn it into a window drag.
  88. - (void)cr_mouseDownOnFrameView:(NSEvent*)event {
  89. if ([self.window _resizeDirectionForMouseLocation:event.locationInWindow] !=
  90. -1)
  91. return;
  92. [self.window performWindowDragWithEvent:event];
  93. }
  94. @end
  95. @implementation NativeWidgetMacNSWindowTitledFrame
  96. - (void)mouseDown:(NSEvent*)event {
  97. if (self.window.isMovable)
  98. [self cr_mouseDownOnFrameView:event];
  99. [super mouseDown:event];
  100. }
  101. - (BOOL)usesCustomDrawing {
  102. return NO;
  103. }
  104. // The base implementation just tests [self class] == [NSThemeFrame class].
  105. - (BOOL)_shouldFlipTrafficLightsForRTL {
  106. return [[self window] windowTitlebarLayoutDirection] ==
  107. NSUserInterfaceLayoutDirectionRightToLeft;
  108. }
  109. @end
  110. @implementation NativeWidgetMacNSWindowBorderlessFrame
  111. - (void)mouseDown:(NSEvent*)event {
  112. [self cr_mouseDownOnFrameView:event];
  113. [super mouseDown:event];
  114. }
  115. - (BOOL)usesCustomDrawing {
  116. return NO;
  117. }
  118. @end
  119. @implementation NativeWidgetMacNSWindow {
  120. @private
  121. base::scoped_nsobject<CommandDispatcher> _commandDispatcher;
  122. base::scoped_nsprotocol<id<UserInterfaceItemCommandHandler>> _commandHandler;
  123. id<WindowTouchBarDelegate> _touchBarDelegate; // Weak.
  124. uint64_t _bridgedNativeWidgetId;
  125. remote_cocoa::NativeWidgetNSWindowBridge* _bridge;
  126. BOOL _willUpdateRestorableState;
  127. BOOL _isEnforcingNeverMadeVisible;
  128. BOOL _preventKeyWindow;
  129. BOOL _isTooltip;
  130. }
  131. @synthesize bridgedNativeWidgetId = _bridgedNativeWidgetId;
  132. @synthesize bridge = _bridge;
  133. @synthesize isTooltip = _isTooltip;
  134. - (instancetype)initWithContentRect:(NSRect)contentRect
  135. styleMask:(NSUInteger)windowStyle
  136. backing:(NSBackingStoreType)bufferingType
  137. defer:(BOOL)deferCreation {
  138. DCHECK(NSEqualRects(contentRect, ui::kWindowSizeDeterminedLater));
  139. if ((self = [super initWithContentRect:ui::kWindowSizeDeterminedLater
  140. styleMask:windowStyle
  141. backing:bufferingType
  142. defer:deferCreation])) {
  143. _commandDispatcher.reset([[CommandDispatcher alloc] initWithOwner:self]);
  144. }
  145. return self;
  146. }
  147. // This override helps diagnose lifetime issues in crash stacktraces by
  148. // inserting a symbol on NativeWidgetMacNSWindow and should be kept even if it
  149. // does nothing.
  150. - (void)dealloc {
  151. if (_isEnforcingNeverMadeVisible) {
  152. [self removeObserver:self forKeyPath:@"visible"];
  153. }
  154. _willUpdateRestorableState = YES;
  155. [NSObject cancelPreviousPerformRequestsWithTarget:self];
  156. [super dealloc];
  157. }
  158. - (void)addChildWindow:(NSWindow*)childWin ordered:(NSWindowOrderingMode)place {
  159. // Attaching a window to be a child window resets the window level, so
  160. // restore the window level afterwards.
  161. NSInteger level = childWin.level;
  162. [super addChildWindow:childWin ordered:place];
  163. childWin.level = level;
  164. }
  165. - (void)enforceNeverMadeVisible {
  166. if (_isEnforcingNeverMadeVisible)
  167. return;
  168. _isEnforcingNeverMadeVisible = YES;
  169. [self addObserver:self
  170. forKeyPath:@"visible"
  171. options:NSKeyValueObservingOptionNew
  172. context:nil];
  173. }
  174. - (void)observeValueForKeyPath:(NSString*)keyPath
  175. ofObject:(id)object
  176. change:(NSDictionary*)change
  177. context:(void*)context {
  178. if ([keyPath isEqual:@"visible"]) {
  179. DCHECK(_isEnforcingNeverMadeVisible);
  180. DCHECK_EQ(object, self);
  181. DCHECK_EQ(context, nil);
  182. if ([change[NSKeyValueChangeNewKey] boolValue])
  183. base::debug::DumpWithoutCrashing();
  184. }
  185. [super observeValueForKeyPath:keyPath
  186. ofObject:object
  187. change:change
  188. context:context];
  189. }
  190. // Public methods.
  191. - (void)setCommandDispatcherDelegate:(id<CommandDispatcherDelegate>)delegate {
  192. [_commandDispatcher setDelegate:delegate];
  193. }
  194. - (void)sheetDidEnd:(NSWindow*)sheet
  195. returnCode:(NSInteger)returnCode
  196. contextInfo:(void*)contextInfo {
  197. // Note NativeWidgetNSWindowBridge may have cleared [self delegate], in which
  198. // case this will no-op. This indirection is necessary to handle AppKit
  199. // invoking this selector via a posted task. See https://crbug.com/851376.
  200. [[self viewsNSWindowDelegate] sheetDidEnd:sheet
  201. returnCode:returnCode
  202. contextInfo:contextInfo];
  203. }
  204. - (void)setWindowTouchBarDelegate:(id<WindowTouchBarDelegate>)delegate {
  205. _touchBarDelegate = delegate;
  206. }
  207. - (void)orderFrontKeepWindowKeyState {
  208. if ([self isOnActiveSpace]) {
  209. [self orderWindow:NSWindowAbove relativeTo:0];
  210. return;
  211. }
  212. // The OS will activate the window if it causes a space switch.
  213. // Temporarily prevent the window from becoming the key window until after
  214. // the space change completes.
  215. _preventKeyWindow = ![self isKeyWindow];
  216. NSNotificationCenter* notificationCenter =
  217. [[NSWorkspace sharedWorkspace] notificationCenter];
  218. __block id observer = [notificationCenter
  219. addObserverForName:NSWorkspaceActiveSpaceDidChangeNotification
  220. object:[NSWorkspace sharedWorkspace]
  221. queue:[NSOperationQueue mainQueue]
  222. usingBlock:^(NSNotification* notification) {
  223. _preventKeyWindow = NO;
  224. [notificationCenter removeObserver:observer];
  225. }];
  226. [self orderWindow:NSWindowAbove relativeTo:0];
  227. }
  228. // Private methods.
  229. - (ViewsNSWindowDelegate*)viewsNSWindowDelegate {
  230. return base::mac::ObjCCastStrict<ViewsNSWindowDelegate>([self delegate]);
  231. }
  232. - (BOOL)hasViewsMenuActive {
  233. bool hasMenuController = false;
  234. if (_bridge)
  235. _bridge->host()->GetHasMenuController(&hasMenuController);
  236. return hasMenuController;
  237. }
  238. - (id<NSAccessibility>)rootAccessibilityObject {
  239. id<NSAccessibility> obj =
  240. _bridge ? _bridge->host_helper()->GetNativeViewAccessible() : nil;
  241. // We should like to DCHECK that the object returned implemements the
  242. // NSAccessibility protocol, but the NSAccessibilityRemoteUIElement interface
  243. // does not conform.
  244. // TODO(https://crbug.com/944698): Create a sub-class that does.
  245. return obj;
  246. }
  247. - (NSAccessibilityRole)accessibilityRole {
  248. return _isTooltip ? NSAccessibilityHelpTagRole : [super accessibilityRole];
  249. }
  250. // NSWindow overrides.
  251. + (Class)frameViewClassForStyleMask:(NSWindowStyleMask)windowStyle {
  252. if (windowStyle & NSWindowStyleMaskTitled) {
  253. if (Class customFrame = [NativeWidgetMacNSWindowTitledFrame class])
  254. return customFrame;
  255. } else if (Class customFrame =
  256. [NativeWidgetMacNSWindowBorderlessFrame class]) {
  257. return customFrame;
  258. }
  259. return [super frameViewClassForStyleMask:windowStyle];
  260. }
  261. - (BOOL)_isTitleHidden {
  262. bool shouldShowWindowTitle = YES;
  263. if (_bridge)
  264. _bridge->host()->GetShouldShowWindowTitle(&shouldShowWindowTitle);
  265. return !shouldShowWindowTitle;
  266. }
  267. // The base implementation returns YES if the window's frame view is a custom
  268. // class, which causes undesirable changes in behavior. AppKit NSWindow
  269. // subclasses are known to override it and return NO.
  270. - (BOOL)_usesCustomDrawing {
  271. return NO;
  272. }
  273. // Ignore [super canBecome{Key,Main}Window]. The default is NO for windows with
  274. // NSWindowStyleMaskBorderless, which is not the desired behavior.
  275. // Note these can be called via -[NSWindow close] while the widget is being torn
  276. // down, so check for a delegate.
  277. - (BOOL)canBecomeKeyWindow {
  278. if (_preventKeyWindow)
  279. return NO;
  280. bool canBecomeKey = NO;
  281. if (_bridge)
  282. _bridge->host()->GetCanWindowBecomeKey(&canBecomeKey);
  283. return canBecomeKey;
  284. }
  285. - (BOOL)canBecomeMainWindow {
  286. if (!_bridge)
  287. return NO;
  288. // Dialogs and bubbles shouldn't take large shadows away from their parent.
  289. if (_bridge->parent())
  290. return NO;
  291. bool canBecomeKey = NO;
  292. if (_bridge)
  293. _bridge->host()->GetCanWindowBecomeKey(&canBecomeKey);
  294. return canBecomeKey;
  295. }
  296. // Lets the traffic light buttons on the parent window keep their active state.
  297. - (BOOL)hasKeyAppearance {
  298. // Note that this function is called off of the main thread. In such cases,
  299. // it is not safe to access the mojo interface or the ui::Widget, as they are
  300. // not reentrant.
  301. // https://crbug.com/941506.
  302. if (![NSThread isMainThread])
  303. return [super hasKeyAppearance];
  304. if (_bridge) {
  305. bool isAlwaysRenderWindowAsKey = NO;
  306. _bridge->host()->GetAlwaysRenderWindowAsKey(&isAlwaysRenderWindowAsKey);
  307. if (isAlwaysRenderWindowAsKey)
  308. return YES;
  309. }
  310. return [super hasKeyAppearance];
  311. }
  312. // Override sendEvent to intercept window drag events and allow key events to be
  313. // forwarded to a toolkit-views menu while it is active, and while still
  314. // allowing any native subview to retain firstResponder status.
  315. - (void)sendEvent:(NSEvent*)event {
  316. // TODO(bokan): Tracing added temporarily to diagnose crbug.com/1039833.
  317. TRACE_EVENT1("browser", "NSWindow::sendEvent", "WindowNum",
  318. [self windowNumber]);
  319. // Let CommandDispatcher check if this is a redispatched event.
  320. if ([_commandDispatcher preSendEvent:event]) {
  321. TRACE_EVENT_INSTANT0("browser", "StopSendEvent", TRACE_EVENT_SCOPE_THREAD);
  322. return;
  323. }
  324. NSEventType type = [event type];
  325. // Draggable regions only respond to left-click dragging, but the system will
  326. // still suppress right-clicks in a draggable region. Forwarding right-clicks
  327. // and ctrl+left-clicks allows the underlying views to respond to right-click
  328. // to potentially bring up a frame context menu.
  329. if (type == NSEventTypeRightMouseDown ||
  330. (type == NSEventTypeLeftMouseDown &&
  331. ([event modifierFlags] & NSEventModifierFlagControl))) {
  332. if ([[self contentView] hitTest:event.locationInWindow] == nil) {
  333. [[self contentView] rightMouseDown:event];
  334. return;
  335. }
  336. } else if (type == NSEventTypeRightMouseUp) {
  337. if ([[self contentView] hitTest:event.locationInWindow] == nil) {
  338. [[self contentView] rightMouseUp:event];
  339. return;
  340. }
  341. } else if ([self hasViewsMenuActive]) {
  342. // Send to the menu, after converting the event into an action message using
  343. // the content view.
  344. if (type == NSEventTypeKeyDown) {
  345. [[self contentView] keyDown:event];
  346. return;
  347. } else if (type == NSEventTypeKeyUp) {
  348. [[self contentView] keyUp:event];
  349. return;
  350. }
  351. }
  352. [super sendEvent:event];
  353. }
  354. - (void)reallyOrderWindow:(NSWindowOrderingMode)orderingMode
  355. relativeTo:(NSInteger)otherWindowNumber {
  356. NativeWidgetMacNSWindow* parent =
  357. static_cast<NativeWidgetMacNSWindow*>([self parentWindow]);
  358. // This is not a child window. No need to patch.
  359. if (!parent) {
  360. [self orderWindow:orderingMode relativeTo:otherWindowNumber];
  361. return;
  362. }
  363. // `otherWindow` is nil if `otherWindowNumber` is 0. In this case, place
  364. // `self` at the top / bottom, depending on `orderingMode`.
  365. NSWindow* otherWindow = [NSApp windowWithWindowNumber:otherWindowNumber];
  366. if (otherWindow == nullptr || parent == [otherWindow parentWindow] ||
  367. parent == otherWindow)
  368. OrderChildWindow(self, otherWindow, orderingMode);
  369. [[self viewsNSWindowDelegate] onWindowOrderChanged:nil];
  370. }
  371. // Override window order functions to intercept other visibility changes. This
  372. // is needed in addition to the -[NSWindow display] override because Cocoa
  373. // hardly ever calls display, and reports -[NSWindow isVisible] incorrectly
  374. // when ordering in a window for the first time.
  375. // Note that this methods has no effect for children windows. Use
  376. // -reallyOrderWindow:relativeTo: instead.
  377. - (void)orderWindow:(NSWindowOrderingMode)orderingMode
  378. relativeTo:(NSInteger)otherWindowNumber {
  379. [super orderWindow:orderingMode relativeTo:otherWindowNumber];
  380. [[self viewsNSWindowDelegate] onWindowOrderChanged:nil];
  381. }
  382. // NSResponder implementation.
  383. - (BOOL)performKeyEquivalent:(NSEvent*)event {
  384. // TODO(bokan): Tracing added temporarily to diagnose crbug.com/1039833.
  385. TRACE_EVENT1("browser", "NSWindow::performKeyEquivalent", "WindowNum",
  386. [self windowNumber]);
  387. return [_commandDispatcher performKeyEquivalent:event];
  388. }
  389. - (void)cursorUpdate:(NSEvent*)theEvent {
  390. // The cursor provided by the delegate should only be applied within the
  391. // content area. This is because we rely on the contentView to track the
  392. // mouse cursor and forward cursorUpdate: messages up the responder chain.
  393. // The cursorUpdate: isn't handled in BridgedContentView because views-style
  394. // SetCapture() conflicts with the way tracking events are processed for
  395. // the view during a drag. Since the NSWindow is still in the responder chain
  396. // overriding cursorUpdate: here handles both cases.
  397. if (!NSPointInRect([theEvent locationInWindow], [[self contentView] frame])) {
  398. [super cursorUpdate:theEvent];
  399. return;
  400. }
  401. NSCursor* cursor = [[self viewsNSWindowDelegate] cursor];
  402. if (cursor)
  403. [cursor set];
  404. else
  405. [super cursorUpdate:theEvent];
  406. }
  407. - (NSTouchBar*)makeTouchBar {
  408. return _touchBarDelegate ? [_touchBarDelegate makeTouchBar] : nil;
  409. }
  410. // Called when the window is the delegate of the archiver passed to
  411. // |-encodeRestorableStateWithCoder:|, below. It prevents the archiver from
  412. // trying to encode the window or an NSView, say, to represent the first
  413. // responder. When AppKit calls |-encodeRestorableStateWithCoder:|, it
  414. // accomplishes the same thing by passing a custom coder.
  415. - (id)archiver:(NSKeyedArchiver*)archiver willEncodeObject:(id)object {
  416. if (object == self)
  417. return nil;
  418. if ([object isKindOfClass:[NSView class]])
  419. return nil;
  420. return object;
  421. }
  422. - (void)saveRestorableState {
  423. if (!_bridge)
  424. return;
  425. if (![self _isConsideredOpenForPersistentState])
  426. return;
  427. // On macOS 12+, create restorable state archives with secure encoding. See
  428. // the article at
  429. // https://sector7.computest.nl/post/2022-08-process-injection-breaking-all-macos-security-layers-with-a-single-vulnerability/
  430. // for more details.
  431. base::scoped_nsobject<NSKeyedArchiver> encoder([[NSKeyedArchiver alloc]
  432. initRequiringSecureCoding:base::mac::IsAtLeastOS12()]);
  433. encoder.get().delegate = self;
  434. [self encodeRestorableStateWithCoder:encoder];
  435. [encoder finishEncoding];
  436. NSData* restorableStateData = encoder.get().encodedData;
  437. auto* bytes = static_cast<uint8_t const*>(restorableStateData.bytes);
  438. _bridge->host()->OnWindowStateRestorationDataChanged(
  439. std::vector<uint8_t>(bytes, bytes + restorableStateData.length));
  440. _willUpdateRestorableState = NO;
  441. }
  442. // AppKit calls -invalidateRestorableState when a property of the window which
  443. // affects its restorable state changes.
  444. - (void)invalidateRestorableState {
  445. [super invalidateRestorableState];
  446. if ([self _isConsideredOpenForPersistentState]) {
  447. if (_willUpdateRestorableState)
  448. return;
  449. _willUpdateRestorableState = YES;
  450. [self performSelectorOnMainThread:@selector(saveRestorableState)
  451. withObject:nil
  452. waitUntilDone:NO
  453. modes:@[ NSDefaultRunLoopMode ]];
  454. } else if (_willUpdateRestorableState) {
  455. _willUpdateRestorableState = NO;
  456. [NSObject cancelPreviousPerformRequestsWithTarget:self];
  457. }
  458. }
  459. // On newer SDKs, _canMiniaturize respects NSWindowStyleMaskMiniaturizable in
  460. // the window's styleMask. Views assumes that Widgets can always be minimized,
  461. // regardless of their window style, so override that behavior here.
  462. - (BOOL)_canMiniaturize {
  463. return YES;
  464. }
  465. - (BOOL)respondsToSelector:(SEL)aSelector {
  466. // If this window or its parent does not handle commands, remove it from the
  467. // chain.
  468. bool isCommandDispatch =
  469. aSelector == @selector(commandDispatch:) ||
  470. aSelector == @selector(commandDispatchUsingKeyModifiers:);
  471. if (isCommandDispatch && _commandHandler == nil &&
  472. [_commandDispatcher bubbleParent] == nil) {
  473. return NO;
  474. }
  475. return [super respondsToSelector:aSelector];
  476. }
  477. // CommandDispatchingWindow implementation.
  478. - (void)setCommandHandler:(id<UserInterfaceItemCommandHandler>)commandHandler {
  479. _commandHandler.reset([commandHandler retain]);
  480. }
  481. - (CommandDispatcher*)commandDispatcher {
  482. return _commandDispatcher.get();
  483. }
  484. - (BOOL)defaultPerformKeyEquivalent:(NSEvent*)event {
  485. // TODO(bokan): Tracing added temporarily to diagnose crbug.com/1039833.
  486. TRACE_EVENT1("browser", "NSWindow::defaultPerformKeyEquivalent", "WindowNum",
  487. [self windowNumber]);
  488. return [super performKeyEquivalent:event];
  489. }
  490. - (BOOL)defaultValidateUserInterfaceItem:
  491. (id<NSValidatedUserInterfaceItem>)item {
  492. return [super validateUserInterfaceItem:item];
  493. }
  494. - (void)commandDispatch:(id)sender {
  495. [_commandDispatcher dispatch:sender forHandler:_commandHandler];
  496. }
  497. - (void)commandDispatchUsingKeyModifiers:(id)sender {
  498. [_commandDispatcher dispatchUsingKeyModifiers:sender
  499. forHandler:_commandHandler];
  500. }
  501. // NSWindow overrides (NSUserInterfaceItemValidations implementation)
  502. - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
  503. return [_commandDispatcher validateUserInterfaceItem:item
  504. forHandler:_commandHandler];
  505. }
  506. // NSWindow overrides (NSAccessibility informal protocol implementation).
  507. - (id)accessibilityFocusedUIElement {
  508. if (![self delegate])
  509. return [super accessibilityFocusedUIElement];
  510. // The SDK documents this as "The deepest descendant of the accessibility
  511. // hierarchy that has the focus" and says "if a child element does not have
  512. // the focus, either return self or, if available, invoke the superclass's
  513. // implementation."
  514. // The behavior of NSWindow is usually to return null, except when the window
  515. // is first shown, when it returns self. But in the second case, we can
  516. // provide richer a11y information by reporting the views::RootView instead.
  517. // Additionally, if we don't do this, VoiceOver reads out the partial a11y
  518. // properties on the NSWindow and repeats them when focusing an item in the
  519. // RootView's a11y group. See http://crbug.com/748221.
  520. id superFocus = [super accessibilityFocusedUIElement];
  521. if (!_bridge || superFocus != self)
  522. return superFocus;
  523. return _bridge->host_helper()->GetNativeViewAccessible();
  524. }
  525. - (NSString*)accessibilityTitle {
  526. // Check when NSWindow is asked for its title to provide the title given by
  527. // the views::RootView (and WidgetDelegate::GetAccessibleWindowTitle()). For
  528. // all other attributes, use what NSWindow provides by default since diverging
  529. // from NSWindow's behavior can easily break VoiceOver integration.
  530. NSString* viewsValue = self.rootAccessibilityObject.accessibilityTitle;
  531. return viewsValue ? viewsValue : [super accessibilityTitle];
  532. }
  533. @end