support testing of lazy update behavior in native menu tests. b=446352 r=mstange sr=roc
authorJosh Aas <joshmoz@gmail.com>
Wed, 29 Oct 2008 22:36:01 -0700
changeset 21081 b7e0323caf3a62152aea2fe87fec56c93410078a
parent 21080 45f2c96d4a9b04d774443a1b838ae761a42bfb2f
child 21082 0cb8250ba9885803af1de6cfecbe764be15788e9
push id3293
push userjosh@mozilla.com
push dateThu, 30 Oct 2008 05:34:30 +0000
treeherdermozilla-central@b7e0323caf3a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange, roc
bugs446352
milestone1.9.1b2pre
support testing of lazy update behavior in native menu tests. b=446352 r=mstange sr=roc
dom/public/idl/base/nsIDOMWindowUtils.idl
dom/src/base/nsDOMWindowUtils.cpp
widget/public/nsIWidget.h
widget/src/cocoa/nsChildView.h
widget/src/cocoa/nsChildView.mm
widget/src/cocoa/nsMenuBarX.h
widget/src/cocoa/nsMenuBarX.mm
widget/src/cocoa/nsMenuUtilsX.h
widget/src/cocoa/nsMenuX.mm
widget/src/xpwidgets/nsBaseWidget.h
widget/tests/native_menus_window.xul
--- a/dom/public/idl/base/nsIDOMWindowUtils.idl
+++ b/dom/public/idl/base/nsIDOMWindowUtils.idl
@@ -42,17 +42,17 @@
  * to the current nsIDOMWindow.  Some of the methods may require
  * elevated privileges; the method implementations should contain the
  * necessary security checks.  Access this interface by calling
  * getInterface on a DOMWindow.
  */
 
 interface nsIDOMElement;
 
