~ubuntu-branches/ubuntu/precise/firefox/precise-proposed

« back to all changes in this revision

Viewing changes to debian/globalmenu/src/uGlobalMenu.cpp

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2012-04-02 08:30:28 UTC
  • Revision ID: package-import@ubuntu.com-20120402083028-430x8nc65vivx0ne
Tags: 11.0+build1-0ubuntu2
* Update globalmenu-extension to 3.0.1
  - Reduce our memory footprint a bit, which wasn't really a lot anyway
  - Avoid the use of the component manager for accessing commonly used
    services, where "commonly used" means "accessed when building every
    menu item". This should save some CPU cycles when building or
    refreshing menus
  - Try to recycle menuitems when they are removed from a menu by
    adding contiguous blocks of removed items to a "free list" which
    is emptied asynchronously, and reusing the items in this list when
    new items are added in place of the removed items. This means that
    refreshing the history menu contents when the menu is opened no
    longer alters the menu structure, but results in a shifting of
    properties between existing nodes instead. This has a few benefits:
    + With no layout changes, unity-panel-service doesn't request
      the entire menu structure, which significantly reduces dbus traffic
      and makes it much faster to refresh the menu contents
    + The size of the menu doesn't change when it is refreshed, which
      eliminates the flicker that used to occur when opening the history
      menu.
    + The HUD can refresh our menus now without triggering layout updates
      (assuming that menu layout really hasn't changed, eg, by adding
      a bookmark)
  - Remove all use of the global observer service for sending our own
    internal notifications around
  - Clean up the way we ensure that the correct edit commands are
    enabled by just installing our own popupshowing handler rather
    than using an additional notification to fix things up after the
    default handler runs
  - Get rid of a static initializer
  - Don't support older than Firefox 11
  - Fix some GError leaks
  - Hide the internal menu when creating a native menu, rather than
    waiting for confirmation that the native menu is registered
    successfully. We don't try to create a native menu if we don't
    find a menu service to register the menu with anyway
  - Keep menu contents updated whilst the menu is open, rather than
    just whilst it is opening

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 
2
/* ***** BEGIN LICENSE BLOCK *****
 
3
 *       Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
4
 *
 
5
 * The contents of this file are subject to the Mozilla Public License Version
 
6
 * 1.1 (the "License"); you may not use this file except in compliance with
 
7
 * the License. You may obtain a copy of the License at
 
8
 * http://www.mozilla.org/MPL/
 
9
 * 
 
10
 * Software distributed under the License is distributed on an "AS IS" basis,
 
11
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
12
 * for the specific language governing rights and limitations under the
 
13
 * License.
 
14
 *
 
15
 * The Original Code is globalmenu-extension.
 
16
 *
 
17
 * The Initial Developer of the Original Code is
 
18
 * Canonical Ltd.
 
19
 * Portions created by the Initial Developer are Copyright (C) 2010
 
20
 * the Initial Developer. All Rights Reserved.
 
21
 *
 
22
 * Contributor(s):
 
23
 * Chris Coulson <chris.coulson@canonical.com>
 
24
 *
 
25
 * Alternatively, the contents of this file may be used under the terms of
 
26
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 
27
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 
28
 * in which case the provisions of the GPL or the LGPL are applicable instead
 
29
 * of those above. If you wish to allow use of your version of this file only
 
30
 * under the terms of either the GPL or the LGPL, and not to allow others to
 
31
 * use your version of this file under the terms of the MPL, indicate your
 
32
 * decision by deleting the provisions above and replace them with the notice
 
33
 * and other provisions required by the GPL or the LGPL. If you do not delete
 
34
 * the provisions above, a recipient may use your version of this file under
 
35
 * the terms of any one of the MPL, the GPL or the LGPL.
 
36
 * 
 
37
 * ***** END LICENSE BLOCK ***** */
 
38
 
 
39
#include <nsDebug.h>
 
40
#include <nsIXBLService.h>
 
41
#include <nsIAtom.h>
 
42
#include <nsIDOMEvent.h>
 
43
#include <nsIDOMMouseEvent.h>
 
44
#include <nsIDOMWindow.h>
 
45
#include <nsIDOMDocument.h>
 
46
#include <nsStringAPI.h>
 
47
#include <nsIDOMEventTarget.h>
 
48
#include <nsIPrivateDOMEvent.h>
 
49
#include <nsPIDOMWindow.h>
 
50
#include <nsIDOMXULCommandEvent.h>
 
51
#include <nsIXPConnect.h>
 
52
#include <nsIScriptGlobalObject.h>
 
53
#include <nsIScriptContext.h>
 
54
#include <jsapi.h>
 
55
#include <mozilla/dom/Element.h>
 
56
 
 
57
#include <glib-object.h>
 
58
 
 
59
#include "uGlobalMenuService.h"
 
60
#include "uGlobalMenu.h"
 
61
#include "uGlobalMenuBar.h"
 
