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/. */ 5 6#include "nsMenuUtilsX.h" 7#include <unordered_set> 8 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" 25 26using namespace mozilla; 27 28void nsMenuUtilsX::DispatchCommandTo(nsIContent* aTargetContent, 29 NSEventModifierFlags aModifierFlags, int16_t aButton) { 30 MOZ_ASSERT(aTargetContent, "null ptr"); 31 32 dom::Document* doc = aTargetContent->OwnerDoc(); 33 if (doc) { 34 RefPtr<dom::XULCommandEvent> event = 35 new dom::XULCommandEvent(doc, doc->GetPresContext(), nullptr); 36 37 bool ctrlKey = aModifierFlags & NSEventModifierFlagControl; 38 bool altKey = aModifierFlags & NSEventModifierFlagOption; 39 bool shiftKey = aModifierFlags & NSEventModifierFlagShift; 40 bool cmdKey = aModifierFlags & NSEventModifierFlagCommand; 41 42 IgnoredErrorResult rv; 43 event->InitCommandEvent(u"command"_ns, true, true, 44 nsGlobalWindowInner::Cast(doc->GetInnerWindow()), 0, ctrlKey, altKey, 45 shiftKey, cmdKey, aButton, nullptr, 0, rv); 46 if (!rv.Failed()) { 47 event->SetTrusted(true); 48 aTargetContent->DispatchEvent(*event); 49 } 50 } 51} 52 53NSString* nsMenuUtilsX::GetTruncatedCocoaLabel(const nsString& itemLabel) { 54 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 55 56 // We want to truncate long strings to some reasonable pixel length but there is no 57 // good API for doing that which works for all OS versions and architectures. For now 58 // we'll do nothing for consistency and depend on good user interface design to limit 59 // string lengths. 60 return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(itemLabel.get()) 61 length:itemLabel.Length()]; 62 63 NS_OBJC_END_TRY_ABORT_BLOCK; 64} 65 66uint8_t nsMenuUtilsX::GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute) { 67 uint8_t modifiers = knsMenuItemNoModifier; 68 char* str = ToNewCString(modifiersAttribute); 69 char* newStr; 70 char* token = strtok_r(str, ", \t", &newStr); 71 while (token != nullptr) { 72 if (strcmp(token, "shift") == 0) { 73 modifiers |= knsMenuItemShiftModifier; 74 } else if (strcmp(token, "alt") == 0) { 75 modifiers |= knsMenuItemAltModifier; 76 } else if (strcmp(token, "control") == 0) { 77 modifiers |= knsMenuItemControlModifier; 78 } else if ((strcmp(token, "accel") == 0) || (strcmp(token, "meta") == 0)) { 79 modifiers |= knsMenuItemCommandModifier; 80 } 81 token = strtok_r(newStr, ", \t", &newStr); 82 } 83 free(str); 84 85 return modifiers; 86} 87 88unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(uint8_t geckoModifiers) { 89 unsigned int macModifiers = 0; 90 91 if (geckoModifiers & knsMenuItemShiftModifier) { 92 macModifiers |= NSEventModifierFlagShift; 93 } 94 if (geckoModifiers & knsMenuItemAltModifier) { 95 macModifiers |= NSEventModifierFlagOption; 96 } 97 if (geckoModifiers & knsMenuItemControlModifier) { 98 macModifiers |= NSEventModifierFlagControl; 99 } 100 if (geckoModifiers & knsMenuItemCommandModifier) { 101 macModifiers |= NSEventModifierFlagCommand; 102 } 103 104 return macModifiers; 105} 106 107nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar() { 108 nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget(); 109 if (hiddenWindowWidgetNoCOMPtr) { 110 return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)->GetMenuBar(); 111 } 112 return nullptr; 113} 114 115// It would be nice if we could localize these edit menu names. 116NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem() { 117 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 118 119 // In principle we should be able to allocate this once and then always 120 // return the same object. But weird interactions happen between native 121 // app-modal dialogs and Gecko-modal dialogs that open above them. So what 122 // we return here isn't always released before it needs to be added to 123 // another menu. See bmo bug 468393. 124 NSMenuItem* standardEditMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Edit" 125 action:nil 126 keyEquivalent:@""] autorelease]; 127 NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"]; 128 standardEditMenuItem.submenu = standardEditMenu; 129 [standardEditMenu release]; 130 131 // Add Undo 132 NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" 133 action:@selector(undo:) 134 keyEquivalent:@"z"]; 135 [standardEditMenu addItem:undoItem]; 136 [undoItem release]; 137 138 // Add Redo 139 NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" 140 action:@selector(redo:) 141 keyEquivalent:@"Z"]; 142 [standardEditMenu addItem:redoItem]; 143 [redoItem release]; 144 145 // Add separator 146 [standardEditMenu addItem:[NSMenuItem separatorItem]]; 147 148 // Add Cut 149 NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut" 150 action:@selector(cut:) 151 keyEquivalent:@"x"]; 152 [standardEditMenu addItem:cutItem]; 153 [cutItem release]; 154 155 // Add Copy 156 NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" 157 action:@selector(copy:) 158 keyEquivalent:@"c"]; 159 [standardEditMenu addItem:copyItem]; 160 [copyItem release]; 161 162 // Add Paste 163 NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste" 164 action:@selector(paste:) 165 keyEquivalent:@"v"]; 166 [standardEditMenu addItem:pasteItem]; 167 [pasteItem release]; 168 169 // Add Delete 170 NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete" 171 action:@selector(delete:) 172 keyEquivalent:@""]; 173 [standardEditMenu addItem:deleteItem]; 174 [deleteItem release]; 175 176 // Add Select All 177 NSMenuItem* selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All" 178 action:@selector(selectAll:) 179 keyEquivalent:@"a"]; 180 [standardEditMenu addItem:selectAllItem]; 181 [selectAllItem release]; 182 183 return standardEditMenuItem; 184 185 NS_OBJC_END_TRY_ABORT_BLOCK; 186} 187 188bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* aContent) { 189 return aContent->IsElement() && 190 (aContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, 191 eCaseMatters) || 192 aContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::collapsed, 193 nsGkAtoms::_true, eCaseMatters)); 194} 195 196NSMenuItem* nsMenuUtilsX::NativeMenuItemWithLocation(NSMenu* aRootMenu, NSString* aLocationString, 197 bool aIsMenuBar) { 198 NSArray<NSString*>* indexes = [aLocationString componentsSeparatedByString:@"|"]; 199 unsigned int pathLength = indexes.count; 200 if (pathLength == 0) { 201 return nil; 202 } 203 204 NSMenu* currentSubmenu = aRootMenu; 205 for (unsigned int depth = 0; depth < pathLength; depth++) { 206 NSInteger targetIndex = [indexes objectAtIndex:depth].integerValue; 207 if (aIsMenuBar && depth == 0) { 208 // We remove the application menu from consideration for the top-level menu. 209 targetIndex++; 210 } 211 int itemCount = currentSubmenu.numberOfItems; 212 if (targetIndex >= itemCount) { 213 return nil; 214 } 215 NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex]; 216 // if this is the last index just return the menu item 217 if (depth == pathLength - 1) { 218 return menuItem; 219 } 220 // if this is not the last index find the submenu and keep going 221 if (menuItem.hasSubmenu) { 222 currentSubmenu = menuItem.submenu; 223 } else { 224 return nil; 225 } 226 } 227 228 return nil; 229} 230 231static void CheckNativeMenuConsistencyImpl(NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects); 232 233static void CheckNativeMenuItemConsistencyImpl(NSMenuItem* aMenuItem, 234 std::unordered_set<void*>& aSeenObjects) { 235 bool inserted = aSeenObjects.insert(aMenuItem).second; 236 MOZ_RELEASE_ASSERT(inserted, "Duplicate NSMenuItem object in native menu structure"); 237 if (aMenuItem.hasSubmenu) { 238 CheckNativeMenuConsistencyImpl(aMenuItem.submenu, aSeenObjects); 239 } 240} 241 242static void CheckNativeMenuConsistencyImpl(NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects) { 243 bool inserted = aSeenObjects.insert(aMenu).second; 244 MOZ_RELEASE_ASSERT(inserted, "Duplicate NSMenu object in native menu structure"); 245 for (NSMenuItem* item in aMenu.itemArray) { 246 CheckNativeMenuItemConsistencyImpl(item, aSeenObjects); 247 } 248} 249 250void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenu* aMenu) { 251 std::unordered_set<void*> seenObjects; 252 CheckNativeMenuConsistencyImpl(aMenu, seenObjects); 253} 254 255void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenuItem* aMenuItem) { 256 std::unordered_set<void*> seenObjects; 257 CheckNativeMenuItemConsistencyImpl(aMenuItem, seenObjects); 258} 259 260static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent, 261 const Maybe<int>& aIndexInParentMenu); 262 263static void DumpNativeNSMenuImpl(NSMenu* aMenu, uint32_t aIndent) { 264 printf("%*sNSMenu [%p] %-16s\n", aIndent * 2, "", aMenu, 265 (aMenu.title.length == 0 ? "(no title)" : aMenu.title.UTF8String)); 266 int index = 0; 267 for (NSMenuItem* item in aMenu.itemArray) { 268 DumpNativeNSMenuItemImpl(item, aIndent + 1, Some(index)); 269 index++; 270 } 271} 272 273static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent, 274 const Maybe<int>& aIndexInParentMenu) { 275 printf("%*s", aIndent * 2, ""); 276 if (aIndexInParentMenu) { 277 printf("[%d] ", *aIndexInParentMenu); 278 } 279 printf("NSMenuItem [%p] %-16s%s\n", aItem, 280 aItem.isSeparatorItem ? "----" 281 : (aItem.title.length == 0 ? "(no title)" : aItem.title.UTF8String), 282 aItem.hasSubmenu ? " [hasSubmenu]" : ""); 283 if (aItem.hasSubmenu) { 284 DumpNativeNSMenuImpl(aItem.submenu, aIndent + 1); 285 } 286} 287 288void nsMenuUtilsX::DumpNativeMenu(NSMenu* aMenu) { DumpNativeNSMenuImpl(aMenu, 0); } 289 290void nsMenuUtilsX::DumpNativeMenuItem(NSMenuItem* aMenuItem) { 291 DumpNativeNSMenuItemImpl(aMenuItem, 0, Nothing()); 292} 293