-[scriptable, uuid(6db7b95f-0a35-4c16-ac50-094adb3372f9)]
+[scriptable, uuid(440D036F-0879-4497-9364-35DE49F6849F)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -202,24 +202,23 @@ interface nsIDOMWindowUtils : nsISupport
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without UniversalXPConnect
    * privileges.
    */
   void activateNativeMenuItemAt(in AString indexString);
 
   /**
-   * See nsIWidget::ForceNativeMenuReload
+   * See nsIWidget::ForceUpdateNativeMenuAt
    *
-   * This is used for native menu system testing. Calling this forces a full
-   * reload of the menu system, reloading all native menus and their items.
-   * This is important for testing because changes to the DOM can affect the
-   * native menu system lazily.
+   * Cannot be accessed from unprivileged context (not content-accessible)
+   * Will throw a DOM security error if called without UniversalXPConnect
+   * privileges.
    */
-  void forceNativeMenuReload();
+  void forceUpdateNativeMenuAt(in AString indexString);
 
   /**
    * Focus the element aElement. The element should be in the same document
    * that the window is displaying. Pass null to blur the element, if any,
    * that currently has focus, and focus the document.
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without UniversalXPConnect
--- a/dom/src/base/nsDOMWindowUtils.cpp
+++ b/dom/src/base/nsDOMWindowUtils.cpp
@@ -379,34 +379,32 @@ nsDOMWindowUtils::ActivateNativeMenuItem
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return NS_ERROR_FAILURE;
 
   return widget->ActivateNativeMenuItemAt(indexString);
 }
 
-
 NS_IMETHODIMP
-nsDOMWindowUtils::ForceNativeMenuReload()
+nsDOMWindowUtils::ForceUpdateNativeMenuAt(const nsAString& indexString)
 {
   PRBool hasCap = PR_FALSE;
   if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
       || !hasCap)
     return NS_ERROR_DOM_SECURITY_ERR;
 
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  return widget->ForceNativeMenuReload();
+  return widget->ForceUpdateNativeMenuAt(indexString);
 }
 
-
 nsIWidget*
 nsDOMWindowUtils::GetWidget()
 {
   if (mWindow) {
     nsIDocShell *docShell = mWindow->GetDocShell();
     if (docShell) {
       nsCOMPtr<nsIPresShell> presShell;
       docShell->GetPresShell(getter_AddRefs(presShell));
--- a/widget/public/nsIWidget.h
+++ b/widget/public/nsIWidget.h
@@ -88,20 +88,20 @@ typedef nsEventStatus (* EVENT_CALLBACK)
 #define NS_NATIVE_PLUGIN_PORT 8
 #define NS_NATIVE_SCREEN      9
 #define NS_NATIVE_SHELLWIDGET 10      // Get the shell GtkWidget
 #ifdef XP_MACOSX
 #define NS_NATIVE_PLUGIN_PORT_QD    100
 #define NS_NATIVE_PLUGIN_PORT_CG    101
 #endif
 
-// 8c91457a-ef86-4da1-b4f9-36022dcc6c7e
+// 7E01D11D-DAFC-4A5E-8C0A-7442A2E17252
 #define NS_IWIDGET_IID \
-{ 0x8c91457a, 0xef86, 0x4da1, \
-  { 0xb4, 0xf9, 0x36, 0x02, 0x2d, 0xcc, 0x6c, 0x7e } }
+{ 0x7E01D11D, 0xDAFC, 0x4A5E, \
+  { 0x8C, 0x0A, 0x74, 0x42, 0xA2, 0xE1, 0x72, 0x52 } }
 
 // Hide the native window systems real window type so as to avoid
 // including native window system types and APIs. This is necessary
 // to ensure cross-platform code.
 typedef void* nsNativeWidget;
 
 /*
  * Window shadow styles
@@ -1129,26 +1129,44 @@ class nsIWidget : public nsISupports {
                                               PRUint32 aModifierFlags,
                                               const nsAString& aCharacters,
                                               const nsAString& aUnmodifiedCharacters) = 0;
 
     /**
      * Activates a native menu item at the position specified by the index
      * string. The index string is a string of positive integers separated
      * by the "|" (pipe) character. The last integer in the string represents
-     * the item index in a submenu located using the integers prior to it.
+     * the item index in a submenu located using the integers preceeding it.
      *
      * Example: 1|0|4
      * In this string, the first integer represents the top-level submenu
      * in the native menu bar. Since the integer is 1, it is the second submeu
      * in the native menu bar. Within that, the first item (index 0) is a
      * submenu, and we want to activate the 5th item within that submenu.
      */
     virtual nsresult ActivateNativeMenuItemAt(const nsAString& indexString) = 0;
 
+    /**
+     * This is used for native menu system testing.
+     *
+     * Updates a native menu at the position specified by the index string.
+     * The index string is a string of positive integers separated by the "|" 
+     * (pipe) character.
+     *
+     * Example: 1|0|4
+     * In this string, the first integer represents the top-level submenu
+     * in the native menu bar. Since the integer is 1, it is the second submeu
+     * in the native menu bar. Within that, the first item (index 0) is a
+     * submenu, and we want to update submenu at index 4 within that submenu.
+     *
+     * If this is called with an empty string it forces a full reload of the
+     * menu system.
+     */
+    virtual nsresult ForceUpdateNativeMenuAt(const nsAString& indexString) = 0;
+
     /*
      * Force Input Method Editor to commit the uncommited input
      */
     NS_IMETHOD ResetInputState()=0;
 
     /*
      * Following methods relates to IME 'Opened'/'Closed' state.
      * 'Opened' means the user can input any character. I.e., users can input Japanese  
@@ -1217,24 +1235,16 @@ class nsIWidget : public nsISupports {
      * NS_VK_SCROLL_LOCK.
      * aLEDState is the result for current LED state of the key.
      * If the LED is 'ON', it returns TRUE, otherwise, FALSE.
      * If the platform doesn't support the LED state (or we cannot get the
      * state), this method returns NS_ERROR_NOT_IMPLEMENTED.
      */
     NS_IMETHOD GetToggledKeyState(PRUint32 aKeyCode, PRBool* aLEDState) = 0;
 
-    /**
-     * This is used for native menu system testing. Calling this forces a full
-     * reload of the menu system, reloading all native menus and their items.
-     * This is important for testing because changes to the DOM can affect the
-     * native menu system lazily.
-     */
-    virtual nsresult ForceNativeMenuReload() = 0;
-
 protected:
     // keep the list of children.  We also keep track of our siblings.
     // The ownership model is as follows: parent holds a strong ref to
     // the first element of the list, and each element holds a strong
     // ref to the next element in the list.  The prevsibling and
     // lastchild pointers are weak, which is fine as long as they are
     // maintained properly.
     nsCOMPtr<nsIWidget> mFirstChild;
--- a/widget/src/cocoa/nsChildView.h
+++ b/widget/src/cocoa/nsChildView.h
@@ -385,17 +385,17 @@ public:
   NS_IMETHOD        SetCursor(imgIContainer* aCursor, PRUint32 aHotspotX, PRUint32 aHotspotY);
   
   NS_IMETHOD        CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent);
   NS_IMETHOD        SetTitle(const nsAString& title);
 
   NS_IMETHOD        GetAttention(PRInt32 aCycleCount);
 
   NS_IMETHOD        ActivateNativeMenuItemAt(const nsAString& indexString);