62
#include "uGlobalMenuUtils.h"
 
63
#include "uWidgetAtoms.h"
 
64
 
 
65
#include "uDebug.h"
 
66
 
 
67
uGlobalMenu::RecycleList::RecycleList(uGlobalMenu *aMenu):
 
68
  mMarker(0), mMenu(aMenu)
 
69
{
 
70
  nsRefPtr<nsIRunnable> event =
 
71
    NS_NewNonOwningRunnableMethod(mMenu, &uGlobalMenu::FreeRecycleList);
 
72
  NS_DispatchToCurrentThread(event);
 
73
}
 
74
 
 
75
uGlobalMenu::RecycleList::~RecycleList()
 
76
{
 
77
  for (PRUint32 i = 0; i < mList.Length(); i++) {
 
78
    dbusmenu_menuitem_child_delete(mMenu->GetDbusMenuItem(), mList[i]);
 
79
  }
 
80
}
 
81
 
 
82
DbusmenuMenuitem*
 
83
uGlobalMenu::RecycleList::PopRecyclableItem()
 
84
{
 
85
  NS_ASSERTION(mList.Length() > 0, "No more recyclable menuitems");
 
86
 
 
87
  ++mMarker;
 
88
  DbusmenuMenuitem *recycled = mList[0];
 
89
  mList.RemoveElementAt(0);
 
90
 
 
91
  if (mList.Length() == 0) {
 
92
    mMenu->FreeRecycleList();
 
93
  }
 
94
 
 
95
  return recycled;
 
96
}
 
97
 
 
98
void
 
99
uGlobalMenu::RecycleList::PrependRecyclableItem(DbusmenuMenuitem *aItem)
 
100
{
 
101
  mList.InsertElementAt(0, aItem);
 
102
}
 
103
 
 
104
void
 
105
uGlobalMenu::RecycleList::AppendRecyclableItem(DbusmenuMenuitem *aItem)
 
106
{
 
107
  mList.AppendElement(aItem);
 
108
}
 
109
 
 
110
/*static*/ bool
 
111
uGlobalMenu::MenuEventCallback(DbusmenuMenuitem *menu,
 
112
                               const gchar *name,
 
113
                               GVariant *value,
 
114
                               guint timestamp,
 
115
                               void *data)
 
116
{
 
117
  uGlobalMenu *self = static_cast<uGlobalMenu *>(data);
 
118
  if (!g_strcmp0("closed", name)) {
 
119
    self->OnClose();
 
120
    return true;
 
121
  }
 
122
 
 
123
  if (!g_strcmp0("opened", name)) {
 
124
    self->OnOpen();
 
125
    return true;
 
126
  }
 
127
 
 
128
  return false;
 
129
}
 
130
 
 
131
/*static*/ bool
 
132
uGlobalMenu::MenuAboutToOpenCallback(DbusmenuMenuitem *menu,
 
133
                                     void *data)
 
134
{
 
135
  uGlobalMenu *self = static_cast<uGlobalMenu *>(data);
 
136
  self->AboutToOpen();
 
137
 
 
138
  // We return false here for "needsUpdate", as we have no way of
 
139
  // knowing in advance if the menu structure is going to be updated.
 
140
  // The menu layout will still update on the client, but we won't block
 
141
  // opening the menu until it's happened
 
142
  return false;
 
143
}
 
144
 
 
145
void
 
146
uGlobalMenu::Activate()
 
147
{
 
148
  mContent->SetAttr(kNameSpaceID_None, uWidgetAtoms::menuactive,
 
149
                    NS_LITERAL_STRING("true"), true);
 
150
 
 
151
  nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(mContent);
 
152
  if (target) {
 
153
    nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mContent->OwnerDoc());
 
154
    if (domDoc) {
 
155
      nsCOMPtr<nsIDOMEvent> event;
 
156
      domDoc->CreateEvent(NS_LITERAL_STRING("Events"),
 
157
                          getter_AddRefs(event));
 
158
      if (event) {
 
159
        event->InitEvent(NS_LITERAL_STRING("DOMMenuItemActive"),
 
160
                         true, true);
 
161
        nsCOMPtr<nsIPrivateDOMEvent> priv = do_QueryInterface(event);
 
162
        if (priv) {
 
163
          priv->SetTrusted(true);
 
164
        }
 
165
        bool dummy;
 
166
        target->DispatchEvent(event, &dummy);
 
167
      }
 
168
    }
 
169
  }
 
170
}
 
171
 
 
172
void
 
173
uGlobalMenu::Deactivate()
 
174
{
 
175
  mContent->UnsetAttr(kNameSpaceID_None, uWidgetAtoms::menuactive, true);
 
176
 
 
177
  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mContent->OwnerDoc());
 
