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"
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,
33 MOZ_ASSERT(aTargetContent, "null ptr");
35 dom::Document* doc = aTargetContent->OwnerDoc();
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,
51 event->SetTrusted(true);
52 aTargetContent->DispatchEvent(*event);
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.
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);
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;
87 token = strtok_r(newStr, ", \t", &newStr);
94 unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(
95 uint8_t geckoModifiers) {
96 unsigned int macModifiers = 0;
98 if (geckoModifiers & knsMenuItemShiftModifier) {
99 macModifiers |= NSEventModifierFlagShift;
101 if (geckoModifiers & knsMenuItemAltModifier) {
102 macModifiers |= NSEventModifierFlagOption;
104 if (geckoModifiers & knsMenuItemControlModifier) {
105 macModifiers |= NSEventModifierFlagControl;
107 if (geckoModifiers & knsMenuItemCommandModifier) {
108 macModifiers |= NSEventModifierFlagCommand;
114 nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar() {
115 if (gfxPlatform::IsHeadless()) {
118 nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget();
119 if (hiddenWindowWidgetNoCOMPtr) {
120 return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)
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];
143 NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo"
144 action:@selector(undo:)
146 [standardEditMenu addItem:undoItem];
150 NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo"
151 action:@selector(redo:)
153 [standardEditMenu addItem:redoItem];
157 [standardEditMenu addItem:[NSMenuItem separatorItem]];
160 NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
161 action:@selector(cut:)
163 [standardEditMenu addItem:cutItem];
167 NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy"
168 action:@selector(copy:)
170 [standardEditMenu addItem:copyItem];
174 NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
175 action:@selector(paste:)
177 [standardEditMenu addItem:pasteItem];
181 NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete"
182 action:@selector(delete:)
184 [standardEditMenu addItem:deleteItem];
185 [deleteItem release];
188 NSMenuItem* selectAllItem =
189 [[NSMenuItem alloc] initWithTitle:@"Select All"
190 action:@selector(selectAll:)
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,
212 NSArray<NSString*>* indexes =
213 [aLocationString componentsSeparatedByString:@"|"];
214 unsigned int pathLength = indexes.count;
215 if (pathLength == 0) {
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
227 int itemCount = currentSubmenu.numberOfItems;
228 if (targetIndex >= itemCount) {
231 NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
232 // if this is the last index just return the menu item
233 if (depth == pathLength - 1) {
236 // if this is not the last index find the submenu and keep going
237 if (menuItem.hasSubmenu) {
238 currentSubmenu = menuItem.submenu;
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);
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);
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));
287 for (NSMenuItem* item in aMenu.itemArray) {
288 DumpNativeNSMenuItemImpl(item, aIndent + 1, Some(index));
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);
300 "NSMenuItem [%p] %-16s%s\n", aItem,
301 aItem.isSeparatorItem
303 : (aItem.title.length == 0 ? "(no title)" : aItem.title.UTF8String),
304 aItem.hasSubmenu ? " [hasSubmenu]" : "");
305 if (aItem.hasSubmenu) {
306 DumpNativeNSMenuImpl(aItem.submenu, aIndent + 1);
310 void nsMenuUtilsX::DumpNativeMenu(NSMenu* aMenu) {
311 DumpNativeNSMenuImpl(aMenu, 0);
314 void nsMenuUtilsX::DumpNativeMenuItem(NSMenuItem* aMenuItem) {
315 DumpNativeNSMenuItemImpl(aMenuItem, 0, Nothing());