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 [[[GeckoNSMenuItem alloc] initWithTitle:@"Edit"
138 keyEquivalent:@""] autorelease];
139 NSMenu* standardEditMenu = [[GeckoNSMenu alloc] initWithTitle:@"Edit"];
140 standardEditMenuItem.submenu = standardEditMenu;
141 [standardEditMenu release];
144 NSMenuItem* undoItem = [[GeckoNSMenuItem alloc] initWithTitle:@"Undo"
145 action:@selector(undo:)
147 [standardEditMenu addItem:undoItem];
151 NSMenuItem* redoItem = [[GeckoNSMenuItem alloc] initWithTitle:@"Redo"
152 action:@selector(redo:)
154 [standardEditMenu addItem:redoItem];
158 [standardEditMenu addItem:[NSMenuItem separatorItem]];
161 NSMenuItem* cutItem = [[GeckoNSMenuItem alloc] initWithTitle:@"Cut"
162 action:@selector(cut:)
164 [standardEditMenu addItem:cutItem];
168 NSMenuItem* copyItem = [[GeckoNSMenuItem alloc] initWithTitle:@"Copy"
169 action:@selector(copy:)
171 [standardEditMenu addItem:copyItem];
175 NSMenuItem* pasteItem =
176 [[GeckoNSMenuItem alloc] initWithTitle:@"Paste"
177 action:@selector(paste:)
179 [standardEditMenu addItem:pasteItem];
183 NSMenuItem* deleteItem =
184 [[GeckoNSMenuItem alloc] initWithTitle:@"Delete"
185 action:@selector(delete:)
187 [standardEditMenu addItem:deleteItem];
188 [deleteItem release];
191 NSMenuItem* selectAllItem =
192 [[GeckoNSMenuItem alloc] initWithTitle:@"Select All"
193 action:@selector(selectAll:)
195 [standardEditMenu addItem:selectAllItem];
196 [selectAllItem release];
198 return standardEditMenuItem;
200 NS_OBJC_END_TRY_ABORT_BLOCK;
203 bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* aContent) {
204 return aContent->IsElement() && (aContent->AsElement()->AttrValueIs(
205 kNameSpaceID_None, nsGkAtoms::hidden,
206 nsGkAtoms::_true, eCaseMatters) ||
207 aContent->AsElement()->AttrValueIs(
208 kNameSpaceID_None, nsGkAtoms::collapsed,
209 nsGkAtoms::_true, eCaseMatters));
212 NSMenuItem* nsMenuUtilsX::NativeMenuItemWithLocation(NSMenu* aRootMenu,
213 NSString* aLocationString,
215 NSArray<NSString*>* indexes =
216 [aLocationString componentsSeparatedByString:@"|"];
217 unsigned int pathLength = indexes.count;
218 if (pathLength == 0) {
222 NSMenu* currentSubmenu = aRootMenu;
223 for (unsigned int depth = 0; depth < pathLength; depth++) {
224 NSInteger targetIndex = [indexes objectAtIndex:depth].integerValue;
225 if (aIsMenuBar && depth == 0) {
226 // We remove the application menu from consideration for the top-level
230 int itemCount = currentSubmenu.numberOfItems;
231 if (targetIndex >= itemCount) {
234 NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
235 // if this is the last index just return the menu item
236 if (depth == pathLength - 1) {
239 // if this is not the last index find the submenu and keep going
240 if (menuItem.hasSubmenu) {
241 currentSubmenu = menuItem.submenu;
250 static void CheckNativeMenuConsistencyImpl(
251 NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects);
253 static void CheckNativeMenuItemConsistencyImpl(
254 NSMenuItem* aMenuItem, std::unordered_set<void*>& aSeenObjects) {
255 bool inserted = aSeenObjects.insert(aMenuItem).second;
256 MOZ_RELEASE_ASSERT(inserted,
257 "Duplicate NSMenuItem object in native menu structure");
259 id representedObject = aMenuItem.representedObject;
260 if ([representedObject isKindOfClass:[MOZMenuItemRepresentedObject class]]) {
261 nsMenuGroupOwnerX* owner =
262 ((MOZMenuItemRepresentedObject*)representedObject).menuGroupOwner;
263 nsMenuItemX* menuItemX = owner->GetMenuItemForCommandID(aMenuItem.tag);
265 inserted = aSeenObjects.insert(menuItemX).second;
268 "Duplicate represented nsMenuItemX object in native menu structure");
270 nsIContent* menuItemContent = menuItemX->Content();
271 inserted = aSeenObjects.insert(menuItemContent).second;
272 MOZ_RELEASE_ASSERT(inserted, "Duplicate represented <menuitem> "
273 "nsIContent in native menu structure");
275 // The NSMenuItems created in nsMenuBar::CreateNativeAppMenuItem don't
276 // have corresponding nsMenuItemX objects. Ignore those.
280 if (aMenuItem.hasSubmenu) {
281 CheckNativeMenuConsistencyImpl(aMenuItem.submenu, aSeenObjects);
285 static void CheckNativeMenuConsistencyImpl(
286 NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects) {
287 bool inserted = aSeenObjects.insert(aMenu).second;
288 MOZ_RELEASE_ASSERT(inserted,
289 "Duplicate NSMenu object in native menu structure");
290 for (NSMenuItem* item in aMenu.itemArray) {
291 CheckNativeMenuItemConsistencyImpl(item, aSeenObjects);
295 void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenu* aMenu) {
296 std::unordered_set<void*> seenObjects;
297 CheckNativeMenuConsistencyImpl(aMenu, seenObjects);
300 void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenuItem* aMenuItem) {
301 std::unordered_set<void*> seenObjects;
302 CheckNativeMenuItemConsistencyImpl(aMenuItem, seenObjects);
305 static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent,
306 const Maybe<int>& aIndexInParentMenu);
308 static void DumpNativeNSMenuImpl(NSMenu* aMenu, uint32_t aIndent) {
309 printf("%*sNSMenu [%p] %-16s\n", aIndent * 2, "", aMenu,
310 (aMenu.title.length == 0 ? "(no title)" : aMenu.title.UTF8String));
312 for (NSMenuItem* item in aMenu.itemArray) {
313 DumpNativeNSMenuItemImpl(item, aIndent + 1, Some(index));
318 static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent,
319 const Maybe<int>& aIndexInParentMenu) {
320 printf("%*s", aIndent * 2, "");
321 if (aIndexInParentMenu) {
322 printf("[%d] ", *aIndexInParentMenu);
325 "NSMenuItem [%p] %-16s%s\n", aItem,
326 aItem.isSeparatorItem
328 : (aItem.title.length == 0 ? "(no title)" : aItem.title.UTF8String),
329 aItem.hasSubmenu ? " [hasSubmenu]" : "");
330 if (aItem.hasSubmenu) {
331 DumpNativeNSMenuImpl(aItem.submenu, aIndent + 1);
335 void nsMenuUtilsX::DumpNativeMenu(NSMenu* aMenu) {
336 DumpNativeNSMenuImpl(aMenu, 0);
339 void nsMenuUtilsX::DumpNativeMenuItem(NSMenuItem* aMenuItem) {
340 DumpNativeNSMenuItemImpl(aMenuItem, 0, Nothing());