178
  if (domDoc) {
 
179
    nsCOMPtr<nsIDOMEvent> event;
 
180
    domDoc->CreateEvent(NS_LITERAL_STRING("Events"),
 
181
                        getter_AddRefs(event));
 
182
    if (event) {
 
183
      event->InitEvent(NS_LITERAL_STRING("DOMMenuItemInactive"),
 
184
                       true, true);
 
185
      nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(mContent);
 
186
      if (target) {
 
187
        nsCOMPtr<nsIPrivateDOMEvent> priv = do_QueryInterface(event);
 
188
        if (priv) {
 
189
          priv->SetTrusted(true);
 
190
        }
 
191
        bool dummy;
 
192
        target->DispatchEvent(event, &dummy);
 
193
      }
 
194
    }
 
195
  }
 
196
}
 
197
 
 
198
bool
 
199
uGlobalMenu::CanOpen()
 
200
{
 
201
    bool isHidden = IsHidden();
 
202
    bool isDisabled = mContent->AttrValueIs(kNameSpaceID_None,
 
203
                                            uWidgetAtoms::disabled,
 
204
                                            uWidgetAtoms::_true,
 
205
                                            eCaseMatters);
 
206
 
 
207
    return (!isHidden && !isDisabled);
 
208
}
 
209
 
 
210
void
 
211
uGlobalMenu::AboutToOpen()
 
212
{
 
213
  TRACE_WITH_THIS_MENUOBJECT();
 
214
 
 
215
  // XXX: We ignore the first AboutToOpen on top-level menus, because Unity
 
216
  //      sends this signal on all top-levels when the window opens.
 
217
  //      This isn't useful for us and it doesn't finish the job by sending
 
218
  //      open/close events, so we end up in a state where we resent the
 
219
  //      entire menu structure over dbus on every page navigation
 
220
  if (!(mFlags & UNITY_MENU_READY)) {
 
221
    DEBUG_WITH_THIS_MENUOBJECT("Ignoring first AboutToOpen");
 
222
    SetFlags(UNITY_MENU_READY);
 
223
    return;
 
224
  }
 
225
 
 
226
  if (DoesNeedRebuild()) {
 
227
    Build();
 
228
  }
 
229
 
 
230
  SetFlags(UNITY_MENU_IS_OPEN_OR_OPENING);
 
231
 
 
232
  // If there is no popup content, then there is nothing to do, and it's
 
233
  // unsafe to proceed anyway
 
234
  if (!mPopupContent) {
 
235
    DEBUG_WITH_THIS_MENUOBJECT("Menu has no popup content");
 
236
    return;
 
237
  }
 
238
 
 
239
  PRUint32 count = mMenuObjects.Length();
 
240
  for (PRUint32 i = 0; i < count; i++) {
 
241
    mMenuObjects[i]->AboutToShowNotify();
 
242
  }
 
243
 
 
244
  // XXX: This should happen when the pointer hovers over the menu entry,
 
245
  //      but we don't have that information right now. We synthesize it for
 
246
  //      menus, but this doesn't work for menuitems at all
 
247
  Activate();
 
248
 
 
249
  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mPopupContent->OwnerDoc());
 
250
  if (domDoc) {
 
251
    nsCOMPtr<nsIDOMEvent> event;
 
252
    domDoc->CreateEvent(NS_LITERAL_STRING("mouseevent"),
 
253
                        getter_AddRefs(event));
 
254
    if (event) {
 
255
      nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(event);
 
256
      if (mouseEvent) {
 
257
        nsCOMPtr<nsIDOMWindow> window;
 
258
        domDoc->GetDefaultView(getter_AddRefs(window));
 
259
        if (window) {
 
260
          mouseEvent->InitMouseEvent(NS_LITERAL_STRING("popupshowing"),
 
261
                                     true, true, window, nsnull,
 
262
                                     0, 0, 0, 0, false, false,
 
263
                                     false, false, 0, nsnull);
 
264
          nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(mPopupContent);
 
265
          if (target) {
 
266
            nsCOMPtr<nsIPrivateDOMEvent> priv = do_QueryInterface(event);
 
267
            if (priv) {
 
268
              priv->SetTrusted(true);
 
269
            }
 
270
            bool dummy;
 
271
            // XXX: dummy == false means that we should prevent the
 
272
            //      the menu from opening, but there's no way to do this
 
273
            target->DispatchEvent(event, &dummy);
 
274
          }
 
275
        }
 
276
      }
 
277
    }
 
278
  }
 
279
}
 
280
 
 
281
void
 
282
uGlobalMenu::OnOpen()
 
283
{
 
284
  if (!IsOpenOrOpening()) {
 
285
    // If we didn't receive an AboutToOpen, then generate it ourselves
 
286
    AboutToOpen();
 
287
  }
 
288
 
 
289
  mContent->SetAttr(kNameSpaceID_None, uWidgetAtoms::open, NS_LITERAL_STRING("true"), true);
 
290
 
 
291
  // If there is no popup content, then there is nothing to do, and it's
 
292
  // unsafe to proceed anyway
 
293
  if (!mPopupContent) {
 
294
    return;
 
295
  }
 
296
 
 
297
  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mPopupContent->OwnerDoc());
 
