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 "nsMenuGroupOwnerX.h"
7#include "nsMenuBarX.h"
8#include "nsMenuX.h"
9#include "nsMenuItemX.h"
10#include "nsMenuUtilsX.h"
11#include "nsCocoaUtils.h"
12#include "nsCocoaWindow.h"
13
14#include "nsCOMPtr.h"
15#include "nsString.h"
16#include "nsObjCExceptions.h"
17#include "nsThreadUtils.h"
18
19#include "mozilla/dom/Element.h"
20#include "nsIWidget.h"
21#include "mozilla/dom/Document.h"
22
23#include "nsINode.h"
24
25using namespace mozilla;
26
27NS_IMPL_ISUPPORTS(nsMenuGroupOwnerX, nsIMutationObserver)
28
29nsMenuGroupOwnerX::nsMenuGroupOwnerX(mozilla::dom::Element* aElement, nsMenuBarX* aMenuBarIfMenuBar)
30    : mContent(aElement), mMenuBar(aMenuBarIfMenuBar) {
31  mRepresentedObject = [[MOZMenuItemRepresentedObject alloc] initWithMenuGroupOwner:this];
32}
33
34nsMenuGroupOwnerX::~nsMenuGroupOwnerX() {
35  MOZ_ASSERT(mContentToObserverTable.Count() == 0, "have outstanding mutation observers!\n");
36  [mRepresentedObject setMenuGroupOwner:nullptr];
37  [mRepresentedObject release];
38}
39
40//
41// nsIMutationObserver
42//
43
44void nsMenuGroupOwnerX::CharacterDataWillChange(nsIContent* aContent,
45                                                const CharacterDataChangeInfo&) {}
46
47void nsMenuGroupOwnerX::CharacterDataChanged(nsIContent* aContent, const CharacterDataChangeInfo&) {
48}
49
50void nsMenuGroupOwnerX::ContentAppended(nsIContent* aFirstNewContent) {
51  for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
52    ContentInserted(cur);
53  }
54}
55
56void nsMenuGroupOwnerX::NodeWillBeDestroyed(const nsINode* aNode) {}
57
58void nsMenuGroupOwnerX::AttributeWillChange(dom::Element* aElement, int32_t aNameSpaceID,
59                                            nsAtom* aAttribute, int32_t aModType) {}
60
61void nsMenuGroupOwnerX::NativeAnonymousChildListChange(nsIContent* aContent, bool aIsRemove) {}
62
63void nsMenuGroupOwnerX::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
64                                         nsAtom* aAttribute, int32_t aModType,
65                                         const nsAttrValue* aOldValue) {
66  nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
67  nsChangeObserver* obs = LookupContentChangeObserver(aElement);
68  if (obs) {
69    obs->ObserveAttributeChanged(aElement->OwnerDoc(), aElement, aAttribute);
70  }
71}
72
73void nsMenuGroupOwnerX::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) {
74  nsIContent* container = aChild->GetParent();
75  if (!container) {
76    return;
77  }
78
79  nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
80  nsChangeObserver* obs = LookupContentChangeObserver(container);
81  if (obs) {
82    obs->ObserveContentRemoved(aChild->OwnerDoc(), container, aChild, aPreviousSibling);
83  } else if (container != mContent) {
84    // We do a lookup on the parent container in case things were removed
85    // under a "menupopup" item. That is basically a wrapper for the contents
86    // of a "menu" node.
87    nsCOMPtr<nsIContent> parent = container->GetParent();
88    if (parent) {
89      obs = LookupContentChangeObserver(parent);
90      if (obs) {
91        obs->ObserveContentRemoved(aChild->OwnerDoc(), container, aChild, aPreviousSibling);
92      }
93    }
94  }
95}
96
97void nsMenuGroupOwnerX::ContentInserted(nsIContent* aChild) {
98  nsIContent* container = aChild->GetParent();
99  if (!container) {
100    return;
101  }
102
103  nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
104  nsChangeObserver* obs = LookupContentChangeObserver(container);
105  if (obs) {
106    obs->ObserveContentInserted(aChild->OwnerDoc(), container, aChild);
107  } else if (container != mContent) {
108    // We do a lookup on the parent container in case things were removed
109    // under a "menupopup" item. That is basically a wrapper for the contents
110    // of a "menu" node.
111    nsCOMPtr<nsIContent> parent = container->GetParent();
112    if (parent) {
113      obs = LookupContentChangeObserver(parent);
114      if (obs) {
115        obs->ObserveContentInserted(aChild->OwnerDoc(), container, aChild);
116      }
117    }
118  }
119}
120
121void nsMenuGroupOwnerX::ParentChainChanged(nsIContent* aContent) {}
122
123// For change management, we don't use a |nsSupportsHashtable| because
124// we know that the lifetime of all these items is bounded by the
125// lifetime of the menubar. No need to add any more strong refs to the
126// picture because the containment hierarchy already uses strong refs.
127void nsMenuGroupOwnerX::RegisterForContentChanges(nsIContent* aContent,
128                                                  nsChangeObserver* aMenuObject) {
129  if (!mContentToObserverTable.Contains(aContent)) {
130    aContent->AddMutationObserver(this);
131  }
132  mContentToObserverTable.InsertOrUpdate(aContent, aMenuObject);
133}
134
135void nsMenuGroupOwnerX::UnregisterForContentChanges(nsIContent* aContent) {
136  if (mContentToObserverTable.Contains(aContent)) {
137    aContent->RemoveMutationObserver(this);
138  }
139  mContentToObserverTable.Remove(aContent);
140}
141
142nsChangeObserver* nsMenuGroupOwnerX::LookupContentChangeObserver(nsIContent* aContent) {
143  nsChangeObserver* result;
144  if (mContentToObserverTable.Get(aContent, &result)) {
145    return result;
146  }
147  return nullptr;
148}
149
150// Given a menu item, creates a unique 4-character command ID and
151// maps it to the item. Returns the id for use by the client.
152uint32_t nsMenuGroupOwnerX::RegisterForCommand(nsMenuItemX* aMenuItem) {
153  // no real need to check for uniqueness. We always start afresh with each
154  // window at 1. Even if we did get close to the reserved Apple command id's,
155  // those don't start until at least '    ', which is integer 538976288. If
156  // we have that many menu items in one window, I think we have other
157  // problems.
158
159  // make id unique
160  ++mCurrentCommandID;
161
162  mCommandToMenuObjectTable.InsertOrUpdate(mCurrentCommandID, aMenuItem);
163
164  return mCurrentCommandID;
165}
166
167// Removes the mapping between the given 4-character command ID
168// and its associated menu item.
169void nsMenuGroupOwnerX::UnregisterCommand(uint32_t aCommandID) {
170  mCommandToMenuObjectTable.Remove(aCommandID);
171}
172
173nsMenuItemX* nsMenuGroupOwnerX::GetMenuItemForCommandID(uint32_t aCommandID) {
174  nsMenuItemX* result;
175  if (mCommandToMenuObjectTable.Get(aCommandID, &result)) {
176    return result;
177  }
178  return nullptr;
179}
180
181@implementation MOZMenuItemRepresentedObject {
182  nsMenuGroupOwnerX* mMenuGroupOwner;  // weak, cleared by nsMenuGroupOwnerX's destructor
183}
184
185- (id)initWithMenuGroupOwner:(nsMenuGroupOwnerX*)aMenuGroupOwner {
186  self = [super init];
187  mMenuGroupOwner = aMenuGroupOwner;
188  return self;
189}
190
191- (void)setMenuGroupOwner:(nsMenuGroupOwnerX*)aMenuGroupOwner {
192  mMenuGroupOwner = aMenuGroupOwner;
193}
194
195- (nsMenuGroupOwnerX*)menuGroupOwner {
196  return mMenuGroupOwner;
197}
198
199@end
200