[M120 Migration]Fix for crash during chrome exit
[platform/framework/web/chromium-efl.git] / chrome / browser / global_keyboard_shortcuts_mac.mm
1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/global_keyboard_shortcuts_mac.h"
6
7 #import <AppKit/AppKit.h>
8 #include <Carbon/Carbon.h>
9
10 #include "base/apple/foundation_util.h"
11 #include "base/check.h"
12 #include "base/feature_list.h"
13 #include "base/no_destructor.h"
14 #include "build/buildflag.h"
15 #include "chrome/app/chrome_command_ids.h"
16 #import "chrome/browser/app_controller_mac.h"
17 #include "chrome/browser/ui/cocoa/accelerators_cocoa.h"
18 #include "chrome/browser/ui/ui_features.h"
19 #include "ui/base/accelerators/accelerator.h"
20 #include "ui/base/accelerators/platform_accelerator_cocoa.h"
21 #import "ui/base/cocoa/nsmenu_additions.h"
22 #import "ui/base/cocoa/nsmenuitem_additions.h"
23 #include "ui/base/ui_base_features.h"
24 #include "ui/events/event_constants.h"
25 #include "ui/events/keycodes/keyboard_code_conversion_mac.h"
26
27 namespace {
28
29 // Returns a ui::Accelerator given a KeyboardShortcutData.
30 ui::Accelerator AcceleratorFromShortcut(const KeyboardShortcutData& shortcut) {
31   int modifiers = 0;
32   if (shortcut.command_key)
33     modifiers |= ui::EF_COMMAND_DOWN;
34   if (shortcut.shift_key)
35     modifiers |= ui::EF_SHIFT_DOWN;
36   if (shortcut.cntrl_key)
37     modifiers |= ui::EF_CONTROL_DOWN;
38   if (shortcut.opt_key)
39     modifiers |= ui::EF_ALT_DOWN;
40
41   return ui::Accelerator(ui::KeyboardCodeFromKeyCode(shortcut.vkey_code),
42                          modifiers);
43 }
44
45 int MenuCommandForKeyEvent(NSEvent* event) {
46   NSMenuItem* item = [[NSApp mainMenu] cr_menuItemForKeyEquivalentEvent:event];
47
48   if (!item)
49     return NO_COMMAND;
50
51   if ([item action] == @selector(commandDispatch:) && [item tag] > 0)
52     return [item tag];
53
54   // "Close window", "Quit", and other commands don't use the `commandDispatch:`
55   // mechanism. Menu items that do not correspond to IDC_ constants need no
56   // special treatment however, as they can't be reserved in
57   // |BrowserCommandController::IsReservedCommandOrKey()| anyhow.
58   SEL itemAction = [item action];
59
60   if (itemAction == @selector(performClose:))
61     return IDC_CLOSE_WINDOW;
62
63   if (itemAction == @selector(terminate:))
64     return IDC_EXIT;
65
66   return NO_COMMAND;
67 }
68
69 bool MatchesEventForKeyboardShortcut(const KeyboardShortcutData& shortcut,
70                                      bool command_key,
71                                      bool shift_key,
72                                      bool cntrl_key,
73                                      bool opt_key,
74                                      int vkey_code) {
75   return shortcut.command_key == command_key &&
76          shortcut.shift_key == shift_key && shortcut.cntrl_key == cntrl_key &&
77          shortcut.opt_key == opt_key && shortcut.vkey_code == vkey_code;
78 }
79
80 const std::vector<KeyboardShortcutData>&
81 GetDelayedShortcutsNotPresentInMainMenu() {
82   // clang-format off
83   static base::NoDestructor<std::vector<KeyboardShortcutData>> keys({
84     // cmd    shift  cntrl  option vkeycode               command
85     // ---    -----  -----  ------ --------               -------
86       {true,  false, false, false, kVK_LeftArrow,         IDC_BACK},
87       {true,  false, false, false, kVK_RightArrow,        IDC_FORWARD},
88   });
89   // clang-format on
90   return *keys;
91 }
92
93 CommandForKeyEventResult NoCommand() {
94   return {NO_COMMAND, /*from_main_menu=*/false};
95 }
96
97 CommandForKeyEventResult MainMenuCommand(int cmd) {
98   return {cmd, /*from_main_menu=*/true};
99 }
100
101 CommandForKeyEventResult ShortcutCommand(int cmd) {
102   return {cmd, /*from_main_menu=*/false};
103 }
104
105 }  // namespace
106
107 // Returns a vector of hidden keyboard shortcuts (i.e. ones that arent present
108 // in the menus). Note that the hidden "Cmd =" shortcut is somehow enabled by
109 // the ui::VKEY_OEM_PLUS entry in accelerators_cocoa.mm.
110 const std::vector<KeyboardShortcutData>& GetShortcutsNotPresentInMainMenu() {
111   static const base::NoDestructor<std::vector<KeyboardShortcutData>> keys([]() {
112     // clang-format off
113     std::vector<KeyboardShortcutData> keys({
114     // cmd    shift  cntrl  option vkeycode               command
115     // ---    -----  -----  ------ --------               -------
116       {true,  true,  false, false, kVK_ANSI_RightBracket, IDC_SELECT_NEXT_TAB},
117       {true,  true,  false, false, kVK_ANSI_LeftBracket,  IDC_SELECT_PREVIOUS_TAB},
118       {false, false, true,  false, kVK_PageDown,          IDC_SELECT_NEXT_TAB},
119       {false, false, true,  false, kVK_PageUp,            IDC_SELECT_PREVIOUS_TAB},
120       {true,  false, false, true,  kVK_RightArrow,        IDC_SELECT_NEXT_TAB},
121       {true,  false, false, true,  kVK_LeftArrow,         IDC_SELECT_PREVIOUS_TAB},
122       {false, true,  true,  false, kVK_PageDown,          IDC_MOVE_TAB_NEXT},
123       {false, true,  true,  false, kVK_PageUp,            IDC_MOVE_TAB_PREVIOUS},
124
125       // Cmd-0..8 select the nth tab, with cmd-9 being "last tab".
126       {true,  false, false, false, kVK_ANSI_1,            IDC_SELECT_TAB_0},
127       {true,  false, false, false, kVK_ANSI_Keypad1,      IDC_SELECT_TAB_0},
128       {true,  false, false, false, kVK_ANSI_2,            IDC_SELECT_TAB_1},
129       {true,  false, false, false, kVK_ANSI_Keypad2,      IDC_SELECT_TAB_1},
130       {true,  false, false, false, kVK_ANSI_3,            IDC_SELECT_TAB_2},
131       {true,  false, false, false, kVK_ANSI_Keypad3,      IDC_SELECT_TAB_2},
132       {true,  false, false, false, kVK_ANSI_4,            IDC_SELECT_TAB_3},
133       {true,  false, false, false, kVK_ANSI_Keypad4,      IDC_SELECT_TAB_3},
134       {true,  false, false, false, kVK_ANSI_5,            IDC_SELECT_TAB_4},
135       {true,  false, false, false, kVK_ANSI_Keypad5,      IDC_SELECT_TAB_4},
136       {true,  false, false, false, kVK_ANSI_6,            IDC_SELECT_TAB_5},
137       {true,  false, false, false, kVK_ANSI_Keypad6,      IDC_SELECT_TAB_5},
138       {true,  false, false, false, kVK_ANSI_7,            IDC_SELECT_TAB_6},
139       {true,  false, false, false, kVK_ANSI_Keypad7,      IDC_SELECT_TAB_6},
140       {true,  false, false, false, kVK_ANSI_8,            IDC_SELECT_TAB_7},
141       {true,  false, false, false, kVK_ANSI_Keypad8,      IDC_SELECT_TAB_7},
142       {true,  false, false, false, kVK_ANSI_9,            IDC_SELECT_LAST_TAB},
143       {true,  false, false, false, kVK_ANSI_Keypad9,      IDC_SELECT_LAST_TAB},
144
145       {true,  true,  false, false, kVK_ANSI_M,            IDC_SHOW_AVATAR_MENU},
146       {true,  false, false, true,  kVK_ANSI_L,            IDC_SHOW_DOWNLOADS},
147       {true,  true,  false, false, kVK_ANSI_C,            IDC_DEV_TOOLS_INSPECT},
148       {true,  false, false, true,  kVK_ANSI_C,            IDC_DEV_TOOLS_INSPECT},
149       {true,  false, false, true,  kVK_DownArrow,         IDC_FOCUS_NEXT_PANE},
150       {true,  false, false, true,  kVK_UpArrow,           IDC_FOCUS_PREVIOUS_PANE},
151       {true,  true,  false, true,  kVK_ANSI_A,            IDC_FOCUS_INACTIVE_POPUP_FOR_ACCESSIBILITY},
152     });
153     // clang-format on
154
155     if (base::FeatureList::IsEnabled(features::kUIDebugTools)) {
156       keys.push_back(
157           {false, true, true, true, kVK_ANSI_T, IDC_DEBUG_TOGGLE_TABLET_MODE});
158       keys.push_back(
159           {false, true, true, true, kVK_ANSI_V, IDC_DEBUG_PRINT_VIEW_TREE});
160       keys.push_back({false, true, true, true, kVK_ANSI_M,
161                       IDC_DEBUG_PRINT_VIEW_TREE_DETAILS});
162     }
163     return keys;
164   }());
165   return *keys;
166 }
167
168 const std::vector<NSMenuItem*>& GetMenuItemsNotPresentInMainMenu() {
169   static base::NoDestructor<std::vector<NSMenuItem*>> menu_items([]() {
170     std::vector<NSMenuItem*> menu_items;
171     for (const auto& shortcut : GetShortcutsNotPresentInMainMenu()) {
172       ui::Accelerator accelerator = AcceleratorFromShortcut(shortcut);
173       KeyEquivalentAndModifierMask* equivalent =
174           ui::GetKeyEquivalentAndModifierMaskFromAccelerator(accelerator);
175
176       // Intentionally leaked!
177       NSMenuItem* item =
178           [[NSMenuItem alloc] initWithTitle:@""
179                                      action:nullptr
180                               keyEquivalent:equivalent.keyEquivalent];
181       item.keyEquivalentModifierMask = equivalent.modifierMask;
182
183       // We store the command in the tag.
184       item.tag = shortcut.chrome_command;
185       menu_items.push_back(item);
186     }
187     return menu_items;
188   }());
189   return *menu_items;
190 }
191
192 CommandForKeyEventResult CommandForKeyEvent(NSEvent* event) {
193   DCHECK(event);
194   if ([event type] != NSEventTypeKeyDown)
195     return NoCommand();
196
197   int cmdNum = MenuCommandForKeyEvent(event);
198   if (cmdNum != NO_COMMAND)
199     return MainMenuCommand(cmdNum);
200
201   // Scan through keycodes and see if it corresponds to one of the non-menu
202   // shortcuts.
203   for (NSMenuItem* menu_item : GetMenuItemsNotPresentInMainMenu()) {
204     if ([menu_item cr_firesForKeyEquivalentEvent:event])
205       return ShortcutCommand(menu_item.tag);
206   }
207
208   return NoCommand();
209 }
210
211 int DelayedWebContentsCommandForKeyEvent(NSEvent* event) {
212   DCHECK(event);
213   if ([event type] != NSEventTypeKeyDown)
214     return NO_COMMAND;
215
216   // Look in secondary keyboard shortcuts.
217   NSUInteger modifiers = [event modifierFlags];
218   const bool cmdKey = (modifiers & NSEventModifierFlagCommand) != 0;
219   const bool shiftKey = (modifiers & NSEventModifierFlagShift) != 0;
220   const bool cntrlKey = (modifiers & NSEventModifierFlagControl) != 0;
221   const bool optKey = (modifiers & NSEventModifierFlagOption) != 0;
222   const int keyCode = [event keyCode];
223
224   // Scan through keycodes and see if it corresponds to one of the non-menu
225   // shortcuts.
226   for (const auto& shortcut : GetDelayedShortcutsNotPresentInMainMenu()) {
227     if (MatchesEventForKeyboardShortcut(shortcut, cmdKey, shiftKey, cntrlKey,
228                                         optKey, keyCode)) {
229       return shortcut.chrome_command;
230     }
231   }
232
233   return NO_COMMAND;
234 }
235
236 // AppKit sends an event via performKeyEquivalent: if it has at least one of the
237 // command or control modifiers, and is an NSEventTypeKeyDown event.
238 // CommandDispatcher supplements this by also sending event with the option
239 // modifier to performKeyEquivalent:.
240 bool EventUsesPerformKeyEquivalent(NSEvent* event) {
241   return ([event modifierFlags] & ui::cocoa::ModifierMaskForKeyEvent(event)) !=
242          0;
243 }
244
245 bool GetDefaultMacAcceleratorForCommandId(int command_id,
246                                           ui::Accelerator* accelerator) {
247   // See if it corresponds to one of the non-menu shortcuts.
248   for (const auto& shortcut : GetShortcutsNotPresentInMainMenu()) {
249     if (shortcut.chrome_command == command_id) {
250       *accelerator = AcceleratorFromShortcut(shortcut);
251       return true;
252     }
253   }
254
255   // See if it corresponds to one of the default NSMenu keyEquivalents.
256   const ui::Accelerator* default_nsmenu_equivalent =
257       AcceleratorsCocoa::GetInstance()->GetAcceleratorForCommand(command_id);
258   if (default_nsmenu_equivalent)
259     *accelerator = *default_nsmenu_equivalent;
260   return default_nsmenu_equivalent != nullptr;
261 }