298
  if (domDoc) {
 
299
    nsCOMPtr<nsIDOMEvent> event;
 
300
    domDoc->CreateEvent(NS_LITERAL_STRING("mouseevent"),
 
301
                        getter_AddRefs(event));
 
302
    if (event) {
 
303
      nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(event);
 
304
      if (mouseEvent) {
 
305
        nsCOMPtr<nsIDOMWindow> window;
 
306
        domDoc->GetDefaultView(getter_AddRefs(window));
 
307
        if (window) {
 
308
          mouseEvent->InitMouseEvent(NS_LITERAL_STRING("popupshown"),
 
309
                                     true, true, window, nsnull,
 
310
                                     0, 0, 0, 0, false, false,
 
311
                                     false, false, 0, nsnull);
 
312
          nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(mPopupContent);
 
313
          if (target) {
 
314
            nsCOMPtr<nsIPrivateDOMEvent> priv = do_QueryInterface(event);
 
315
            if (priv) {
 
316
              priv->SetTrusted(true);
 
317
            }
 
318
            bool dummy;
 
319
            target->DispatchEvent(event, &dummy);
 
320
          }
 
321
        }
 
322
      }
 
323
    }
 
324
  }
 
325
}
 
326
 
 
327
void
 
328
uGlobalMenu::OnClose()
 
329
{
 
330
  mContent->UnsetAttr(kNameSpaceID_None, uWidgetAtoms::open, true);
 
331
 
 
332
  // If there is no popup content, then there is nothing to do, and it's
 
333
  // unsafe to proceed anyway
 
334
  if (!mPopupContent) {
 
335
    ClearFlags(UNITY_MENU_IS_OPEN_OR_OPENING);
 
336
    return;
 
337
  }
 
338
 
 
339
  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mPopupContent->OwnerDoc());
 
340
  if (domDoc) {
 
341
    nsCOMPtr<nsIDOMEvent> event;
 
342
    domDoc->CreateEvent(NS_LITERAL_STRING("mouseevent"),
 
343
                        getter_AddRefs(event));
 
344
    if (event) {
 
345
      nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(event);
 
346
      if (mouseEvent) {
 
347
        nsCOMPtr<nsIDOMWindow> window;
 
348
        domDoc->GetDefaultView(getter_AddRefs(window));
 
349
        if (window) {
 
350
          mouseEvent->InitMouseEvent(NS_LITERAL_STRING("popuphiding"),
 
351
                                     true, true, window, nsnull,
 
352
                                     0, 0, 0, 0, false, false,
 
353
                                     false, false, 0, nsnull);
 
354
          nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(mPopupContent);
 
355
          if (target) {
 
356
            nsCOMPtr<nsIPrivateDOMEvent> priv = do_QueryInterface(event);
 
357
            if (priv) {
 
358
              priv->SetTrusted(true);
 
359
            }
 
360
            bool dummy;
 
361
            target->DispatchEvent(event, &dummy);
 
362
            mouseEvent->InitMouseEvent(NS_LITERAL_STRING("popuphidden"),
 
363
                                       true, true, window, nsnull,
 
364
                                       0, 0, 0, 0, false, false,
 
365
                                       false, false, 0, nsnull);
 
366
            target->DispatchEvent(event, &dummy);
 
367
          }
 
368
        }
 
369
      }
 
370
    }
 
371
  }
 
372
 
 
373
  ClearFlags(UNITY_MENU_IS_OPEN_OR_OPENING);
 
374
 
 
375
  Deactivate();
 
376
}
 
377
 
 
378
void
 
379
uGlobalMenu::SyncProperties()
 
380
{
 
381
  TRACE_WITH_THIS_MENUOBJECT();
 
382
 
 
383
  UpdateInfoFromContentClass();
 
384
  SyncLabelFromContent();
 
385
  SyncSensitivityFromContent();
 
386
  SyncVisibilityFromContent();
 
387
  SyncIconFromContent();
 
388
 
 
389
  ClearInvalid();
 
390
}
 
391
 
 
392
void
 
393
uGlobalMenu::InitializeDbusMenuItem()
 
394
{
 
395
  if (!mDbusMenuItem) {
 
396
    mDbusMenuItem = dbusmenu_menuitem_new();
 
397
    if (!mDbusMenuItem) {
 
398
      return;
 
399
    }
 
400
  } else {
 
401
    OnlyKeepProperties(static_cast<uMenuObjectProperties>(eLabel | eEnabled |
 
402
                                                          eVisible | eIconData |
 
403
                                                          eChildDisplay));
 
404
  }
 
405
 
 
406
  // This happens automatically when we add children, but we have to
 
407
  // do this manually for menus which don't initially have children,
 
408
  // so we can receive about-to-show which triggers a build of the menu
 
409
  dbusmenu_menuitem_property_set(mDbusMenuItem,
 
410
                                 DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY,
 
411
                                 DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU);
 
412
 
 
413
  g_signal_connect(G_OBJECT(mDbusMenuItem), "about-to-show",
 
414
                   G_CALLBACK(MenuAboutToOpenCallback), this);
 
415
  g_signal_connect(G_OBJECT(mDbusMenuItem), "event",
 
416
                   G_CALLBACK(MenuEventCallback), this);
 
417
 
 
418
  SyncProperties();
 
419
}
 
