Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / nsMenuUtilsX.mm
blob9a950221cd9f6a3dbc952705fec42849b2cfda54
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsMenuUtilsX.h"
7 #include <unordered_set>
9 #include "mozilla/EventForwards.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/DocumentInlines.h"
12 #include "mozilla/dom/Event.h"
13 #include "mozilla/dom/XULCommandEvent.h"
14 #include "nsMenuBarX.h"
15 #include "nsMenuX.h"
16 #include "nsMenuItemX.h"
17 #include "NativeMenuMac.h"
18 #include "nsObjCExceptions.h"
19 #include "nsCocoaUtils.h"
20 #include "nsCocoaWindow.h"
21 #include "nsGkAtoms.h"
22 #include "nsGlobalWindowInner.h"
23 #include "nsPIDOMWindow.h"
24 #include "nsQueryObject.h"
26 using namespace mozilla;
28 bool nsMenuUtilsX::gIsSynchronouslyActivatingNativeMenuItemDuringTest = false;
30 void nsMenuUtilsX::DispatchCommandTo(nsIContent* aTargetContent,
31                                      NSEventModifierFlags aModifierFlags,
32                                      int16_t aButton) {
33   MOZ_ASSERT(aTargetContent, "null ptr");
35   dom::Document* doc = aTargetContent->OwnerDoc();
36   if (doc) {
37     RefPtr<dom::XULCommandEvent> event =
38         new dom::XULCommandEvent(doc, doc->GetPresContext(), nullptr);
40     bool ctrlKey = aModifierFlags & NSEventModifierFlagControl;
41     bool altKey = aModifierFlags & NSEventModifierFlagOption;
42     bool shiftKey = aModifierFlags & NSEventModifierFlagShift;
43     bool cmdKey = aModifierFlags & NSEventModifierFlagCommand;
45     IgnoredErrorResult rv;
46     event->InitCommandEvent(u"command"_ns, true, true,
47                             nsGlobalWindowInner::Cast(doc->GetInnerWindow()), 0,
48                             ctrlKey, altKey, shiftKey, cmdKey, aButton, nullptr,
49                             0, rv);
50     if (!rv.Failed()) {
51       event->SetTrusted(true);
52       aTargetContent->DispatchEvent(*event);
53     }
54   }
57 NSString* nsMenuUtilsX::GetTruncatedCocoaLabel(const nsString& itemLabel) {
58   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
60   // We want to truncate long strings to some reasonable pixel length but there
61   // is no good API for doing that which works for all OS versions and
62   // architectures. For now we'll do nothing for consistency and depend on good
63   // user interface design to limit string lengths.
64   return [NSString
65       stringWithCharacters:reinterpret_cast<const unichar*>(itemLabel.get())
66                     length:itemLabel.Length()];
68   NS_OBJC_END_TRY_ABORT_BLOCK;
71 uint8_t nsMenuUtilsX::GeckoModifiersForNodeAttribute(
72     const nsString& modifiersAttribute) {
73   uint8_t modifiers = knsMenuItemNoModifier;
74   char* str = ToNewCString(modifiersAttribute);
75   char* newStr;
76   char* token = strtok_r(str, ", \t", &newStr);
77   while (token != nullptr) {
78     if (strcmp(token, "shift") == 0) {
79       modifiers |= knsMenuItemShiftModifier;
80     } else if (strcmp(token, "alt") == 0) {
81       modifiers |= knsMenuItemAltModifier;
82     } else if (strcmp(token, "control") == 0) {
83       modifiers |= knsMenuItemControlModifier;
84     } else if ((strcmp(token, "accel") == 0) || (strcmp(token, "meta") == 0)) {
85       modifiers |= knsMenuItemCommandModifier;
86     }
87     token = strtok_r(newStr, ", \t", &newStr);
88   }
89   free(str);
91   return modifiers;
94 unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(
95     uint8_t geckoModifiers) {
96   unsigned int macModifiers = 0;
98   if (geckoModifiers & knsMenuItemShiftModifier) {
99     macModifiers |= NSEventModifierFlagShift;
100   }
101   if (geckoModifiers & knsMenuItemAltModifier) {
102     macModifiers |= NSEventModifierFlagOption;
103   }
104   if (geckoModifiers & knsMenuItemControlModifier) {
105     macModifiers |= NSEventModifierFlagControl;
106   }
107   if (geckoModifiers & knsMenuItemCommandModifier) {
108     macModifiers |= NSEventModifierFlagCommand;
109   }
111   return macModifiers;
114 nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar() {
115   if (gfxPlatform::IsHeadless()) {
116     return nullptr;
117   }
118   nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget();
119   if (hiddenWindowWidgetNoCOMPtr) {
120     return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)
121         ->GetMenuBar();
122   }
123   return nullptr;
126 // It would be nice if we could localize these edit menu names.
127 NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem() {
128   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
130   // In principle we should be able to allocate this once and then always
131   // return the same object.  But weird interactions happen between native
132   // app-modal dialogs and Gecko-modal dialogs that open above them.  So what
133   // we return here isn't always released before it needs to be added to
134   // another menu.  See bmo bug 468393.
135   NSMenuItem* standardEditMenuItem =
136       [[[NSMenuItem alloc] initWithTitle:@"Edit" action:nil
137                            keyEquivalent:@""] autorelease];
138   NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
139   standardEditMenuItem.submenu = standardEditMenu;
140   [standardEditMenu release];
142   // Add Undo
143   NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo"
144                                                     action:@selector(undo:)
145                                              keyEquivalent:@"z"];
146   [standardEditMenu addItem:undoItem];
147   [undoItem release];
149   // Add Redo
150   NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo"
151                                                     action:@selector(redo:)
152                                              keyEquivalent:@"Z"];
153   [standardEditMenu addItem:redoItem];
154   [redoItem release];
156   // Add separator
157   [standardEditMenu addItem:[NSMenuItem separatorItem]];
159   // Add Cut
160   NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
161                                                    action:@selector(cut:)
162                                             keyEquivalent:@"x"];
163   [standardEditMenu addItem:cutItem];
164   [cutItem release];
166   // Add Copy
167   NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy"
168                                                     action:@selector(copy:)
169                                              keyEquivalent:@"c"];
170   [standardEditMenu addItem:copyItem];
171   [copyItem release];
173   // Add Paste
174   NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
175                                                      action:@selector(paste:)
176                                               keyEquivalent:@"v"];
177   [standardEditMenu addItem:pasteItem];
178   [pasteItem release];
180   // Add Delete
181   NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete"
182                                                       action:@selector(delete:)
183                                                keyEquivalent:@""];
184   [standardEditMenu addItem:deleteItem];
185   [deleteItem release];
187   // Add Select All
188   NSMenuItem* selectAllItem =
189       [[NSMenuItem alloc] initWithTitle:@"Select All"
190                                  action:@selector(selectAll:)
191                           keyEquivalent:@"a"];
192   [standardEditMenu addItem:selectAllItem];
193   [selectAllItem release];
195   return standardEditMenuItem;
197   NS_OBJC_END_TRY_ABORT_BLOCK;
200 bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* aContent) {
201   return aContent->IsElement() && (aContent->AsElement()->AttrValueIs(
202                                        kNameSpaceID_None, nsGkAtoms::hidden,
203                                        nsGkAtoms::_true, eCaseMatters) ||
204                                    aContent->AsElement()->AttrValueIs(
205                                        kNameSpaceID_None, nsGkAtoms::collapsed,
206                                        nsGkAtoms::_true, eCaseMatters));
209 NSMenuItem* nsMenuUtilsX::NativeMenuItemWithLocation(NSMenu* aRootMenu,
210                                                      NSString* aLocationString,
211                                                      bool aIsMenuBar) {
212   NSArray<NSString*>* indexes =
213       [aLocationString componentsSeparatedByString:@"|"];
214   unsigned int pathLength = indexes.count;
215   if (pathLength == 0) {
216     return nil;
217   }
219   NSMenu* currentSubmenu = aRootMenu;
220   for (unsigned int depth = 0; depth < pathLength; depth++) {
221     NSInteger targetIndex = [indexes objectAtIndex:depth].integerValue;
222     if (aIsMenuBar && depth == 0) {
223       // We remove the application menu from consideration for the top-level
224       // menu.
225       targetIndex++;
226     }
227     int itemCount = currentSubmenu.numberOfItems;
228     if (targetIndex >= itemCount) {
229       return nil;
230     }
231     NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
232     // if this is the last index just return the menu item
233     if (depth == pathLength - 1) {
234       return menuItem;
235     }
236     // if this is not the last index find the submenu and keep going
237     if (menuItem.hasSubmenu) {
238       currentSubmenu = menuItem.submenu;
239     } else {
240       return nil;
241     }
242   }
244   return nil;
247 static void CheckNativeMenuConsistencyImpl(
248     NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects);
250 static void CheckNativeMenuItemConsistencyImpl(
251     NSMenuItem* aMenuItem, std::unordered_set<void*>& aSeenObjects) {
252   bool inserted = aSeenObjects.insert(aMenuItem).second;
253   MOZ_RELEASE_ASSERT(inserted,
254                      "Duplicate NSMenuItem object in native menu structure");
255   if (aMenuItem.hasSubmenu) {
256     CheckNativeMenuConsistencyImpl(aMenuItem.submenu, aSeenObjects);
257   }
260 static void CheckNativeMenuConsistencyImpl(
261     NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects) {
262   bool inserted = aSeenObjects.insert(aMenu).second;
263   MOZ_RELEASE_ASSERT(inserted,
264                      "Duplicate NSMenu object in native menu structure");
265   for (NSMenuItem* item in aMenu.itemArray) {
266     CheckNativeMenuItemConsistencyImpl(item, aSeenObjects);
267   }
270 void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenu* aMenu) {
271   std::unordered_set<void*> seenObjects;
272   CheckNativeMenuConsistencyImpl(aMenu, seenObjects);
275 void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenuItem* aMenuItem) {
276   std::unordered_set<void*> seenObjects;
277   CheckNativeMenuItemConsistencyImpl(aMenuItem, seenObjects);
280 static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent,
281                                      const Maybe<int>& aIndexInParentMenu);
283 static void DumpNativeNSMenuImpl(NSMenu* aMenu, uint32_t aIndent) {
284   printf("%*sNSMenu [%p] %-16s\n", aIndent * 2, "", aMenu,
285          (aMenu.title.length == 0 ? "(no title)" : aMenu.title.UTF8String));
286   int index = 0;
287   for (NSMenuItem* item in aMenu.itemArray) {
288     DumpNativeNSMenuItemImpl(item, aIndent + 1, Some(index));
289     index++;
290   }
293 static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent,
294                                      const Maybe<int>& aIndexInParentMenu) {
295   printf("%*s", aIndent * 2, "");
296   if (aIndexInParentMenu) {
297     printf("[%d] ", *aIndexInParentMenu);
298   }
299   printf(
300       "NSMenuItem [%p] %-16s%s\n", aItem,
301       aItem.isSeparatorItem
302           ? "----"
303           : (aItem.title.length == 0 ? "(no title)" : aItem.title.UTF8String),
304       aItem.hasSubmenu ? " [hasSubmenu]" : "");
305   if (aItem.hasSubmenu) {
306     DumpNativeNSMenuImpl(aItem.submenu, aIndent + 1);
307   }
310 void nsMenuUtilsX::DumpNativeMenu(NSMenu* aMenu) {
311   DumpNativeNSMenuImpl(aMenu, 0);
314 void nsMenuUtilsX::DumpNativeMenuItem(NSMenuItem* aMenuItem) {
315   DumpNativeNSMenuItemImpl(aMenuItem, 0, Nothing());