diff --git a/widget/src/cocoa/nsIChangeManager.idl b/widget/src/cocoa/nsIChangeManager.idl index 249c8b219659..abd89c3af2c0 100644 --- a/widget/src/cocoa/nsIChangeManager.idl +++ b/widget/src/cocoa/nsIChangeManager.idl @@ -54,11 +54,13 @@ interface nsIDocument; // its corresponding content object. // (NOTE: If we need more things, we can add them later) // -[scriptable, uuid(d8cf3bd0-1dd1-11b2-a837-fec5f865b071)] +[uuid(f5734700-fb27-11da-974d-0800200c9a66)] interface nsIChangeObserver : nsISupports { - void AttributeChanged ( in nsIDocument aDocument, - in long aNameSpaceID, in nsIAtom aAttribute ) ; + void AttributeChanged(in nsIDocument aDocument, + in long aNameSpaceID, + in nsIContent aContent, + in nsIAtom aAttribute); void ContentRemoved ( in nsIDocument aDocument, in nsIContent aChild, in long aIndexInContainer ) ; diff --git a/widget/src/cocoa/nsMenuBarX.mm b/widget/src/cocoa/nsMenuBarX.mm index 4f5a597f03f6..ba084c8dbc75 100644 --- a/widget/src/cocoa/nsMenuBarX.mm +++ b/widget/src/cocoa/nsMenuBarX.mm @@ -869,7 +869,7 @@ nsMenuBarX::AttributeChanged(nsIDocument * aDocument, nsIContent * aContent, nsCOMPtr obs; Lookup(aContent, getter_AddRefs(obs)); if (obs) - obs->AttributeChanged(aDocument, aNameSpaceID, aAttribute); + obs->AttributeChanged(aDocument, aNameSpaceID, aContent, aAttribute); } void diff --git a/widget/src/cocoa/nsMenuItemX.h b/widget/src/cocoa/nsMenuItemX.h index aeef51bd5aa3..3acd0346f410 100644 --- a/widget/src/cocoa/nsMenuItemX.h +++ b/widget/src/cocoa/nsMenuItemX.h @@ -113,7 +113,8 @@ protected: nsCOMPtr mXULCommandListener; nsWeakPtr mDocShellWeakRef; // weak ref to docshell - nsCOMPtr mContent; + nsCOMPtr mContent; + nsCOMPtr mCommandContent; PRUint8 mModifiers; PRPackedBool mIsSeparator; diff --git a/widget/src/cocoa/nsMenuItemX.mm b/widget/src/cocoa/nsMenuItemX.mm index 1f13b053d32d..ce281d824a42 100644 --- a/widget/src/cocoa/nsMenuItemX.mm +++ b/widget/src/cocoa/nsMenuItemX.mm @@ -66,6 +66,7 @@ nsMenuItemX::nsMenuItemX() { mNativeMenuItem = nil; mMenuParent = nsnull; + mManager = nsnull; mIsSeparator = PR_FALSE; mKeyEquivalent.AssignLiteral(" "); mEnabled = PR_TRUE; @@ -77,7 +78,10 @@ nsMenuItemX::nsMenuItemX() nsMenuItemX::~nsMenuItemX() { [mNativeMenuItem autorelease]; - mManager->Unregister(mContent); + if (mContent) + mManager->Unregister(mContent); + if (mCommandContent) + mManager->Unregister(mCommandContent); } @@ -97,6 +101,27 @@ NS_METHOD nsMenuItemX::Create(nsIMenu* aParent, const nsString & aLabel, PRBool nsCOMPtr obs = do_QueryInterface(NS_STATIC_CAST(nsIChangeObserver*,this)); mManager->Register(mContent, obs); // does not addref this + // if we have a command associated with this menu item, register for changes + // to the command DOM node + nsAutoString ourCommand; + mContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::command, ourCommand); + if (!ourCommand.IsEmpty()) { + nsIDocument* currDoc = mContent->GetCurrentDoc(); + if (currDoc) { + nsCOMPtr domDoc(do_QueryInterface(currDoc)); + if (domDoc) { + // get the command DOM element + nsCOMPtr commandElt; + domDoc->GetElementById(ourCommand, getter_AddRefs(commandElt)); + if (commandElt) { + mCommandContent = do_QueryInterface(commandElt); + // register to observe the command DOM element + mManager->Register(mCommandContent, obs); // does not addref this + } + } + } + } + mIsSeparator = aIsSeparator; mLabel = aLabel; @@ -390,23 +415,51 @@ nsMenuItemX::UncheckRadioSiblings(nsIContent* inCheckedContent) NS_IMETHODIMP -nsMenuItemX::AttributeChanged(nsIDocument *aDocument, PRInt32 aNameSpaceID, nsIAtom *aAttribute) +nsMenuItemX::AttributeChanged(nsIDocument *aDocument, PRInt32 aNameSpaceID, nsIContent *aContent, nsIAtom *aAttribute) { - if (aAttribute == nsWidgetAtoms::checked) { - // if we're a radio menu, uncheck our sibling radio items. No need to - // do any of this if we're just a normal check menu. - if (mMenuType == eRadio) { - if (mContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::checked, - nsWidgetAtoms::_true, eCaseMatters)) - UncheckRadioSiblings(mContent); + if (!aContent) + return NS_OK; + + if (aContent == mContent) { // our own content node changed + if (aAttribute == nsWidgetAtoms::checked) { + // if we're a radio menu, uncheck our sibling radio items. No need to + // do any of this if we're just a normal check menu. + if (mMenuType == eRadio) { + if (mContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::checked, + nsWidgetAtoms::_true, eCaseMatters)) + UncheckRadioSiblings(mContent); + } + nsCOMPtr listener = do_QueryInterface(mMenuParent); + listener->SetRebuild(PR_TRUE); + } + else if (aAttribute == nsWidgetAtoms::hidden || + aAttribute == nsWidgetAtoms::collapsed) { + nsCOMPtr listener = do_QueryInterface(mMenuParent); + listener->SetRebuild(PR_TRUE); } - nsCOMPtr listener = do_QueryInterface(mMenuParent); - listener->SetRebuild(PR_TRUE); } - else if (aAttribute == nsWidgetAtoms::disabled || aAttribute == nsWidgetAtoms::hidden || - aAttribute == nsWidgetAtoms::collapsed) { - nsCOMPtr listener = do_QueryInterface(mMenuParent); - listener->SetRebuild(PR_TRUE); + else if (aContent == mCommandContent) { + // the only thing that really matters when the menu isn't showing is the + // enabled state since it enables/disables keyboard commands + if (aAttribute == nsWidgetAtoms::disabled) { + // first we sync our menu item DOM node with the command DOM node + nsAutoString commandDisabled; + nsAutoString menuDisabled; + aContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled); + mContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, menuDisabled); + if (!commandDisabled.Equals(menuDisabled)) { + // The menu's disabled state needs to be updated to match the command. + if (commandDisabled.IsEmpty()) + mContent->UnsetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, PR_TRUE); + else + mContent->SetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled, PR_TRUE); + } + // now we sync our native menu item with the command DOM node + if (aContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::disabled, nsWidgetAtoms::_true, eCaseMatters)) + [mNativeMenuItem setEnabled:NO]; + else + [mNativeMenuItem setEnabled:YES]; + } } return NS_OK; @@ -416,15 +469,19 @@ nsMenuItemX::AttributeChanged(nsIDocument *aDocument, PRInt32 aNameSpaceID, nsIA NS_IMETHODIMP nsMenuItemX::ContentRemoved(nsIDocument *aDocument, nsIContent *aChild, PRInt32 aIndexInContainer) { + if (aChild == mCommandContent) { + mManager->Unregister(mCommandContent); + mCommandContent = nsnull; + } + nsCOMPtr listener = do_QueryInterface(mMenuParent); listener->SetRebuild(PR_TRUE); return NS_OK; - } // ContentRemoved NS_IMETHODIMP -nsMenuItemX :: ContentInserted(nsIDocument *aDocument, nsIContent *aChild, PRInt32 aIndexInContainer) +nsMenuItemX::ContentInserted(nsIDocument *aDocument, nsIContent *aChild, PRInt32 aIndexInContainer) { nsCOMPtr listener = do_QueryInterface(mMenuParent); listener->SetRebuild(PR_TRUE); diff --git a/widget/src/cocoa/nsMenuX.mm b/widget/src/cocoa/nsMenuX.mm index 49a41e9ac86a..af0fca6f60bc 100644 --- a/widget/src/cocoa/nsMenuX.mm +++ b/widget/src/cocoa/nsMenuX.mm @@ -992,7 +992,7 @@ nsMenuX::CountVisibleBefore(PRUint32* outVisibleBefore) NS_IMETHODIMP -nsMenuX::AttributeChanged(nsIDocument *aDocument, PRInt32 aNameSpaceID, nsIAtom *aAttribute) +nsMenuX::AttributeChanged(nsIDocument *aDocument, PRInt32 aNameSpaceID, nsIContent *aContent, nsIAtom *aAttribute) { // ignore the |open| attribute, which is by far the most common if (gConstructingMenu || (aAttribute == nsWidgetAtoms::open))