420
 
 
421
void
 
422
uGlobalMenu::GetMenuPopupFromMenu(nsIContent **aResult)
 
423
{
 
424
  if (!aResult)
 
425
    return;
 
426
 
 
427
  *aResult = nsnull;
 
428
 
 
429
  // Taken from widget/src/cocoa/nsMenuX.mm. Not sure if we need this
 
430
  nsIXBLService *xblService = uGlobalMenuService::GetXBLService();
 
431
  if (!xblService)
 
432
    return;
 
433
 
 
434
  PRInt32 dummy;
 
435
  nsCOMPtr<nsIAtom> tag;
 
436
  xblService->ResolveTag(mContent, &dummy, getter_AddRefs(tag));
 
437
  if (tag == uWidgetAtoms::menupopup) {
 
438
    *aResult = mContent;
 
439
    NS_ADDREF(*aResult);
 
440
    return;
 
441
  }
 
442
 
 
443
  PRUint32 count = mContent->GetChildCount();
 
444
 
 
445
  for (PRUint32 i = 0; i < count; i++) {
 
446
    PRInt32 dummy;
 
447
    nsIContent *child = mContent->GetChildAt(i);
 
448
    nsCOMPtr<nsIAtom> tag;
 
449
    xblService->ResolveTag(child, &dummy, getter_AddRefs(tag));
 
450
    if (tag == uWidgetAtoms::menupopup) {
 
451
      *aResult = child;
 
452
      NS_ADDREF(*aResult);
 
453
      return;
 
454
    }
 
455
  }
 
456
}
 
457
 
 
458
static bool
 
459
IsRecycledItemCompatible(DbusmenuMenuitem *aRecycled,
 
460
                         uGlobalMenuObject *aNewItem)
 
461
{
 
462
  // If the recycled item was a separator, it can only be reused as a separator
 
463
  if (!g_strcmp0(dbusmenu_menuitem_property_get(aRecycled, DBUSMENU_MENUITEM_PROP_TYPE),
 
464
                 "separator") &&
 
465
      aNewItem->GetType() != eMenuSeparator) {
 
466
    return false;
 
467
  }
 
468
 
 
469
  // Everything else is fine
 
470
  return true;
 
471
}
 
472
 
 
473
bool
 
474
uGlobalMenu::InsertMenuObjectAt(uGlobalMenuObject *menuObj,
 
475
                                PRUint32 index)
 
476
{
 
477
  PRUint32 correctedIndex = index;
 
478
 
 
479
  DbusmenuMenuitem *recycled = nsnull;
 
480
  if (mRecycleList) {
 
481
    if (index < mRecycleList->mMarker) {
 
482
      ++mRecycleList->mMarker;
 
483
    } else if (index > mRecycleList->mMarker) {
 
484
      correctedIndex += mRecycleList->mList.Length();
 
485
    } else {
 
486
      recycled = mRecycleList->PopRecyclableItem();
 
487
      if (!IsRecycledItemCompatible(recycled, menuObj)) {
 
488
        recycled = nsnull;
 
489
        mRecycleList = nsnull;
 
490
      }
 
491
    }
 
492
  }
 
493
 
 
494
  gboolean res = TRUE;
 
495
  if (recycled) {
 
496
    menuObj->SetDbusMenuItem(recycled);
 
497
  } else {
 
498
    res = dbusmenu_menuitem_child_add_position(mDbusMenuItem,
 
499
                                               menuObj->GetDbusMenuItem(),
 
500
                                               correctedIndex);
 
501
  }
 
502
 
 
503
  return res && mMenuObjects.InsertElementAt(index, menuObj);
 
504
}
 
505
 
 
506
bool
 
507
uGlobalMenu::AppendMenuObject(uGlobalMenuObject *menuObj)
 
508
{
 
509
  DbusmenuMenuitem *recycled = nsnull;
 
510
  if (mRecycleList && mRecycleList->mMarker > mMenuObjects.Length()) {
 
511
    recycled = mRecycleList->PopRecyclableItem();
 
512
    if (!IsRecycledItemCompatible(recycled, menuObj)) {
 
513
      recycled = nsnull;
 
514
      mRecycleList = nsnull;
 
515
    }
 
516
  }
 
517
 
 
518
  gboolean res = TRUE;
 
519
  if (recycled) {
 
520
    menuObj->SetDbusMenuItem(recycled);
 
521
  } else {
 
522
    res = dbusmenu_menuitem_child_append(mDbusMenuItem,
 
523
                                         menuObj->GetDbusMenuItem());
 
524
  }
 
525
 
 
526
  return res && mMenuObjects.AppendElement(menuObj);
 
527
}
 
