widget/cocoa/nsMenuItemX.mm

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "nsMenuItemX.h"
michael@0 7 #include "nsMenuBarX.h"
michael@0 8 #include "nsMenuX.h"
michael@0 9 #include "nsMenuItemIconX.h"
michael@0 10 #include "nsMenuUtilsX.h"
michael@0 11 #include "nsCocoaUtils.h"
michael@0 12
michael@0 13 #include "nsObjCExceptions.h"
michael@0 14
michael@0 15 #include "nsCOMPtr.h"
michael@0 16 #include "nsGkAtoms.h"
michael@0 17
michael@0 18 #include "mozilla/dom/Element.h"
michael@0 19 #include "nsIWidget.h"
michael@0 20 #include "nsIDocument.h"
michael@0 21 #include "nsIDOMDocument.h"
michael@0 22 #include "nsIDOMElement.h"
michael@0 23 #include "nsIDOMEvent.h"
michael@0 24
michael@0 25 nsMenuItemX::nsMenuItemX()
michael@0 26 {
michael@0 27 mType = eRegularMenuItemType;
michael@0 28 mNativeMenuItem = nil;
michael@0 29 mMenuParent = nullptr;
michael@0 30 mMenuGroupOwner = nullptr;
michael@0 31 mIsChecked = false;
michael@0 32
michael@0 33 MOZ_COUNT_CTOR(nsMenuItemX);
michael@0 34 }
michael@0 35
michael@0 36 nsMenuItemX::~nsMenuItemX()
michael@0 37 {
michael@0 38 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 39
michael@0 40 // Prevent the icon object from outliving us.
michael@0 41 if (mIcon)
michael@0 42 mIcon->Destroy();
michael@0 43
michael@0 44 // autorelease the native menu item so that anything else happening to this
michael@0 45 // object happens before the native menu item actually dies
michael@0 46 [mNativeMenuItem autorelease];
michael@0 47
michael@0 48 if (mContent)
michael@0 49 mMenuGroupOwner->UnregisterForContentChanges(mContent);
michael@0 50 if (mCommandContent)
michael@0 51 mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
michael@0 52
michael@0 53 MOZ_COUNT_DTOR(nsMenuItemX);
michael@0 54
michael@0 55 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 56 }
michael@0 57
michael@0 58 nsresult nsMenuItemX::Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
michael@0 59 nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
michael@0 60 {
michael@0 61 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 62
michael@0 63 mType = aItemType;
michael@0 64 mMenuParent = aParent;
michael@0 65 mContent = aNode;
michael@0 66
michael@0 67 mMenuGroupOwner = aMenuGroupOwner;
michael@0 68 NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one!");
michael@0 69
michael@0 70 mMenuGroupOwner->RegisterForContentChanges(mContent, this);
michael@0 71
michael@0 72 nsIDocument *doc = mContent->GetCurrentDoc();
michael@0 73
michael@0 74 // if we have a command associated with this menu item, register for changes
michael@0 75 // to the command DOM node
michael@0 76 if (doc) {
michael@0 77 nsAutoString ourCommand;
michael@0 78 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::command, ourCommand);
michael@0 79
michael@0 80 if (!ourCommand.IsEmpty()) {
michael@0 81 nsIContent *commandElement = doc->GetElementById(ourCommand);
michael@0 82
michael@0 83 if (commandElement) {
michael@0 84 mCommandContent = commandElement;
michael@0 85 // register to observe the command DOM element
michael@0 86 mMenuGroupOwner->RegisterForContentChanges(mCommandContent, this);
michael@0 87 }
michael@0 88 }
michael@0 89 }
michael@0 90
michael@0 91 // decide enabled state based on command content if it exists, otherwise do it based
michael@0 92 // on our own content
michael@0 93 bool isEnabled;
michael@0 94 if (mCommandContent)
michael@0 95 isEnabled = !mCommandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
michael@0 96 else
michael@0 97 isEnabled = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
michael@0 98
michael@0 99 // set up the native menu item
michael@0 100 if (mType == eSeparatorMenuItemType) {
michael@0 101 mNativeMenuItem = [[NSMenuItem separatorItem] retain];
michael@0 102 }
michael@0 103 else {
michael@0 104 NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(aLabel);
michael@0 105 mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
michael@0 106
michael@0 107 [mNativeMenuItem setEnabled:(BOOL)isEnabled];
michael@0 108
michael@0 109 SetChecked(mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
michael@0 110 nsGkAtoms::_true, eCaseMatters));
michael@0 111 SetKeyEquiv();
michael@0 112 }
michael@0 113
michael@0 114 mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
michael@0 115 if (!mIcon)
michael@0 116 return NS_ERROR_OUT_OF_MEMORY;
michael@0 117
michael@0 118 return NS_OK;
michael@0 119
michael@0 120 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 121 }
michael@0 122
michael@0 123 nsresult nsMenuItemX::SetChecked(bool aIsChecked)
michael@0 124 {
michael@0 125 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 126
michael@0 127 mIsChecked = aIsChecked;
michael@0 128
michael@0 129 // update the content model. This will also handle unchecking our siblings
michael@0 130 // if we are a radiomenu
michael@0 131 mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
michael@0 132 mIsChecked ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"), true);
michael@0 133
michael@0 134 // update native menu item
michael@0 135 if (mIsChecked)
michael@0 136 [mNativeMenuItem setState:NSOnState];
michael@0 137 else
michael@0 138 [mNativeMenuItem setState:NSOffState];
michael@0 139
michael@0 140 return NS_OK;
michael@0 141
michael@0 142 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 143 }
michael@0 144
michael@0 145 EMenuItemType nsMenuItemX::GetMenuItemType()
michael@0 146 {
michael@0 147 return mType;
michael@0 148 }
michael@0 149
michael@0 150 // Executes the "cached" javaScript command.
michael@0 151 // Returns NS_OK if the command was executed properly, otherwise an error code.
michael@0 152 void nsMenuItemX::DoCommand()
michael@0 153 {
michael@0 154 // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
michael@0 155 if (mType == eCheckboxMenuItemType ||
michael@0 156 (mType == eRadioMenuItemType && !mIsChecked)) {
michael@0 157 if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
michael@0 158 nsGkAtoms::_false, eCaseMatters))
michael@0 159 SetChecked(!mIsChecked);
michael@0 160 /* the AttributeChanged code will update all the internal state */
michael@0 161 }
michael@0 162
michael@0 163 nsMenuUtilsX::DispatchCommandTo(mContent);
michael@0 164 }
michael@0 165
michael@0 166 nsresult nsMenuItemX::DispatchDOMEvent(const nsString &eventName, bool *preventDefaultCalled)
michael@0 167 {
michael@0 168 if (!mContent)
michael@0 169 return NS_ERROR_FAILURE;
michael@0 170
michael@0 171 // get owner document for content
michael@0 172 nsCOMPtr<nsIDocument> parentDoc = mContent->OwnerDoc();
michael@0 173
michael@0 174 // get interface for creating DOM events from content owner document
michael@0 175 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(parentDoc);
michael@0 176 if (!domDoc) {
michael@0 177 NS_WARNING("Failed to QI parent nsIDocument to nsIDOMDocument");
michael@0 178 return NS_ERROR_FAILURE;
michael@0 179 }
michael@0 180
michael@0 181 // create DOM event
michael@0 182 nsCOMPtr<nsIDOMEvent> event;
michael@0 183 nsresult rv = domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
michael@0 184 if (NS_FAILED(rv)) {
michael@0 185 NS_WARNING("Failed to create nsIDOMEvent");
michael@0 186 return rv;
michael@0 187 }
michael@0 188 event->InitEvent(eventName, true, true);
michael@0 189
michael@0 190 // mark DOM event as trusted
michael@0 191 event->SetTrusted(true);
michael@0 192
michael@0 193 // send DOM event
michael@0 194 rv = mContent->DispatchEvent(event, preventDefaultCalled);
michael@0 195 if (NS_FAILED(rv)) {
michael@0 196 NS_WARNING("Failed to send DOM event via EventTarget");
michael@0 197 return rv;
michael@0 198 }
michael@0 199
michael@0 200 return NS_OK;
michael@0 201 }
michael@0 202
michael@0 203 // Walk the sibling list looking for nodes with the same name and
michael@0 204 // uncheck them all.
michael@0 205 void nsMenuItemX::UncheckRadioSiblings(nsIContent* inCheckedContent)
michael@0 206 {
michael@0 207 nsAutoString myGroupName;
michael@0 208 inCheckedContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, myGroupName);
michael@0 209 if (!myGroupName.Length()) // no groupname, nothing to do
michael@0 210 return;
michael@0 211
michael@0 212 nsCOMPtr<nsIContent> parent = inCheckedContent->GetParent();
michael@0 213 if (!parent)
michael@0 214 return;
michael@0 215
michael@0 216 // loop over siblings
michael@0 217 uint32_t count = parent->GetChildCount();
michael@0 218 for (uint32_t i = 0; i < count; i++) {
michael@0 219 nsIContent *sibling = parent->GetChildAt(i);
michael@0 220 if (sibling) {
michael@0 221 if (sibling != inCheckedContent) { // skip this node
michael@0 222 // if the current sibling is in the same group, clear it
michael@0 223 if (sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
michael@0 224 myGroupName, eCaseMatters))
michael@0 225 sibling->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, NS_LITERAL_STRING("false"), true);
michael@0 226 }
michael@0 227 }
michael@0 228 }
michael@0 229 }
michael@0 230
michael@0 231 void nsMenuItemX::SetKeyEquiv()
michael@0 232 {
michael@0 233 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 234
michael@0 235 // Set key shortcut and modifiers
michael@0 236 nsAutoString keyValue;
michael@0 237 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
michael@0 238 if (!keyValue.IsEmpty() && mContent->GetCurrentDoc()) {
michael@0 239 nsIContent *keyContent = mContent->GetCurrentDoc()->GetElementById(keyValue);
michael@0 240 if (keyContent) {
michael@0 241 nsAutoString keyChar;
michael@0 242 bool hasKey = keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
michael@0 243
michael@0 244 if (!hasKey || keyChar.IsEmpty()) {
michael@0 245 nsAutoString keyCodeName;
michael@0 246 keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeName);
michael@0 247 uint32_t charCode =
michael@0 248 nsCocoaUtils::ConvertGeckoNameToMacCharCode(keyCodeName);
michael@0 249 if (charCode) {
michael@0 250 keyChar.Assign(charCode);
michael@0 251 }
michael@0 252 else {
michael@0 253 keyChar.Assign(NS_LITERAL_STRING(" "));
michael@0 254 }
michael@0 255 }
michael@0 256
michael@0 257 nsAutoString modifiersStr;
michael@0 258 keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
michael@0 259 uint8_t modifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
michael@0 260
michael@0 261 unsigned int macModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(modifiers);
michael@0 262 [mNativeMenuItem setKeyEquivalentModifierMask:macModifiers];
michael@0 263
michael@0 264 NSString *keyEquivalent = [[NSString stringWithCharacters:(unichar*)keyChar.get()
michael@0 265 length:keyChar.Length()] lowercaseString];
michael@0 266 if ([keyEquivalent isEqualToString:@" "])
michael@0 267 [mNativeMenuItem setKeyEquivalent:@""];
michael@0 268 else
michael@0 269 [mNativeMenuItem setKeyEquivalent:keyEquivalent];
michael@0 270
michael@0 271 return;
michael@0 272 }
michael@0 273 }
michael@0 274
michael@0 275 // if the key was removed, clear the key
michael@0 276 [mNativeMenuItem setKeyEquivalent:@""];
michael@0 277
michael@0 278 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 279 }
michael@0 280
michael@0 281 //
michael@0 282 // nsChangeObserver
michael@0 283 //
michael@0 284
michael@0 285 void
michael@0 286 nsMenuItemX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute)
michael@0 287 {
michael@0 288 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 289
michael@0 290 if (!aContent)
michael@0 291 return;
michael@0 292
michael@0 293 if (aContent == mContent) { // our own content node changed
michael@0 294 if (aAttribute == nsGkAtoms::checked) {
michael@0 295 // if we're a radio menu, uncheck our sibling radio items. No need to
michael@0 296 // do any of this if we're just a normal check menu.
michael@0 297 if (mType == eRadioMenuItemType) {
michael@0 298 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
michael@0 299 nsGkAtoms::_true, eCaseMatters))
michael@0 300 UncheckRadioSiblings(mContent);
michael@0 301 }
michael@0 302 mMenuParent->SetRebuild(true);
michael@0 303 }
michael@0 304 else if (aAttribute == nsGkAtoms::hidden ||
michael@0 305 aAttribute == nsGkAtoms::collapsed ||
michael@0 306 aAttribute == nsGkAtoms::label) {
michael@0 307 mMenuParent->SetRebuild(true);
michael@0 308 }
michael@0 309 else if (aAttribute == nsGkAtoms::key) {
michael@0 310 SetKeyEquiv();
michael@0 311 }
michael@0 312 else if (aAttribute == nsGkAtoms::image) {
michael@0 313 SetupIcon();
michael@0 314 }
michael@0 315 else if (aAttribute == nsGkAtoms::disabled) {
michael@0 316 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
michael@0 317 [mNativeMenuItem setEnabled:NO];
michael@0 318 else
michael@0 319 [mNativeMenuItem setEnabled:YES];
michael@0 320 }
michael@0 321 }
michael@0 322 else if (aContent == mCommandContent) {
michael@0 323 // the only thing that really matters when the menu isn't showing is the
michael@0 324 // enabled state since it enables/disables keyboard commands
michael@0 325 if (aAttribute == nsGkAtoms::disabled) {
michael@0 326 // first we sync our menu item DOM node with the command DOM node
michael@0 327 nsAutoString commandDisabled;
michael@0 328 nsAutoString menuDisabled;
michael@0 329 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled);
michael@0 330 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, menuDisabled);
michael@0 331 if (!commandDisabled.Equals(menuDisabled)) {
michael@0 332 // The menu's disabled state needs to be updated to match the command.
michael@0 333 if (commandDisabled.IsEmpty())
michael@0 334 mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
michael@0 335 else
michael@0 336 mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled, true);
michael@0 337 }
michael@0 338 // now we sync our native menu item with the command DOM node
michael@0 339 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
michael@0 340 [mNativeMenuItem setEnabled:NO];
michael@0 341 else
michael@0 342 [mNativeMenuItem setEnabled:YES];
michael@0 343 }
michael@0 344 }
michael@0 345
michael@0 346 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 347 }
michael@0 348
michael@0 349 void nsMenuItemX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, int32_t aIndexInContainer)
michael@0 350 {
michael@0 351 if (aChild == mCommandContent) {
michael@0 352 mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
michael@0 353 mCommandContent = nullptr;
michael@0 354 }
michael@0 355
michael@0 356 mMenuParent->SetRebuild(true);
michael@0 357 }
michael@0 358
michael@0 359 void nsMenuItemX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
michael@0 360 nsIContent *aChild)
michael@0 361 {
michael@0 362 mMenuParent->SetRebuild(true);
michael@0 363 }
michael@0 364
michael@0 365 void nsMenuItemX::SetupIcon()
michael@0 366 {
michael@0 367 if (mIcon)
michael@0 368 mIcon->SetupIcon();
michael@0 369 }

mercurial