-  NS_IMETHOD        ForceNativeMenuReload();
+  NS_IMETHOD        ForceUpdateNativeMenuAt(const nsAString& indexString);
 
   NS_IMETHOD        ResetInputState();
   NS_IMETHOD        SetIMEOpenState(PRBool aState);
   NS_IMETHOD        GetIMEOpenState(PRBool* aState);
   NS_IMETHOD        SetIMEEnabled(PRUint32 aState);
   NS_IMETHOD        GetIMEEnabled(PRUint32* aState);
   NS_IMETHOD        CancelIMEComposition();
   NS_IMETHOD        GetToggledKeyState(PRUint32 aKeyCode,
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -66,16 +66,17 @@
 #include "nsILocalFileMac.h"
 #include "nsGfxCIID.h"
 #include "nsIMenuRollup.h"
 
 #include "nsDragService.h"
 #include "nsCursorManager.h"
 #include "nsWindowMap.h"
 #include "nsCocoaUtils.h"
+#include "nsMenuUtilsX.h"
 #include "nsMenuBarX.h"
 
 #include "nsIDOMSimpleGestureEvent.h"
 
 #include "gfxContext.h"
 #include "gfxQuartzSurface.h"
 
 #include <dlfcn.h>
@@ -1544,82 +1545,97 @@ nsresult nsChildView::SynthesizeNativeKe
     gOverrideKeyboardLayout = currentLayout;
   }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