528
 
 
529
bool
 
530
uGlobalMenu::RemoveMenuObjectAt(PRUint32 index)
 
531
{
 
532
  NS_ASSERTION(index < mMenuObjects.Length(), "Invalid index");
 
533
  if (index >= mMenuObjects.Length()) {
 
534
    return false;
 
535
  }
 
536
 
 
537
  if (!mRecycleList) {
 
538
    mRecycleList = new RecycleList(this);
 
539
  }
 
540
 
 
541
  gboolean res = TRUE;
 
542
  if (mRecycleList->mList.Length() == 0 || index == mRecycleList->mMarker) {
 
543
    mRecycleList->AppendRecyclableItem(mMenuObjects[index]->GetDbusMenuItem());
 
544
  } else if (index == mRecycleList->mMarker - 1) {
 
545
    mRecycleList->PrependRecyclableItem(mMenuObjects[index]->GetDbusMenuItem());
 
546
  } else {
 
547
    mRecycleList = nsnull;
 
548
    res = dbusmenu_menuitem_child_delete(mDbusMenuItem,
 
549
                                         mMenuObjects[index]->GetDbusMenuItem());
 
550
  }
 
551
 
 
552
  if (mRecycleList) {
 
553
    mRecycleList->mMarker = index;
 
554
  }
 
555
 
 
556
  mMenuObjects.RemoveElementAt(index);
 
557
 
 
558
  return !!res;
 
559
}
 
560
 
 
561
nsresult
 
562
uGlobalMenu::Build()
 
563
{
 
564
  TRACE_WITH_THIS_MENUOBJECT();
 
565
 
 
566
  PRUint32 count = mMenuObjects.Length();
 
567
  for (PRUint32 i = 0; i < count; i++) {
 
568
    RemoveMenuObjectAt(0);
 
569
  }
 
570
 
 
571
  // Removing all of the children causes dbusmenu to convert us from a
 
572
  // submenu to a normal menuitem. Adding children changes this back again.
 
573
  // We can avoid the shell ever seeing this by manually making ourself
 
574
  // a submenu again before spinning the event loop
 
575
  dbusmenu_menuitem_property_set(mDbusMenuItem,
 
576
                                 DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY,
 
577
                                 DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU);
 
578
 
 
579
  if (mPopupContent && mPopupContent != mContent) {
 
580
    mListener->UnregisterForContentChanges(mPopupContent, this);
 
581
  }
 
582
 
 
583
  GetMenuPopupFromMenu(getter_AddRefs(mPopupContent));
 
584
 
 
585
  if (!mPopupContent) {
 
586
    // The menu has no popup, so there are no menuitems here
 
587
    return NS_OK;
 
588
  }
 
589
 
 
590
  // Manually wrap the menupopup node to make sure it's bounded
 
591
  // Borrowed from widget/src/cocoa/nsMenuX.mm, we need this to make
 
592
  // some menus in Thunderbird work
 
593
  nsIDocument *doc = mPopupContent->GetCurrentDoc();
 
594
  if (doc) {
 
595
    nsIXPConnect *xpconnect = uGlobalMenuService::GetXPConnect();
 
596
    if (xpconnect) {
 
597
      nsIScriptGlobalObject *sgo = doc->GetScriptGlobalObject();
 
598
      nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
 
599
      JSObject *global = sgo->GetGlobalJSObject();
 
600
      if (scriptContext && global) {
 
601
        JSContext *cx = (JSContext *)scriptContext->GetNativeContext();
 
602
        if (cx) {
 
603
          nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
 
604
          xpconnect->WrapNative(cx, global,
 
605
                                mPopupContent, NS_GET_IID(nsISupports),
 
606
                                getter_AddRefs(wrapper));
 
607
        }
 
608
      }
 
609
    }
 
610
  }
 
611
 
 
612
  if (mContent != mPopupContent) {
 
613
    nsresult rv = mListener->RegisterForContentChanges(mPopupContent, this);
 
614
    if (NS_FAILED(rv)) {
 
615
      NS_WARNING("Failed to register for popup content changes");
 
616
      return rv;
 
617
    }
 
618
  }
 
619
 
 
620
  ClearNeedsRebuild();
 
621
 
 
622
  count = mPopupContent->GetChildCount();
 
623
 
 
624
  for (PRUint32 i = 0; i < count; i++) {
 
625
    nsIContent *child = mPopupContent->GetChildAt(i);
 
626
    uGlobalMenuObject *menuObject =
 
627
      NewGlobalMenuItem(static_cast<uGlobalMenuObject *>(this),
 
628
                        mListener, child, mMenuBar);
 
629
    bool res = false;
 
630
    if (menuObject) {
 
631
      res = AppendMenuObject(menuObject);
 
632
    }
 
633
    NS_WARN_IF_FALSE(res, "Failed to append menuitem. Marking menu invalid");
 
634
    if (!res) {
 
635
      SetNeedsRebuild();
 
636
      return NS_ERROR_FAILURE;
 
637
    }
 
638
  }
 
639
 
 
640
  return NS_OK;
 
641
}
 
