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 nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget();
116 if (hiddenWindowWidgetNoCOMPtr) {
117 return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)
123 // It would be nice if we could localize these edit menu names.
124 NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem() {
125 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
127 // In principle we should be able to allocate this once and then always
128 // return the same object. But weird interactions happen between native
129 // app-modal dialogs and Gecko-modal dialogs that open above them. So what
130 // we return here isn't always released before it needs to be added to
131 // another menu. See bmo bug 468393.
132 NSMenuItem* standardEditMenuItem =
133 [[[NSMenuItem alloc] initWithTitle:@"Edit" action:nil
134 keyEquivalent:@""] autorelease];
135 NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
136 standardEditMenuItem.submenu = standardEditMenu;
137 [standardEditMenu release];
140 NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo"
141 action:@selector(undo:)
143 [standardEditMenu addItem:undoItem];
147 NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo"
148 action:@selector(redo:)
150 [standardEditMenu addItem:redoItem];
154 [standardEditMenu addItem:[NSMenuItem separatorItem]];
157 NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
158 action:@selector(cut:)
160 [standardEditMenu addItem:cutItem];
164 NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy"
165 action:@selector(copy:)
167 [standardEditMenu addItem:copyItem];
171 NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
172 action:@selector(paste:)
174 [standardEditMenu addItem:pasteItem];
178 NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete"
179 action:@selector(delete:)
181 [standardEditMenu addItem:deleteItem];
182 [deleteItem release];
185 NSMenuItem* selectAllItem =
186 [[NSMenuItem alloc] initWithTitle:@"Select All"
187 action:@selector(selectAll:)
189 [standardEditMenu addItem:selectAllItem];
190 [selectAllItem release];
192 return standardEditMenuItem;
194 NS_OBJC_END_TRY_ABORT_BLOCK;
197 bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* aContent) {
198 return aContent->IsElement() && (aContent->AsElement()->AttrValueIs(
199 kNameSpaceID_None, nsGkAtoms::hidden,
200 nsGkAtoms::_true, eCaseMatters) ||
201 aContent->AsElement()->AttrValueIs(
202 kNameSpaceID_None, nsGkAtoms::collapsed,
203 nsGkAtoms::_true, eCaseMatters));
206 NSMenuItem* nsMenuUtilsX::NativeMenuItemWithLocation(NSMenu* aRootMenu,
207 NSString* aLocationString,
209 NSArray<NSString*>* indexes =
210 [aLocationString componentsSeparatedByString:@"|"];
211 unsigned int pathLength = indexes.count;
212 if (pathLength == 0) {
216 NSMenu* currentSubmenu = aRootMenu;
217 for (unsigned int depth = 0; depth < pathLength; depth++) {
218 NSInteger targetIndex = [indexes objectAtIndex:depth].integerValue;
219 if (aIsMenuBar && depth == 0) {
220 // We remove the application menu from consideration for the top-level
224 int itemCount = currentSubmenu.numberOfItems;
225 if (targetIndex >= itemCount) {
228 NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
229 // if this is the last index just return the menu item
230 if (depth == pathLength - 1) {
233 // if this is not the last index find the submenu and keep going
234 if (menuItem.hasSubmenu) {
235 currentSubmenu = menuItem.submenu;
244 static void CheckNativeMenuConsistencyImpl(
245 NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects);
247 static void CheckNativeMenuItemConsistencyImpl(
248 NSMenuItem* aMenuItem, std::unordered_set<void*>& aSeenObjects) {
249 bool inserted = aSeenObjects.insert(aMenuItem).second;
250 MOZ_RELEASE_ASSERT(inserted,
251 "Duplicate NSMenuItem object in native menu structure");
252 if (aMenuItem.hasSubmenu) {
253 CheckNativeMenuConsistencyImpl(aMenuItem.submenu, aSeenObjects);
257 static void CheckNativeMenuConsistencyImpl(
258 NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects) {
259 bool inserted = aSeenObjects.insert(aMenu).second;
260 MOZ_RELEASE_ASSERT(inserted,
261 "Duplicate NSMenu object in native menu structure");
262 for (NSMenuItem* item in aMenu.itemArray) {
263 CheckNativeMenuItemConsistencyImpl(item, aSeenObjects);
267 void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenu* aMenu) {
268 std::unordered_set<void*> seenObjects;
269 CheckNativeMenuConsistencyImpl(aMenu, seenObjects);
272 void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenuItem* aMenuItem) {
273 std::unordered_set<void*> seenObjects;
274 CheckNativeMenuItemConsistencyImpl(aMenuItem, seenObjects);
277 static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent,
278 const Maybe<int>& aIndexInParentMenu);
280 static void DumpNativeNSMenuImpl(NSMenu* aMenu, uint32_t aIndent) {
281 printf("%*sNSMenu [%p] %-16s\n", aIndent * 2, "", aMenu,
282 (aMenu.title.length == 0 ? "(no title)" : aMenu.title.UTF8String));
284 for (NSMenuItem* item in aMenu.itemArray) {
285 DumpNativeNSMenuItemImpl(item, aIndent + 1, Some(index));
290 static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent,
291 const Maybe<int>& aIndexInParentMenu) {
292 printf("%*s", aIndent * 2, "");
293 if (aIndexInParentMenu) {
294 printf("[%d] ", *aIndexInParentMenu);
297 "NSMenuItem [%p] %-16s%s\n", aItem,
298 aItem.isSeparatorItem
300 : (aItem.title.length == 0 ? "(no title)" : aItem.title.UTF8String),
301 aItem.hasSubmenu ? " [hasSubmenu]" : "");
302 if (aItem.hasSubmenu) {
303 DumpNativeNSMenuImpl(aItem.submenu, aIndent + 1);
307 void nsMenuUtilsX::DumpNativeMenu(NSMenu* aMenu) {
308 DumpNativeNSMenuImpl(aMenu, 0);
311 void nsMenuUtilsX::DumpNativeMenuItem(NSMenuItem* aMenuItem) {
312 DumpNativeNSMenuItemImpl(aMenuItem, 0, Nothing());