-// Used for testing native menu system structure and event handling.
-NS_IMETHODIMP nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString)
-{
-  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
-
-  NSString* title = [NSString stringWithCharacters:indexString.BeginReading() length:indexString.Length()];
-  NSArray* indexes = [title componentsSeparatedByString:@"|"];
+
+// First argument has to be an NSMenu representing the application's top-level
+// menu bar. The returned item is *not* retained.
+static NSMenuItem* NativeMenuItemWithLocation(NSMenu* menubar, NSString* locationString)
+{
+  NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
   unsigned int indexCount = [indexes count];
   if (indexCount == 0)
-    return NS_OK;
-  
+    return nil;
+
   NSMenu* currentSubmenu = [NSApp mainMenu];
-  for (unsigned int i = 0; i < (indexCount - 1); i++) {
-    NSMenu* newSubmenu = nil;
+  for (unsigned int i = 0; i < indexCount; i++) {
     int targetIndex;
     // We remove the application menu from consideration for the top-level menu
     if (i == 0)
       targetIndex = [[indexes objectAtIndex:i] intValue] + 1;
     else
       targetIndex = [[indexes objectAtIndex:i] intValue];
     int itemCount = [currentSubmenu numberOfItems];
     if (targetIndex < itemCount) {
       NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
+      // if this is the last index just return the menu item
+      if (i == (indexCount - 1))
+        return menuItem;
+      // if this is not the last index find the submenu and keep going
       if ([menuItem hasSubmenu])
-        newSubmenu = [menuItem submenu];
+        currentSubmenu = [menuItem submenu];
+      else
+        return nil;
     }
-    
-    if (newSubmenu)
-      currentSubmenu = newSubmenu;
-    else
-      return NS_ERROR_FAILURE;
   }
 
-  int itemCount = [currentSubmenu numberOfItems];
-  int targetIndex = [[indexes objectAtIndex:(indexCount - 1)] intValue];
+  return nil;
+}
+
+
+// Used for testing native menu system structure and event handling.
+NS_IMETHODIMP nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  NSString* locationString = [NSString stringWithCharacters:indexString.BeginReading() length:indexString.Length()];
+  NSMenuItem* item = NativeMenuItemWithLocation([NSApp mainMenu], locationString);
   // We can't perform an action on an item with a submenu, that will raise
   // an obj-c exception.
-  if (targetIndex < itemCount && ![[currentSubmenu itemAtIndex:targetIndex] hasSubmenu]) {
+  if (item && ![item hasSubmenu]) {
+    NSMenu* parent = [item menu];
+    if (parent) {
       // NSLog(@"Performing action for native menu item titled: %@\n",
       //       [[currentSubmenu itemAtIndex:targetIndex] title]);
-      [currentSubmenu performActionForItemAtIndex:targetIndex];      
+      [parent performActionForItemAtIndex:[parent indexOfItem:item]];
+      return NS_OK;
+    }
   }
-  else {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
+  return NS_ERROR_FAILURE;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 
-NS_IMETHODIMP nsChildView::ForceNativeMenuReload()
-{
+// Used for testing native menu system structure and event handling.
+NS_IMETHODIMP nsChildView::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
   id windowDelegate = [[mView nativeWindow] delegate];
   if (windowDelegate && [windowDelegate isKindOfClass:[WindowDelegate class]]) {
     nsCocoaWindow *widget = [(WindowDelegate *)windowDelegate geckoWidget];
     if (widget) {
       nsMenuBarX* mb = widget->GetMenuBar();
       if (mb) {
-        mb->ForceNativeMenuReload();
+        if (indexString.IsEmpty())
+          mb->ForceNativeMenuReload();
+        else
+          mb->ForceUpdateNativeMenuAt(indexString);
       }
     }
   }
-
   return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 
 #pragma mark -
 
 
 #ifdef INVALIDATE_DEBUGGING
 
@@ -4009,22 +4025,22 @@ static nsEventStatus SendGeckoMouseEnter
     nsAutoRetainCocoaObject kungFuDeathGrip(self);
     mGeckoChild->DispatchWindowEvent(geckoEvent);
     if (!mGeckoChild)
       return;
 
     // dispatch scroll wheel carbon event for plugins
     {
       EventRef theEvent;
-      OSStatus err = ::MacCreateEvent(NULL,
-                                      kEventClassMouse,
-                                      kEventMouseWheelMoved,
-                                      TicksToEventTime(TickCount()),
-                                      kEventAttributeUserEvent,
-                                      &theEvent);
+      OSStatus err = ::CreateEvent(NULL,
+                                   kEventClassMouse,
+                                   kEventMouseWheelMoved,
+                                   TicksToEventTime(TickCount()),
+                                   kEventAttributeUserEvent,
+                                   &theEvent);
       if (err == noErr) {
         EventMouseWheelAxis axis;
         if (inAxis & nsMouseScrollEvent::kIsVertical)
           axis = kEventMouseWheelAxisY;
         else if (inAxis & nsMouseScrollEvent::kIsHorizontal)
           axis = kEventMouseWheelAxisX;
         
         SetEventParameter(theEvent,
--- a/widget/src/cocoa/nsMenuBarX.h
+++ b/widget/src/cocoa/nsMenuBarX.h
@@ -41,16 +41,18 @@
 
 #import <Cocoa/Cocoa.h>
 
 #include "nsMenuBaseX.h"
 #include "nsIMutationObserver.h"
 #include "nsHashtable.h"
 #include "nsINativeMenuService.h"
 #include "nsAutoPtr.h"
+#include "nsString.h"
+
 
 class nsMenuX;
 class nsMenuItemX;
 class nsChangeObserver;
 class nsIWidget;
 class nsIContent;
 class nsIDocument;
 
@@ -116,16 +118,17 @@ public:
   void              UnregisterForContentChanges(nsIContent* aContent);
   PRUint32          RegisterForCommand(nsMenuItemX* aItem);
   void              UnregisterCommand(PRUint32 aCommandID);
   PRUint32          GetMenuCount();
   bool              MenuContainsAppMenu();
   nsMenuX*          GetMenuAt(PRUint32 aIndex);
   nsMenuItemX*      GetMenuItemForCommandID(PRUint32 inCommandID);
   nsresult          Paint();
+  void              ForceUpdateNativeMenuAt(const nsAString& indexString);
   void              ForceNativeMenuReload(); // used for testing
 
 protected:
   void              ConstructNativeMenus();
   nsresult          InsertMenuAtIndex(nsMenuX* aMenu, PRUint32 aIndex);
   void              RemoveMenuAtIndex(PRUint32 aIndex);
   nsChangeObserver* LookupContentChangeObserver(nsIContent* aContent);
   void              HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode);
--- a/widget/src/cocoa/nsMenuBarX.mm
+++ b/widget/src/cocoa/nsMenuBarX.mm
@@ -257,16 +257,77 @@ void nsMenuBarX::RemoveMenuAtIndex(PRUin
     [mNativeMenu removeItemAtIndex:nativeMenuItemIndex];
 
   mMenuArray.RemoveElementAt(aIndex);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 
+void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+  NSString* locationString = [NSString stringWithCharacters:indexString.BeginReading() length:indexString.Length()];
+  NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+  unsigned int indexCount = [indexes count];
+  if (indexCount == 0)
+    return;
+
+  nsMenuX* currentMenu = NULL;
+  int targetIndex = [[indexes objectAtIndex:0] intValue];
+  int visible = 0;
+  PRUint32 length = mMenuArray.Length();
+  // first find a menu in the menu bar
+  for (unsigned int i = 0; i < length; i++) {
+    nsMenuX* menu = mMenuArray[i];
+    if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
+      visible++;
+      if (visible == (targetIndex + 1)) {
+        currentMenu = menu;
+        break;
+      }
+    }
+  }
+
+  if (!currentMenu)
+    return;
+
+  // fake open/close to cause lazy update to happen so submenus populate
+  nsMenuEvent menuEvent(PR_TRUE, NS_MENU_SELECTED, nsnull);
+  menuEvent.time = PR_IntervalNow();
+  menuEvent.mCommand = (PRUint32)_NSGetCarbonMenu(static_cast<NSMenu*>(currentMenu->NativeData()));
+  currentMenu->MenuOpened(menuEvent);
+  currentMenu->MenuClosed(menuEvent);
+
+  // now find the correct submenu
+  for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+    targetIndex = [[indexes objectAtIndex:i] intValue];
+    visible = 0;
+    length = currentMenu->GetItemCount();
+    for (unsigned int j = 0; j < length; j++) {
+      nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+      if (!targetMenu)
+        return;
+      if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+        visible++;
+        if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+          currentMenu = static_cast<nsMenuX*>(targetMenu);
+          // fake open/close to cause lazy update to happen
+          nsMenuEvent menuEvent(PR_TRUE, NS_MENU_SELECTED, nsnull);
+          menuEvent.time = PR_IntervalNow();
+          menuEvent.mCommand = (PRUint32)_NSGetCarbonMenu(static_cast<NSMenu*>(currentMenu->NativeData()));
+          currentMenu->MenuOpened(menuEvent);
+          currentMenu->MenuClosed(menuEvent);
+          break;
+        }
+      }
+    }
+  }
+}
+
+
 // Calling this forces a full reload of the menu system, reloading all native
 // menus and their items.
 // Without this testing is hard because changes to the DOM affect the native
 // menu system lazily.
 void nsMenuBarX::ForceNativeMenuReload()
 {
   // tear down everything
   while (GetMenuCount() > 0)
--- a/widget/src/cocoa/nsMenuUtilsX.h
+++ b/widget/src/cocoa/nsMenuUtilsX.h
@@ -39,21 +39,24 @@
 #ifndef nsMenuUtilsX_h_
 #define nsMenuUtilsX_h_
 
 #include "nscore.h"
 #include "nsGUIEvent.h"
 #include "nsMenuBaseX.h"
 
 #import <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
 
 class nsIContent;
 class nsString;
 class nsMenuBarX;
 
+extern "C" MenuRef _NSGetCarbonMenu(NSMenu* aMenu);
+
 // Namespace containing utility functions used in our native menu implementation.
 namespace nsMenuUtilsX
 {
   nsEventStatus DispatchCommandTo(nsIContent* aTargetContent);
   NSString*     CreateTruncatedCocoaLabel(const nsString& itemLabel); // returned object is not retained
   PRUint8       GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute);
   unsigned int  MacModifiersForGeckoModifiers(PRUint8 geckoModifiers);
   nsMenuBarX*   GetHiddenWindowMenuBar(); // returned object is not retained
--- a/widget/src/cocoa/nsMenuX.mm
+++ b/widget/src/cocoa/nsMenuX.mm
@@ -69,18 +69,16 @@
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsIXPConnect.h"
 
 // externs defined in nsChildView.mm
 extern nsIRollupListener * gRollupListener;
 extern nsIWidget         * gRollupWidget;
 
-extern "C" MenuRef _NSGetCarbonMenu(NSMenu* aMenu);
-
 static PRBool gConstructingMenu = PR_FALSE;
 static PRBool gMenuMethodsSwizzled = PR_FALSE;
 
 PRInt32 nsMenuX::sIndexingMenuLevel = 0;
 
 
 nsMenuX::nsMenuX()
 : mVisibleItemsCount(0), mParent(nsnull), mMenuBar(nsnull),
--- a/widget/src/xpwidgets/nsBaseWidget.h
+++ b/widget/src/xpwidgets/nsBaseWidget.h
@@ -133,17 +133,17 @@ public:
   NS_IMETHOD              BeginSecureKeyboardInput();
   NS_IMETHOD              EndSecureKeyboardInput();
   NS_IMETHOD              SetWindowTitlebarColor(nscolor aColor, PRBool aActive);
   virtual PRBool          ShowsResizeIndicator(nsIntRect* aResizerRect);
   virtual void            ConvertToDeviceCoordinates(nscoord  &aX,nscoord &aY) {}
   virtual void            FreeNativeData(void * data, PRUint32 aDataType) {}
   NS_IMETHOD              BeginResizeDrag(nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical);
   virtual nsresult        ActivateNativeMenuItemAt(const nsAString& indexString) { return NS_ERROR_NOT_IMPLEMENTED; }
-  virtual nsresult        ForceNativeMenuReload() { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult        ForceUpdateNativeMenuAt(const nsAString& indexString) { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              ResetInputState() { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              SetIMEOpenState(PRBool aState) { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              GetIMEOpenState(PRBool* aState) { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              SetIMEEnabled(PRUint32 aState) { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              GetIMEEnabled(PRUint32* aState) { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              CancelIMEComposition() { return NS_ERROR_NOT_IMPLEMENTED; }
   NS_IMETHOD              GetToggledKeyState(PRUint32 aKeyCode, PRBool* aLEDState) { return NS_ERROR_NOT_IMPLEMENTED; }
 
--- a/widget/tests/native_menus_window.xul
+++ b/widget/tests/native_menus_window.xul
@@ -83,24 +83,24 @@
       window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
     }
 
     function onTestsFinished() {
       window.close();
       window.opener.wrappedJSObject.SimpleTest.finish();
     }
 
-    // We need to force a native menu reload before testing any dom changes
-    // because dom changes can affect the native menu system lazily.
-    function forceNativeMenuReload() {
+    // Force a menu to update itself. All of the menus parents will be updated
+    // as well. An empty string will force a complete menu system reload.
+    function forceUpdateNativeMenuAt(location) {
       netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
       var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                                           getInterface(Components.interfaces.nsIDOMWindowUtils);
       try {
-        utils.forceNativeMenuReload();
+        utils.forceUpdateNativeMenuAt(location);
       }
       catch (e) {
         dump(e + "\n");
       }
     }
 
     var executedCommandID = "";
 
@@ -139,16 +139,17 @@
       const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
       var item = document.createElementNS(XUL_NS, "menuitem");
       item.setAttribute("label", aLabel);
       item.setAttribute("command", aCommandId);
       return item;
     }
 
     function runBaseMenuTests() {
+      forceUpdateNativeMenuAt("0|3");
       return testItem("0|0", "cmd_FooItem0") &&
              testItem("0|1", "cmd_FooItem1") &&
              testItem("0|3|0", "cmd_BarItem0") &&
              testItem("0|3|1", "cmd_BarItem1");
     }
 
     function onLoad() {
       var _delayedOnLoad = function() {
@@ -180,91 +181,92 @@
         //       * NewMenuItem2 *
         //       *******************************
         //       * NewMenu1   > * NewMenuItem3 * <- NewMenu1 submenu
         //       *******************************
         //                      * NewMenuItem4 *
         //                      ****************
         //                      * NewMenuItem5 *
         //                      ****************
-        menubarNode.appendChild(newMenu0);
         newMenu0.appendChild(newMenuPopup0);
         newMenuPopup0.appendChild(newMenuItem0);
         newMenuPopup0.appendChild(newMenuItem1);
         newMenuPopup0.appendChild(newMenuItem2);
         newMenuPopup0.appendChild(newMenu1);
         newMenu1.appendChild(newMenuPopup1);
         newMenuPopup1.appendChild(newMenuItem3);
         newMenuPopup1.appendChild(newMenuItem4);
         newMenuPopup1.appendChild(newMenuItem5);
+        //XXX - we have to append the menu to the top-level of the menu bar
+        // only after constructing it. If we append before construction, it is
+        // invalid because it has no children and we don't validate it if we add
+        // children later.
+        menubarNode.appendChild(newMenu0);
+        forceUpdateNativeMenuAt("1|3");
+        // Run basic tests again.
+        ok(runBaseMenuTests());
 
         // Error strings.
         var sa = "Command handler(s) should have activated";
         var sna = "Command handler(s) should not have activated";
 
-        // Run basic tests again.
-        forceNativeMenuReload();
-        ok(runBaseMenuTests());
-
         // Test middle items.
         ok(testItem("1|1", "cmd_NewItem1"), sa);
         ok(testItem("1|3|1", "cmd_NewItem4"), sa);
 
         // Hide newMenu0.
         newMenu0.setAttribute("hidden", "true");
-        forceNativeMenuReload();
         ok(runBaseMenuTests(), sa); // the base menu should still be unhidden
         ok(!testItem("1|0", ""), sna);
         ok(!testItem("1|1", ""), sna);
         ok(!testItem("1|2", ""), sna);
         ok(!testItem("1|3|0", ""), sna);
         ok(!testItem("1|3|1", ""), sna);
         ok(!testItem("1|3|2", ""), sna);
 
         // Show newMenu0.
         newMenu0.setAttribute("hidden", "false");
-        forceNativeMenuReload();
+        forceUpdateNativeMenuAt("1|3");
         ok(runBaseMenuTests(), sa);
         ok(testItem("1|0", "cmd_NewItem0"), sa);
         ok(testItem("1|1", "cmd_NewItem1"), sa);
         ok(testItem("1|2", "cmd_NewItem2"), sa);
         ok(testItem("1|3|0", "cmd_NewItem3"), sa);
         ok(testItem("1|3|1", "cmd_NewItem4"), sa);
         ok(testItem("1|3|2", "cmd_NewItem5"), sa);
 
         // Hide items.
         newMenuItem1.setAttribute("hidden", "true");
         newMenuItem4.setAttribute("hidden", "true");
-        forceNativeMenuReload();
+        forceUpdateNativeMenuAt("1|2");
         ok(runBaseMenuTests(), sa);
         ok(testItem("1|0", "cmd_NewItem0"), sa);
         ok(testItem("1|1", "cmd_NewItem2"), sa);
         ok(!testItem("1|2", ""), sna);
         ok(testItem("1|2|0", "cmd_NewItem3"), sa);
         ok(testItem("1|2|1", "cmd_NewItem5"), sa);
         ok(!testItem("1|2|2", ""), sna);
 
         // Show items.
         newMenuItem1.setAttribute("hidden", "false");
         newMenuItem4.setAttribute("hidden", "false");
-        forceNativeMenuReload();
+        forceUpdateNativeMenuAt("1|3");
         ok(runBaseMenuTests(), sa);
         ok(testItem("1|0", "cmd_NewItem0"), sa);
         ok(testItem("1|1", "cmd_NewItem1"), sa);
         ok(testItem("1|2", "cmd_NewItem2"), sa);
         ok(testItem("1|3|0", "cmd_NewItem3"), sa);
         ok(testItem("1|3|1", "cmd_NewItem4"), sa);
         ok(testItem("1|3|2", "cmd_NewItem5"), sa);
 
         // At this point in the tests the state of the menus has been returned
         // to the originally diagramed state.
 
         // Remove menu.
         menubarNode.removeChild(newMenu0);
-        forceNativeMenuReload();
         ok(runBaseMenuTests(), sa);
         ok(!testItem("1|0", ""), sna);
         ok(!testItem("1|1", ""), sna);
         ok(!testItem("1|2", ""), sna);
         ok(!testItem("1|3|0", ""), sna);
         ok(!testItem("1|3|1", ""), sna);
         ok(!testItem("1|3|2", ""), sna);
         // return state to original diagramed state
@@ -273,26 +275,27 @@
         // Test for bug 447042, make sure that adding a menu node with no children
         // to the menu bar and then adding another menu node with children works.
         // Menus with no children don't get their native menu items shown and that
         // caused internal arrays to get out of sync and an append crashed.
         var tmpMenu0 = createXULMenu("tmpMenu0");
         menubarNode.removeChild(newMenu0);
         menubarNode.appendChild(tmpMenu0);
         menubarNode.appendChild(newMenu0);
-        forceNativeMenuReload();
+        forceUpdateNativeMenuAt("1|3");
         ok(runBaseMenuTests());
         ok(testItem("1|0", "cmd_NewItem0"), sa);
         ok(testItem("1|1", "cmd_NewItem1"), sa);
         ok(testItem("1|2", "cmd_NewItem2"), sa);
         ok(testItem("1|3|0", "cmd_NewItem3"), sa);
         ok(testItem("1|3|1", "cmd_NewItem4"), sa);
         ok(testItem("1|3|2", "cmd_NewItem5"), sa);
         // return state to original diagramed state
         menubarNode.removeChild(tmpMenu0);
+        delete tmpMenu0;
 
         onTestsFinished();
       }
 
       setTimeout(_delayedOnLoad, 1000);
     }
 
   ]]></script>