642
 
 
643
nsresult
 
644
uGlobalMenu::Init(uGlobalMenuObject *aParent,
 
645
                  uGlobalMenuDocListener *aListener,
 
646
                  nsIContent *aContent,
 
647
                  uGlobalMenuBar *aMenuBar)
 
648
{
 
649
  NS_ENSURE_ARG(aParent);
 
650
  NS_ENSURE_ARG(aListener);
 
651
  NS_ENSURE_ARG(aContent);
 
652
  NS_ENSURE_ARG(aMenuBar);
 
653
 
 
654
  mParent = aParent;
 
655
  mListener = aListener;
 
656
  mContent = aContent;
 
657
  mMenuBar = aMenuBar;
 
658
 
 
659
  SetNeedsRebuild();
 
660
 
 
661
  // See the hack comment above for why this workaround is here
 
662
  if (mParent->GetType() != eMenuBar || mMenuBar->IsRegistered()) {
 
663
    SetFlags(UNITY_MENU_READY);
 
664
  }
 
665
 
 
666
  nsresult rv = mListener->RegisterForContentChanges(mContent, this);
 
667
  if (NS_FAILED(rv)) {
 
668
    NS_WARNING("Failed to register for content changes");
 
669
    return rv;
 
670
  }
 
671
 
 
672
  return NS_OK;
 
673
}
 
674
 
 
675
uGlobalMenu::uGlobalMenu(): uGlobalMenuObject(eMenu)
 
676
{
 
677
  MOZ_COUNT_CTOR(uGlobalMenu);
 
678
}
 
679
 
 
680
uGlobalMenu::~uGlobalMenu()
 
681
{
 
682
  if (mListener) {
 
683
    mListener->UnregisterForContentChanges(mContent, this);
 
684
    if (mPopupContent && mContent != mPopupContent) {
 
685
      mListener->UnregisterForContentChanges(mPopupContent, this);
 
686
    }
 
687
  }
 
688
 
 
689
  DestroyIconLoader();
 
690
 
 
691
  if (mDbusMenuItem) {
 
692
    g_signal_handlers_disconnect_by_func(mDbusMenuItem,
 
693
                                         reinterpret_cast<gpointer>(MenuAboutToOpenCallback),
 
694
                                         this);
 
695
    g_signal_handlers_disconnect_by_func(mDbusMenuItem,
 
696
                                         reinterpret_cast<gpointer>(MenuEventCallback),
 
697
                                         this);
 
698
    g_object_unref(mDbusMenuItem);
 
699
  }
 
700
 
 
701
  MOZ_COUNT_DTOR(uGlobalMenu);
 
702
}
 
703
 
 
704
/*static*/ uGlobalMenuObject*
 
705
uGlobalMenu::Create(uGlobalMenuObject *aParent,
 
706
                    uGlobalMenuDocListener *aListener,
 
707
                    nsIContent *aContent,
 
708
                    uGlobalMenuBar *aMenuBar)
 
709
{
 
710
  TRACE_WITH_CONTENT(aContent);
 
711
 
 
712
  uGlobalMenu *menu = new uGlobalMenu();
 
713
  if (!menu) {
 
714
    return nsnull;
 
715
  }
 
716
 
 
717
  if (NS_FAILED(menu->Init(aParent, aListener, aContent, aMenuBar))) {
 
718
    delete menu;
 
719
    return nsnull;
 
720
  }
 
721
 
 
722
  return static_cast<uGlobalMenuObject *>(menu);
 
723
}
 
724
 
 
725
void
 
726
uGlobalMenu::AboutToShowNotify()
 
727
{
 
728
  TRACE_WITH_THIS_MENUOBJECT();
 
729
 
 
730
  if (IsDirty()) {
 
731
    SyncProperties();
 
732
  } else {
 
733
    UpdateVisibility();
 
734
  }
 
735
}
 
736
 
 
737
void
 
738
uGlobalMenu::OpenMenu()
 
739
{
 
740
  if (!CanOpen()) {
 
741
    return;
 
742
  }
 
743
 
 
744
  dbusmenu_menuitem_show_to_user(mDbusMenuItem, 0);
 
745
}
 
746
 
 
747
void
 
748
uGlobalMenu::ObserveAttributeChanged(nsIDocument *aDocument,
 
749
                                     nsIContent *aContent,
 
750
                                     nsIAtom *aAttribute)
 
751
{
 
752
  TRACE_WITH_THIS_MENUOBJECT();
 
753
  NS_ASSERTION(aContent == mContent || aContent == mPopupContent,
 
754
               "Received an event that wasn't meant for us!");
 
755
 
 
756
  if (IsDirty()) {
 
757
    DEBUG_WITH_THIS_MENUOBJECT("Previously marked as invalid");
 
758
    return;
 
759
  }
 
760
 
 
761
  if (mParent->GetType() == eMenu &&
 
762
      !(static_cast<uGlobalMenu *>(mParent))->IsOpenOrOpening()) {
 
763
    DEBUG_WITH_THIS_MENUOBJECT("Parent isn't open or opening. Marking invalid");
 
764
    Invalidate();
 
765
    return;
 
766
  }
 
767
 
 
768
  if (aAttribute == uWidgetAtoms::open) {
 
769
    return;
 
770
  }
 
771
 
 
772
  if (aAttribute == uWidgetAtoms::disabled) {
 
773
    SyncSensitivityFromContent();
 
774
  } else if (aAttribute == uWidgetAtoms::hidden ||
 
775
             aAttribute == uWidgetAtoms::collapsed) {
 
776
    SyncVisibilityFromContent();
 
777
  } else if (aAttribute == uWidgetAtoms::label || 
 
778
             aAttribute == uWidgetAtoms::accesskey) {
 
779
    SyncLabelFromContent();
 
780
  } else if (aAttribute == uWidgetAtoms::image) {
 
781
    SyncIconFromContent();
 
782
  } else if (aAttribute == uWidgetAtoms::_class) {
 
783
    UpdateInfoFromContentClass();
 
784
    SyncVisibilityFromContent();
 
785
    SyncIconFromContent();
 
786
  }
 
787
}
 
788
 
 
789
void
 
790
uGlobalMenu::ObserveContentRemoved(nsIDocument *aDocument,
 
791
                                   nsIContent *aContainer,
 
792
                                   nsIContent *aChild,
 
793
                                   PRInt32 aIndexInContainer)
 
794
{
 
795
  TRACE_WITH_THIS_MENUOBJECT();
 
796
  NS_ASSERTION(aContainer == mContent || aContainer == mPopupContent,
 
797
               "Received an event that wasn't meant for us!");
 
798
 
 
799
  if (DoesNeedRebuild()) {
 
800
    DEBUG_WITH_THIS_MENUOBJECT("Previously marked as needing a rebuild");
 
801
    return;
 
802
  }
 
803
 
 
804
  if (!IsOpenOrOpening()) {
 
805
    DEBUG_WITH_THIS_MENUOBJECT("Not open or opening - Marking as needing a rebuild");
 
806
    SetNeedsRebuild();
 
807
    return;
 
808
  }
 
809
 
 
810
  if (aContainer == mPopupContent) {
 
811
    bool res = RemoveMenuObjectAt(aIndexInContainer);
 
812
    NS_WARN_IF_FALSE(res, "Failed to remove menuitem. Marking menu invalid");
 
813
    if (!res) {
 
814
      SetNeedsRebuild();
 
815
    }
 
816
  } else {
 
817
    Build();
 
818
  }
 
819
}
 
820
 
 
821
void
 
822
uGlobalMenu::ObserveContentInserted(nsIDocument *aDocument,
 
823
                                    nsIContent *aContainer,
 
824
                                    nsIContent *aChild,
 
825
                                    PRInt32 aIndexInContainer)
 
826
{
 
827
  TRACE_WITH_THIS_MENUOBJECT();
 
828
  NS_ASSERTION(aContainer == mContent || aContainer == mPopupContent,
 
829
               "Received an event that wasn't meant for us!");
 
830
 
 
831
  if (DoesNeedRebuild()) {
 
832
    DEBUG_WITH_THIS_MENUOBJECT("Previously marked as needing a rebuild");
 
833
    return;
 
834
  }
 
835
 
 
836
  if (!IsOpenOrOpening()) {
 
837
    DEBUG_WITH_THIS_MENUOBJECT("Not open or opening - Marking as needing a rebuild");
 
838
    SetNeedsRebuild();
 
839
    return;
 
840
  }
 
841
 
 
842
  if (aContainer == mPopupContent) {
 
843
    uGlobalMenuObject *newItem =
 
844
      NewGlobalMenuItem(static_cast<uGlobalMenuObject *>(this),
 
845
                        mListener, aChild, mMenuBar);
 
846
    bool res = false;
 
847
    if (newItem) {
 
848
      res = InsertMenuObjectAt(newItem, aIndexInContainer);
 
849
    }
 
850
    NS_WARN_IF_FALSE(res, "Failed to insert menuitem. Marking menu invalid");
 
851
    if (!res) {
 
852
      SetNeedsRebuild();
 
853
    }    
 
854
  } else {
 
855
    Build();
 
856
  }
 
857
}