Bug 383930/552341, allow usage of a property on popups instead of using document.popupNode, should fix leak of popupNode, r=neil,sr=roc
authorNeil Deakin <neil@mozilla.com>
Mon, 09 Aug 2010 12:17:19 -0400
changeset 49217 ed8906789d08
parent 49216 10e893c36559
child 49218 ed6dc0b505cc
child 50282 2cf27b575827
push id14948
push userneil@mozilla.com
push dateMon, 09 Aug 2010 16:18:03 +0000
treeherdermozilla-central@ed8906789d08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil, roc
bugs383930, 552341
milestone2.0b4pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 383930/552341, allow usage of a property on popups instead of using document.popupNode, should fix leak of popupNode, r=neil,sr=roc
content/xul/content/src/nsXULPopupListener.cpp
content/xul/document/src/nsXULDocument.cpp
content/xul/document/test/test_bug449457.xul
dom/base/nsPIWindowRoot.h
dom/base/nsWindowRoot.cpp
dom/base/nsWindowRoot.h
dom/interfaces/xul/nsIDOMXULDocument.idl
layout/base/nsDocumentViewer.cpp
layout/xul/base/public/nsIPopupBoxObject.idl
layout/xul/base/public/nsXULPopupManager.h
layout/xul/base/src/crashtests/434458-1.xul
layout/xul/base/src/nsMenuPopupFrame.cpp
layout/xul/base/src/nsMenuPopupFrame.h
layout/xul/base/src/nsPopupBoxObject.cpp
layout/xul/base/src/nsXULPopupManager.cpp
layout/xul/base/src/nsXULTooltipListener.cpp
toolkit/content/tests/widgets/Makefile.in
toolkit/content/tests/widgets/popup_childframe_node.xul
toolkit/content/tests/widgets/popup_shared.js
toolkit/content/tests/widgets/popup_trigger.js
toolkit/content/tests/widgets/test_contextmenu_nested.xul
toolkit/content/tests/widgets/test_tooltip.xul
toolkit/content/tests/widgets/window_popup_attribute.xul
toolkit/content/tests/widgets/window_popup_button.xul
toolkit/content/widgets/popup.xml
--- a/content/xul/content/src/nsXULPopupListener.cpp
+++ b/content/xul/content/src/nsXULPopupListener.cpp
@@ -216,28 +216,16 @@ nsXULPopupListener::PreLaunchPopup(nsIDO
   // submenu of an already-showing popup.  We don't need to do anything at all.
   nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
   if (!mIsContext) {
     nsIAtom *tag = targetContent ? targetContent->Tag() : nsnull;
     if (tag == nsGkAtoms::menu || tag == nsGkAtoms::menuitem)
       return NS_OK;
   }
 
-  // Get the document with the popup.
-  nsCOMPtr<nsIContent> content = do_QueryInterface(mElement);
-
-  // Turn the document into a XUL document so we can use SetPopupNode.
-  nsCOMPtr<nsIDOMXULDocument> xulDocument = do_QueryInterface(content->GetDocument());
-  if (!xulDocument) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Store clicked-on node in xul document for context menus and menu popups.
-  xulDocument->SetPopupNode(targetNode);
-
   nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(aMouseEvent));
 
   if (mIsContext) {
 #ifndef NS_CONTEXT_MENU_IS_MOUSEUP
     // If the context menu launches on mousedown,
     // we have to fire focus on the content we clicked on
     FireFocusOnTargetContent(targetNode);
 #endif
--- a/content/xul/document/src/nsXULDocument.cpp
+++ b/content/xul/document/src/nsXULDocument.cpp
@@ -373,27 +373,25 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
                                                      nsIDOMXULCommandDispatcher)
 
     PRUint32 i, count = tmp->mPrototypes.Length();
     for (i = 0; i < count; ++i) {
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mPrototypes[i]");
         cb.NoteXPCOMChild(static_cast<nsIScriptGlobalObjectOwner*>(tmp->mPrototypes[i]));
     }
 
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mTooltipNode)
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mLocalStore)
 
     if (tmp->mOverlayLoadObservers.IsInitialized())
         tmp->mOverlayLoadObservers.EnumerateRead(TraverseObservers, &cb);
     if (tmp->mPendingOverlayLoadNotifications.IsInitialized())
         tmp->mPendingOverlayLoadNotifications.EnumerateRead(TraverseObservers, &cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXULDocument, nsXMLDocument)
-    NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mTooltipNode)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(nsXULDocument, nsXMLDocument)
 NS_IMPL_RELEASE_INHERITED(nsXULDocument, nsXMLDocument)
 
 
 DOMCI_NODE_DATA(XULDocument, nsXULDocument)
 
@@ -1523,35 +1521,32 @@ nsXULDocument::GetHeight(PRInt32* aHeigh
 //----------------------------------------------------------------------
 //
 // nsIDOMXULDocument interface
 //
 
 NS_IMETHODIMP
 nsXULDocument::GetPopupNode(nsIDOMNode** aNode)
 {
-    // Get popup node.
-    nsresult rv = TrustedGetPopupNode(aNode); // addref happens here
-
-    if (NS_SUCCEEDED(rv) && *aNode && !nsContentUtils::CanCallerAccess(*aNode)) {
-        NS_RELEASE(*aNode);
-        return NS_ERROR_DOM_SECURITY_ERR;
-    }
-
-    return rv;
-}
-
-NS_IMETHODIMP
-nsXULDocument::TrustedGetPopupNode(nsIDOMNode** aNode)
-{
     *aNode = nsnull;
 
+    nsCOMPtr<nsIDOMNode> node;
     nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
     if (rootWin)
-        rootWin->GetPopupNode(aNode); // addref happens here
+        node = rootWin->GetPopupNode(); // addref happens here
+
+    if (!node) {
+        nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+        if (pm) {
+            node = pm->GetLastTriggerPopupNode(this);
+        }
+    }
+
+    if (node && nsContentUtils::CanCallerAccess(node))
+      node.swap(*aNode);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULDocument::SetPopupNode(nsIDOMNode* aNode)
 {
     if (aNode) {
@@ -1610,35 +1605,32 @@ nsXULDocument::GetPopupRangeOffset(PRInt
 
     *aRangeOffset = offset;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULDocument::GetTooltipNode(nsIDOMNode** aNode)
 {
-    if (mTooltipNode && !nsContentUtils::CanCallerAccess(mTooltipNode)) {
-        return NS_ERROR_DOM_SECURITY_ERR;
+    *aNode = nsnull;
+
+    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+    if (pm) {
+        nsCOMPtr<nsIDOMNode> node = pm->GetLastTriggerTooltipNode(this);
+        if (node && nsContentUtils::CanCallerAccess(node))
+            node.swap(*aNode);
     }
-    *aNode = mTooltipNode;
-    NS_IF_ADDREF(*aNode);
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsXULDocument::TrustedGetTooltipNode(nsIDOMNode** aNode)
-{
-    NS_IF_ADDREF(*aNode = mTooltipNode);
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULDocument::SetTooltipNode(nsIDOMNode* aNode)
 {
-    mTooltipNode = aNode;
+    // do nothing
     return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsXULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker)
 {
     *aTracker = mCommandDispatcher;
--- a/content/xul/document/test/test_bug449457.xul
+++ b/content/xul/document/test/test_bug449457.xul
@@ -14,13 +14,13 @@ https://bugzilla.mozilla.org/show_bug.cg
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449457"
      target="_blank">Mozilla Bug 449457</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
 
     /** Test for Bug 449457 **/
-    document.tooltipNode = document;
+    document.popupNode = document;
     ok(true, "This is just a leak test");
 
   ]]></script>
 </window>
--- a/dom/base/nsPIWindowRoot.h
+++ b/dom/base/nsPIWindowRoot.h
@@ -43,28 +43,29 @@
 #include "nsISupports.h"
 #include "nsPIDOMEventTarget.h"
 
 class nsPIDOMWindow;
 class nsIControllers;
 class nsIController;
 struct JSContext;
 
-// 2e26a297-6e40-41c1-81c9-7306571f955e
+// 426C1B56-E38A-435E-B291-BE1557F2A0A2
 #define NS_IWINDOWROOT_IID \
-{ 0x2e26a297, 0x6e40, 0x41c1, \
-  { 0x81, 0xc9, 0x73, 0x06, 0x57, 0x1f, 0x95, 0x5e } }
+{ 0x426c1b56, 0xe38a, 0x435e, \
+  { 0xb2, 0x91, 0xbe, 0x15, 0x57, 0xf2, 0xa0, 0xa2 } }
 
 class nsPIWindowRoot : public nsPIDOMEventTarget {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWINDOWROOT_IID)
 
   virtual nsPIDOMWindow* GetWindow()=0;
 
-  virtual void GetPopupNode(nsIDOMNode** aNode) = 0;
+  // get and set the node that is the context of a popup menu
+  virtual nsIDOMNode* GetPopupNode() = 0;
   virtual void SetPopupNode(nsIDOMNode* aNode) = 0;
 
   virtual nsresult GetControllerForCommand(const char *aCommand,
                                            nsIController** aResult) = 0;
   virtual nsresult GetControllers(nsIControllers** aResult) = 0;
 
   virtual void SetParentTarget(nsPIDOMEventTarget* aTarget) = 0;
   virtual nsPIDOMEventTarget* GetParentTarget() = 0;
--- a/dom/base/nsWindowRoot.cpp
+++ b/dom/base/nsWindowRoot.cpp
@@ -350,20 +350,20 @@ nsWindowRoot::GetControllerForCommand(co
       static_cast<nsGlobalWindow *>
                  (static_cast<nsIDOMWindowInternal *>(piWindow));
     focusedWindow = win->GetPrivateParent();
   }
   
   return NS_OK;
 }
 
-void
-nsWindowRoot::GetPopupNode(nsIDOMNode** aNode)
+nsIDOMNode*
+nsWindowRoot::GetPopupNode()
 {
-  NS_IF_ADDREF(*aNode = mPopupNode);
+  return mPopupNode;
 }
 
 void
 nsWindowRoot::SetPopupNode(nsIDOMNode* aNode)
 {
   mPopupNode = aNode;
 }
 
--- a/dom/base/nsWindowRoot.h
+++ b/dom/base/nsWindowRoot.h
@@ -90,17 +90,17 @@ public:
   // nsPIWindowRoot
 
   virtual nsPIDOMWindow* GetWindow();
 
   virtual nsresult GetControllers(nsIControllers** aResult);
   virtual nsresult GetControllerForCommand(const char * aCommand,
                                            nsIController** _retval);
 
-  virtual void GetPopupNode(nsIDOMNode** aNode);
+  virtual nsIDOMNode* GetPopupNode();
   virtual void SetPopupNode(nsIDOMNode* aNode);
 
   virtual void SetParentTarget(nsPIDOMEventTarget* aTarget)
   {
     mParent = aTarget;
   }
   virtual nsPIDOMEventTarget* GetParentTarget() { return mParent; }
 
--- a/dom/interfaces/xul/nsIDOMXULDocument.idl
+++ b/dom/interfaces/xul/nsIDOMXULDocument.idl
@@ -38,17 +38,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "domstubs.idl"
 
 interface nsIDOMXULCommandDispatcher;
 interface nsIObserver;
 interface nsIBoxObject;
 
-[scriptable, uuid(d55c39B4-b54a-4df5-9e68-09919e4538f9)]
+[scriptable, uuid(b16d13c3-837d-445d-8f56-05d83d9b9eae)]
 interface nsIDOMXULDocument : nsISupports
 {
   attribute nsIDOMNode                          popupNode;
 
   /**
    * These attributes correspond to trustedGetPopupNode().rangeOffset and
    * rangeParent. They will help you find where in the DOM the popup is
    * happening. Can be accessed from chrome only, and only during a popup
@@ -96,21 +96,9 @@ interface nsIDOMXULDocument : nsISupport
    *
    * NOTICE:  In the 2.0 timeframe this API will change such that the 
    *          implementation will fire a DOMXULOverlayMerged event upon merge
    *          completion rather than notifying an observer. Do not rely on this
    *          API's behavior _not_ to change because it will!
    *          - Ben Goodger (8/23/2005)
    */
   void                      loadOverlay(in DOMString url, in nsIObserver aObserver);
-
-  /**
-   * Get the popup node from this XUL document without doing a security check to
-   * make sure that the caller has access to this node. This is for use from C++
-   * callers that can indirectly be called from content.
-   */
-  [noscript] nsIDOMNode     trustedGetPopupNode();
-
-  /**
-   * Like trustedGetPopupNode, but gets the tooltip node instead.
-   */
-  [noscript] nsIDOMNode     trustedGetTooltipNode();
 };
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -3335,17 +3335,17 @@ DocumentViewerImpl::GetPopupNode(nsIDOMN
   // get the private dom window
   nsCOMPtr<nsPIDOMWindow> window(document->GetWindow());
   NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
   if (window) {
     nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
     NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
 
     // get the popup node
-    root->GetPopupNode(aNode); // addref happens here
+    NS_IF_ADDREF(*aNode = root->GetPopupNode());
   }
 
   return NS_OK;
 }
 
 // GetPopupLinkNode: return popup link node or fail
 nsresult
 DocumentViewerImpl::GetPopupLinkNode(nsIDOMNode** aNode)
--- a/layout/xul/base/public/nsIPopupBoxObject.idl
+++ b/layout/xul/base/public/nsIPopupBoxObject.idl
@@ -35,18 +35,20 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIBoxObject.idl"
 
 interface nsIDOMElement;
+interface nsIDOMNode;
+interface nsIDOMEvent;
 
-[scriptable, uuid(88DC87BF-83EC-4F45-847A-E04C15DDA2FA)]
+[scriptable, uuid(e4c3845b-97d2-4fdf-860e-949746d15fb9)]
 interface nsIPopupBoxObject : nsISupports
 {
   /**
    *  This method is deprecated. Use openPopup or openPopupAtScreen instead.
    */
   void showPopup(in nsIDOMElement srcContent, in nsIDOMElement popupContent,
                  in long xpos, in long ypos,
                  in wstring popupType, in wstring anchorAlignment, 
@@ -131,43 +133,54 @@ interface nsIPopupBoxObject : nsISupport
    * this case, position and attributesOverride are ignored.
    *
    * @param anchorElement the node to anchor the popup to, may be null
    * @param position manner is which to anchor the popup to node
    * @param x horizontal offset
    * @param y vertical offset
    * @param isContextMenu true for context menus, false for other popups
    * @param attributesOverride true if popup node attributes override position
+   * @param triggerEvent the event that triggered this popup (mouse click for example)
    */
   void openPopup(in nsIDOMElement anchorElement,
                  in AString position,
                  in long x, in long y,
                  in boolean isContextMenu,
-                 in boolean attributesOverride);
+                 in boolean attributesOverride,
+                 in nsIDOMEvent triggerEvent);
 
   /**
    * Open the popup at a specific screen position specified by x and y. This
    * position may be adjusted if it would cause the popup to be off of the
    * screen. The x and y coordinates are measured in CSS pixels, and like all
    * screen coordinates, are given relative to the top left of the primary
    * screen.
    *
    * @param isContextMenu true for context menus, false for other popups
    * @param x horizontal screen position
    * @param y vertical screen position
+   * @param triggerEvent the event that triggered this popup (mouse click for example)
    */
-  void openPopupAtScreen(in long x, in long y, in boolean isContextMenu);
+  void openPopupAtScreen(in long x, in long y,
+                         in boolean isContextMenu,
+                         in nsIDOMEvent triggerEvent);
 
   /**
    * Returns the state of the popup:
    *   closed - the popup is closed
    *   open - the popup is open
    *   showing - the popup is in the process of being shown
    *   hiding - the popup is in the process of being hidden
    */
   readonly attribute AString popupState;
+
+  /**
+   * The node that triggered the popup. If the popup is not open, will return
+   * null.
+   */
+  readonly attribute nsIDOMNode triggerNode;
 };
 
 %{C++
 nsresult
 NS_NewPopupBoxObject(nsIBoxObject** aResult);
 
 %}
--- a/layout/xul/base/public/nsXULPopupManager.h
+++ b/layout/xul/base/public/nsXULPopupManager.h
@@ -399,19 +399,18 @@ public:
   void ShowMenu(nsIContent *aMenu, PRBool aSelectFirstItem, PRBool aAsynchronous);
 
   /**
    * Open a popup, either anchored or unanchored. If aSelectFirstItem is
    * true, then the first item in the menu is selected. The arguments are
    * similar to those for nsIPopupBoxObject::OpenPopup.
    *
    * aTriggerEvent should be the event that triggered the event. This is used
-   * to determine the coordinates for the popupshowing event. This may be null
-   * if the popup was not triggered by an event, or the coordinates are not
-   * important. Note that this may be reworked in bug 383930.
+   * to determine the coordinates and trigger node for the popup. This may be
+   * null if the popup was not triggered by an event.
    *
    * This fires the popupshowing event synchronously.
    */
   void ShowPopup(nsIContent* aPopup,
                  nsIContent* aAnchorContent,
                  const nsAString& aPosition,
                  PRInt32 aXPos, PRInt32 aYPos,
                  PRBool aIsContextMenu,
@@ -514,16 +513,31 @@ public:
 
   /**
    * Return an array of all the open and visible popup frames for
    * menus, in order from top to bottom.
    */
   nsTArray<nsIFrame *> GetVisiblePopups();
 
   /**
+   * Get the node that last triggered a popup or tooltip in the document
+   * aDocument. aDocument must be non-null and be a document contained within
+   * the same window hierarchy as the popup to retrieve.
+   */
+  already_AddRefed<nsIDOMNode> GetLastTriggerPopupNode(nsIDocument* aDocument)
+  {
+    return GetLastTriggerNode(aDocument, PR_FALSE);
+  }
+
+  already_AddRefed<nsIDOMNode> GetLastTriggerTooltipNode(nsIDocument* aDocument)
+  {
+    return GetLastTriggerNode(aDocument, PR_TRUE);
+  }
+
+  /**
    * Return false if a popup may not be opened. This will return false if the
    * popup is already open, if the popup is in a content shell that is not
    * focused, or if it is a submenu of another menu that isn't open.
    */
   PRBool MayShowPopup(nsMenuPopupFrame* aFrame);
 
   /**
    * Indicate that the popup associated with aView has been moved to the
@@ -611,31 +625,31 @@ public:
 protected:
   nsXULPopupManager();
   ~nsXULPopupManager();
 
   // get the nsMenuFrame, if any, for the given content node
   nsMenuFrame* GetMenuFrameForContent(nsIContent* aContent);
 
   // get the nsMenuPopupFrame, if any, for the given content node
-  nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent);
+  nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent, PRBool aShouldFlush);
 
   // return the topmost menu, skipping over invisible popups
   nsMenuChainItem* GetTopVisibleMenu();
 
   // Hide all of the visible popups from the given list. aDeselectMenu
   // indicates whether to deselect the menu of popups when hiding; this
   // flag is passed as the first argument to HidePopup. This function
   // can cause style changes and frame destruction.
   void HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames,
                         PRBool aDeselectMenu);
 
-  // set the event that was used to trigger the popup, or null to
-  // clear the event details.
-  void SetTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup);
+  // set the event that was used to trigger the popup, or null to clear the
+  // event details. aTriggerContent will be set to the target of the event.
+  void InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup, nsIContent** aTriggerContent);
 
   // callbacks for ShowPopup and HidePopup as events may be done asynchronously
   void ShowPopupCallback(nsIContent* aPopup,
                          nsMenuPopupFrame* aPopupFrame,
                          PRBool aIsContextMenu,
                          PRBool aSelectFirstItem);
   void HidePopupCallback(nsIContent* aPopup,
                          nsMenuPopupFrame* aPopupFrame,
@@ -707,16 +721,18 @@ private:
    * handled and other default handling should not occur.
    */
   PRBool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem,
                                          nsMenuPopupFrame* aFrame,
                                          nsNavigationDirection aDir);
 
 protected:
 
+  already_AddRefed<nsIDOMNode> GetLastTriggerNode(nsIDocument* aDocument, PRBool aIsTooltip);
+
   /**
    * Set mouse capturing for the current popup. This traps mouse clicks that
    * occur outside the popup so that it can be closed up. aOldPopup should be
    * set to the popup that was previously the current popup.
    */
   void SetCaptureState(nsIContent *aOldPopup);
 
   /**
@@ -759,14 +775,18 @@ protected:
   // linked list of noautohide panels and tooltips.
   nsMenuChainItem* mNoHidePanels;
 
   // timer used for HidePopupAfterDelay
   nsCOMPtr<nsITimer> mCloseTimer;
 
   // a popup that is waiting on the timer
   nsMenuPopupFrame* mTimerMenu;
+
+  // the popup that is currently being opened, stored only during the
+  // popupshowing event
+  nsCOMPtr<nsIContent> mOpeningPopup;
 };
 
 nsresult
 NS_NewXULPopupManager(nsISupports** aResult);
 
 #endif
--- a/layout/xul/base/src/crashtests/434458-1.xul
+++ b/layout/xul/base/src/crashtests/434458-1.xul
@@ -1,18 +1,18 @@
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(boom, 30);" class="reftest-wait">
 
 <script>
 function boom() {
   var a = document.getElementById('a');
   var x = a.popupBoxObject;
   a.parentNode.removeChild(a);
   x.enableKeyboardNavigator(true);
-  x.openPopup(null, "after_start", 0, 0, false, false);
-  x.openPopupAtScreen(2, 2, false);
+  x.openPopup(null, "after_start", 0, 0, false, false, null);
+  x.openPopupAtScreen(2, 2, false, null);
   x.showPopup(document.documentElement, a, -1, -1, "popup", "topleft", "topleft");
   x.hidePopup();
   document.documentElement.removeAttribute("class");
 }
 
 </script>
 
 <menupopup id="a"/>
--- a/layout/xul/base/src/nsMenuPopupFrame.cpp
+++ b/layout/xul/base/src/nsMenuPopupFrame.cpp
@@ -73,16 +73,17 @@
 #include "nsReadableUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsLayoutUtils.h"
 #include "nsContentUtils.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsIEventStateManager.h"
 #include "nsIBoxLayout.h"
 #include "nsIPopupBoxObject.h"
+#include "nsPIWindowRoot.h"
 #include "nsIReflowCallback.h"
 #include "nsBindingManager.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIBaseWindow.h"
 #include "nsISound.h"
 #include "nsIRootBox.h"
 #include "nsIScreenManager.h"
 #include "nsIServiceManager.h"
@@ -351,17 +352,17 @@ nsMenuPopupFrame::GetShadowStyle()
     case NS_THEME_TOOLTIP:
       return NS_STYLE_WINDOW_SHADOW_TOOLTIP;
     case NS_THEME_MENUPOPUP:
       return NS_STYLE_WINDOW_SHADOW_MENU;
   }
   return NS_STYLE_WINDOW_SHADOW_DEFAULT;
 }
 
-// this class is used for dispatching popupshowing events asynchronously.
+// this class is used for dispatching popupshown events asynchronously.
 class nsXULPopupShownEvent : public nsRunnable
 {
 public:
   nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext)
     : mPopup(aPopup), mPresContext(aPresContext)
   {
   }
 
@@ -507,16 +508,18 @@ nsMenuPopupFrame::AdjustView()
     NS_DispatchToCurrentThread(event);
   }
 }
 
 void
 nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
                                               const nsAString& aAlign)
 {
+  mTriggerContent = nsnull;
+
   if (aAnchor.EqualsLiteral("topleft"))
     mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
   else if (aAnchor.EqualsLiteral("topright"))
     mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
   else if (aAnchor.EqualsLiteral("bottomleft"))
     mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
   else if (aAnchor.EqualsLiteral("bottomright"))
     mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
@@ -532,24 +535,26 @@ nsMenuPopupFrame::InitPositionFromAnchor
   else if (aAlign.EqualsLiteral("bottomright"))
     mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
   else
     mPopupAlignment = POPUPALIGNMENT_NONE;
 }
 
 void
 nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
+                                  nsIContent* aTriggerContent,
                                   const nsAString& aPosition,
                                   PRInt32 aXPos, PRInt32 aYPos,
                                   PRBool aAttributesOverride)
 {
   EnsureWidget();
 
   mPopupState = ePopupShowing;
   mAnchorContent = aAnchorContent;
+  mTriggerContent = aTriggerContent;
   mXPos = aXPos;
   mYPos = aYPos;
   mAdjustOffsetForContextMenu = PR_FALSE;
 
   // if aAttributesOverride is true, then the popupanchor, popupalign and
   // position attributes on the <popup> override those values passed in.
   // If false, those attributes are only used if the values passed in are empty
   if (aAnchorContent) {
@@ -638,23 +643,25 @@ nsMenuPopupFrame::InitializePopup(nsICon
       PRInt32 y = top.ToInteger(&err);
       if (NS_SUCCEEDED(err))
         mScreenYPos = y;
     }
   }
 }
 
 void
-nsMenuPopupFrame::InitializePopupAtScreen(PRInt32 aXPos, PRInt32 aYPos,
+nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent,
+                                          PRInt32 aXPos, PRInt32 aYPos,
                                           PRBool aIsContextMenu)
 {
   EnsureWidget();
 
   mPopupState = ePopupShowing;
   mAnchorContent = nsnull;
+  mTriggerContent = aTriggerContent;
   mScreenXPos = aXPos;
   mScreenYPos = aYPos;
   mPopupAnchor = POPUPALIGNMENT_NONE;
   mPopupAlignment = POPUPALIGNMENT_NONE;
   mIsContextMenu = aIsContextMenu;
   mAdjustOffsetForContextMenu = aIsContextMenu;
 }
 
@@ -729,20 +736,20 @@ nsMenuPopupFrame::ShowPopup(PRBool aIsCo
   mIsContextMenu = aIsContextMenu;
 
   PRBool hasChildren = PR_FALSE;
 
   if (mPopupState == ePopupShowing) {
     mPopupState = ePopupOpen;
     mIsOpenChanged = PR_TRUE;
 
-    nsIFrame* parent = GetParent();
-    if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
+    nsMenuFrame* menuFrame = GetParentMenu();
+    if (menuFrame) {
       nsWeakFrame weakFrame(this);
-      (static_cast<nsMenuFrame*>(parent))->PopupOpened();
+      menuFrame->PopupOpened();
       if (!weakFrame.IsAlive())
         return PR_FALSE;
     }
 
     // the frames for the child menus have not been created yet, so tell the
     // frame constructor to build them
     if (mFrames.IsEmpty() && !mGeneratedChildren) {
       PresContext()->PresShell()->FrameConstructor()->
@@ -770,16 +777,37 @@ nsMenuPopupFrame::HidePopup(PRBool aDese
 {
   NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible,
                "popup being set to unexpected state");
 
   // don't hide the popup when it isn't open
   if (mPopupState == ePopupClosed || mPopupState == ePopupShowing)
     return;
 
+  // clear the trigger content if the popup is being closed. But don't clear
+  // it if the popup is just being made invisible as a popuphiding or command
+  // event may want to retrieve it.
+  if (aNewState == ePopupClosed) {
+    // if the popup had a trigger node set, clear the global window popup node
+    // as well
+    if (mTriggerContent) {
+      nsIDocument* doc = mContent->GetCurrentDoc();
+      if (doc) {
+        nsPIDOMWindow* win = doc->GetWindow();
+        if (win) {
+          nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
+          if (root) {
+            root->SetPopupNode(nsnull);
+          }
+        }
+      }
+    }
+    mTriggerContent = nsnull;
+  }
+
   // when invisible and about to be closed, HidePopup has already been called,
   // so just set the new state to closed and return
   if (mPopupState == ePopupInvisible) {
     if (aNewState == ePopupClosed)
       mPopupState = ePopupClosed;
     return;
   }
 
@@ -808,19 +836,19 @@ nsMenuPopupFrame::HidePopup(PRBool aDese
   // This code may not the best solution, but we can leave it here until we find the better approach.
   nsIEventStateManager *esm = PresContext()->EventStateManager();
 
   PRInt32 state = esm->GetContentState(mContent);
 
   if (state & NS_EVENT_STATE_HOVER)
     esm->SetContentState(nsnull, NS_EVENT_STATE_HOVER);
 
-  nsIFrame* parent = GetParent();
-  if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
-    (static_cast<nsMenuFrame*>(parent))->PopupClosed(aDeselectMenu);
+  nsMenuFrame* menuFrame = GetParentMenu();
+  if (menuFrame) {
+    menuFrame->PopupClosed(aDeselectMenu);
   }
 }
 
 void
 nsMenuPopupFrame::InvalidateInternal(const nsRect& aDamageRect,
                                      nscoord aX, nscoord aY, nsIFrame* aForChild,
                                      PRUint32 aFlags)
 {
--- a/layout/xul/base/src/nsMenuPopupFrame.h
+++ b/layout/xul/base/src/nsMenuPopupFrame.h
@@ -225,33 +225,47 @@ public:
   // reset the current incremental search string, calculated in
   // FindMenuWithShortcut.
   nsMenuFrame* Enter();
 
   nsPopupType PopupType() const { return mPopupType; }
   PRBool IsMenu() { return mPopupType == ePopupTypeMenu; }
   PRBool IsOpen() { return mPopupState == ePopupOpen || mPopupState == ePopupOpenAndVisible; }
 
+  // returns the parent menupopup, if any
+  nsMenuFrame* GetParentMenu() {
+    nsIFrame* parent = GetParent();
+    if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
+      return static_cast<nsMenuFrame *>(parent);
+    }
+    return nsnull;
+  }
+
+  nsIContent* GetTriggerContent() { return mTriggerContent; }
+  void SetTriggerContent(nsIContent* aTriggerContent) { mTriggerContent = aTriggerContent; }
+
   // returns true if the popup is in a content shell, or false for a popup in
   // a chrome shell
   PRBool IsInContentShell() { return mInContentShell; }
 
   // the Initialize methods are used to set the anchor position for
   // each way of opening a popup.
   void InitializePopup(nsIContent* aAnchorContent,
+                       nsIContent* aTriggerContent,
                        const nsAString& aPosition,
                        PRInt32 aXPos, PRInt32 aYPos,
                        PRBool aAttributesOverride);
 
   /**
    * @param aIsContextMenu if true, then the popup is
    * positioned at a slight offset from aXPos/aYPos to ensure the
    * (presumed) mouse position is not over the menu.
    */
-  void InitializePopupAtScreen(PRInt32 aXPos, PRInt32 aYPos,
+  void InitializePopupAtScreen(nsIContent* aTriggerContent,
+                               PRInt32 aXPos, PRInt32 aYPos,
                                PRBool aIsContextMenu);
 
   void InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
                                       nsAString& aAnchor,
                                       nsAString& aAlign,
                                       PRInt32 aXPos, PRInt32 aYPos);
 
   // indicate that the popup should be opened
@@ -361,16 +375,20 @@ protected:
   void MoveToAttributePosition();
 
   nsString     mIncrementalString;  // for incremental typing navigation
 
   // the content that the popup is anchored to, if any, which may be in a
   // different document than the popup.
   nsCOMPtr<nsIContent> mAnchorContent;
 
+  // the content that triggered the popup, typically the node where the mouse
+  // was clicked. It will be cleared when the popup is hidden.
+  nsCOMPtr<nsIContent> mTriggerContent;
+
   nsMenuFrame* mCurrentMenu; // The current menu that is active.
 
   // A popup's preferred size may be different than its actual size stored in
   // mRect in the case where the popup was resized because it was too large
   // for the screen. The preferred size mPrefSize holds the full size the popup
   // would be before resizing. Computations are performed using this size.
   nsSize mPrefSize;
 
--- a/layout/xul/base/src/nsPopupBoxObject.cpp
+++ b/layout/xul/base/src/nsPopupBoxObject.cpp
@@ -119,34 +119,37 @@ nsPopupBoxObject::ShowPopup(nsIDOMElemen
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPopupBoxObject::OpenPopup(nsIDOMElement* aAnchorElement,
                             const nsAString& aPosition,
                             PRInt32 aXPos, PRInt32 aYPos,
                             PRBool aIsContextMenu,
-                            PRBool aAttributesOverride)
+                            PRBool aAttributesOverride,
+                            nsIDOMEvent* aTriggerEvent)
 {
   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   if (pm && mContent) {
     nsCOMPtr<nsIContent> anchorContent(do_QueryInterface(aAnchorElement));
     pm->ShowPopup(mContent, anchorContent, aPosition, aXPos, aYPos,
-                  aIsContextMenu, aAttributesOverride, PR_FALSE, nsnull);
+                  aIsContextMenu, aAttributesOverride, PR_FALSE, aTriggerEvent);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsPopupBoxObject::OpenPopupAtScreen(PRInt32 aXPos, PRInt32 aYPos, PRBool aIsContextMenu)
+nsPopupBoxObject::OpenPopupAtScreen(PRInt32 aXPos, PRInt32 aYPos,
+                                    PRBool aIsContextMenu,
+                                    nsIDOMEvent* aTriggerEvent)
 {
   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   if (pm && mContent)
-    pm->ShowPopupAtScreen(mContent, aXPos, aYPos, aIsContextMenu, nsnull);
+    pm->ShowPopupAtScreen(mContent, aXPos, aYPos, aIsContextMenu, aTriggerEvent);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPopupBoxObject::MoveTo(PRInt32 aLeft, PRInt32 aTop)
 {
   nsMenuPopupFrame *menuPopupFrame = GetMenuPopupFrame();
   if (menuPopupFrame) {
@@ -255,16 +258,43 @@ nsPopupBoxObject::GetPopupState(nsAStrin
         NS_NOTREACHED("Bad popup state");
         break;
     }
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsPopupBoxObject::GetTriggerNode(nsIDOMNode** aTriggerNode)
+{
+  *aTriggerNode = nsnull;
+
+  nsMenuPopupFrame *menuPopupFrame = GetMenuPopupFrame();
+  while (menuPopupFrame) {
+    nsIContent* triggerContent = menuPopupFrame->GetTriggerContent();
+    if (triggerContent) {
+      CallQueryInterface(triggerContent, aTriggerNode);
+      break;
+    }
+
+    // check up the menu hierarchy until a popup with a trigger node is found
+    nsMenuFrame* menuFrame = menuPopupFrame->GetParentMenu();
+    if (!menuFrame)
+      break;
+
+    nsMenuParent* parentPopup = menuFrame->GetMenuParent();
+    if (!parentPopup || !parentPopup->IsMenu())
+      break;
+
+    menuPopupFrame = static_cast<nsMenuPopupFrame *>(parentPopup);
+  }
+
+  return NS_OK;
+}
 
 // Creation Routine ///////////////////////////////////////////////////////////////////////
 
 nsresult
 NS_NewPopupBoxObject(nsIBoxObject** aResult)
 {
   *aResult = new nsPopupBoxObject;
   if (!*aResult)
--- a/layout/xul/base/src/nsXULPopupManager.cpp
+++ b/layout/xul/base/src/nsXULPopupManager.cpp
@@ -41,16 +41,17 @@
 #include "nsMenuBarFrame.h"
 #include "nsIPopupBoxObject.h"
 #include "nsMenuBarListener.h"
 #include "nsContentUtils.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMNSEvent.h"
 #include "nsIDOMNSUIEvent.h"
 #include "nsIDOMXULElement.h"
+#include "nsIXULDocument.h"
 #include "nsIXULTemplateBuilder.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsEventDispatcher.h"
 #include "nsEventStateManager.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsLayoutUtils.h"
 #include "nsIViewManager.h"
 #include "nsILookAndFeel.h"
@@ -62,16 +63,17 @@
 #include "nsPIDOMWindow.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIBaseWindow.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDOMMouseEvent.h"
 #include "nsCaret.h"
 #include "nsIDocument.h"
 #include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
 #include "nsFrameManager.h"
 
 const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
   {
     eNavigationDirection_Last,   // NS_VK_END
     eNavigationDirection_First,  // NS_VK_HOME
     eNavigationDirection_Start,  // NS_VK_LEFT
     eNavigationDirection_Before, // NS_VK_UP
@@ -374,20 +376,20 @@ nsMenuFrame*
 nsXULPopupManager::GetMenuFrameForContent(nsIContent* aContent)
 {
   // as ShowMenu is called from frames, don't flush to be safe.
   return static_cast<nsMenuFrame *>
                     (GetFrameOfTypeForContent(aContent, nsGkAtoms::menuFrame, PR_FALSE));
 }
 
 nsMenuPopupFrame*
-nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent)
+nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, PRBool aShouldFlush)
 {
   return static_cast<nsMenuPopupFrame *>
-                    (GetFrameOfTypeForContent(aContent, nsGkAtoms::menuPopupFrame, PR_TRUE));
+                    (GetFrameOfTypeForContent(aContent, nsGkAtoms::menuPopupFrame, aShouldFlush));
 }
 
 nsMenuChainItem*
 nsXULPopupManager::GetTopVisibleMenu()
 {
   nsMenuChainItem* item = mPopups;
   while (item && item->Frame()->PopupState() == ePopupInvisible)
     item = item->GetParent();
@@ -398,20 +400,33 @@ void
 nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, PRInt32* aOffset)
 {
   *aNode = mRangeParent;
   NS_IF_ADDREF(*aNode);
   *aOffset = mRangeOffset;
 }
 
 void
-nsXULPopupManager::SetTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup)
+nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup,
+                                    nsIContent** aTriggerContent)
 {
   mCachedMousePoint = nsIntPoint(0, 0);
 
+  if (aTriggerContent) {
+    *aTriggerContent = nsnull;
+    if (aEvent) {
+      // get the trigger content from the event
+      nsCOMPtr<nsIDOMEventTarget> target;
+      aEvent->GetTarget(getter_AddRefs(target));
+      if (target) {
+        CallQueryInterface(target, aTriggerContent);
+      }
+    }
+  }
+
   nsCOMPtr<nsIDOMNSUIEvent> uiEvent = do_QueryInterface(aEvent);
   if (uiEvent) {
     uiEvent->GetRangeParent(getter_AddRefs(mRangeParent));
     uiEvent->GetRangeOffset(&mRangeOffset);
 
     // get the event coordinates relative to the root frame of the document
     // containing the popup.
     nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(aEvent));
@@ -519,20 +534,22 @@ nsXULPopupManager::ShowMenu(nsIContent *
     onMenuBar = parent->IsMenuBar();
   }
 
   nsAutoString position;
   if (onMenuBar || !onmenu)
     position.AssignLiteral("after_start");
   else
     position.AssignLiteral("end_before");
-  popupFrame->InitializePopup(aMenu, position, 0, 0, PR_TRUE);
+
+  popupFrame->InitializePopup(aMenu, nsnull, position, 0, 0, PR_TRUE);
 
   if (aAsynchronous) {
-    SetTriggerEvent(nsnull, nsnull);
+    // there is no trigger event for menus
+    InitTriggerEvent(nsnull, nsnull, nsnull);
     nsCOMPtr<nsIRunnable> event =
       new nsXULPopupShowingEvent(popupFrame->GetContent(), aMenu, popupFrame->PopupType(),
                                  parentIsContextMenu, aSelectFirstItem);
     NS_DispatchToCurrentThread(event);
   }
   else {
     nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
     FirePopupShowingEvent(popupContent, aMenu,
@@ -546,60 +563,62 @@ nsXULPopupManager::ShowPopup(nsIContent*
                              nsIContent* aAnchorContent,
                              const nsAString& aPosition,
                              PRInt32 aXPos, PRInt32 aYPos,
                              PRBool aIsContextMenu,
                              PRBool aAttributesOverride,
                              PRBool aSelectFirstItem,
                              nsIDOMEvent* aTriggerEvent)
 {
-  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup);
+  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, PR_TRUE);
   if (!popupFrame || !MayShowPopup(popupFrame))
     return;
 
-  SetTriggerEvent(aTriggerEvent, aPopup);
+  nsCOMPtr<nsIContent> triggerContent;
+  InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
 
-  popupFrame->InitializePopup(aAnchorContent, aPosition, aXPos, aYPos,
-                              aAttributesOverride);
+  popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition,
+                              aXPos, aYPos, aAttributesOverride);
 
   FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
                         popupFrame->PopupType(), aIsContextMenu, aSelectFirstItem);
 }
 
 void
 nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
                                      PRInt32 aXPos, PRInt32 aYPos,
                                      PRBool aIsContextMenu,
                                      nsIDOMEvent* aTriggerEvent)
 {
-  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup);
+  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, PR_TRUE);
   if (!popupFrame || !MayShowPopup(popupFrame))
     return;
 
-  SetTriggerEvent(aTriggerEvent, aPopup);
+  nsCOMPtr<nsIContent> triggerContent;
+  InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
 
-  popupFrame->InitializePopupAtScreen(aXPos, aYPos, aIsContextMenu);
+  popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu);
 
   FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
                         popupFrame->PopupType(), aIsContextMenu, PR_FALSE);
 }
 
 void
 nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
                                             nsIContent* aAnchorContent,
                                             nsAString& aAnchor,
                                             nsAString& aAlign,
                                             PRInt32 aXPos, PRInt32 aYPos,
                                             PRBool aIsContextMenu)
 {
-  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup);
+  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, PR_TRUE);
   if (!popupFrame || !MayShowPopup(popupFrame))
     return;
 
-  SetTriggerEvent(nsnull, nsnull);
+  InitTriggerEvent(nsnull, aPopup, nsnull);
 
   popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor,
                                              aAlign, aXPos, aYPos);
 
   FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
                         popupFrame->PopupType(), aIsContextMenu, PR_FALSE);
 }
 
@@ -636,20 +655,16 @@ CheckCaretDrawingState() {
 }
 
 void
 nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
                                      nsMenuPopupFrame* aPopupFrame,
                                      PRBool aIsContextMenu,
                                      PRBool aSelectFirstItem)
 {
-  // clear these as they are no longer valid
-  mRangeParent = nsnull;
-  mRangeOffset = 0;
-
   nsPopupType popupType = aPopupFrame->PopupType();
   PRBool ismenu = (popupType == ePopupTypeMenu);
 
   nsMenuChainItem* item =
     new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
   if (!item)
     return;
 
@@ -658,19 +673,18 @@ nsXULPopupManager::ShowPopupCallback(nsI
   // attribute may be used to disable adding these event listeners for popups
   // that want to handle their own keyboard events.
   if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorekeys,
                            nsGkAtoms::_true, eCaseMatters))
     item->SetIgnoreKeys(PR_TRUE);
 
   if (ismenu) {
     // if the menu is on a menubar, use the menubar's listener instead
-    nsIFrame* parent = aPopupFrame->GetParent();
-    if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
-      nsMenuFrame* menuFrame = static_cast<nsMenuFrame *>(parent);
+    nsMenuFrame* menuFrame = aPopupFrame->GetParentMenu();
+    if (menuFrame) {
       item->SetOnMenuBar(menuFrame->IsOnMenuBar());
     }
   }
 
   // use a weak frame as the popup will set an open attribute if it is a menu
   nsWeakFrame weakFrame(aPopupFrame);
   PRBool hasChildren = aPopupFrame->ShowPopup(aIsContextMenu, aSelectFirstItem);
   ENSURE_TRUE(weakFrame.IsAlive());
@@ -795,17 +809,17 @@ nsXULPopupManager::HidePopup(nsIContent*
     if (state == ePopupHiding)
       return;
     // change the popup state to hiding. Don't set the hiding state if the
     // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
     // run again. In the invisible state, we just want the events to fire.
     if (state != ePopupInvisible)
       popupFrame->SetPopupState(ePopupHiding);
 
-    // for menus, popupToHide is always the frommost item in the list to hide.
+    // for menus, popupToHide is always the frontmost item in the list to hide.
     if (aAsynchronous) {
       nsCOMPtr<nsIRunnable> event =
         new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
                                   type, deselectMenu);
         NS_DispatchToCurrentThread(event);
     }
     else {
       FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
@@ -1070,21 +1084,21 @@ nsXULPopupManager::FirePopupShowingEvent
                                          nsIContent* aMenu,
                                          nsPresContext* aPresContext,
                                          nsPopupType aPopupType,
                                          PRBool aIsContextMenu,
                                          PRBool aSelectFirstItem)
 {
   nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
 
-  // XXXndeakin (bug 383930)
-  //   eventually, the popup events will be a different event type with
-  //   additional fields for the anchor node and position and so forth. This
-  //   is where those details would be retrieved. This removes the need for
-  //   all the globals people keep adding to nsIDOMXULDocument.
+  // cache the popup so that document.popupNode can retrieve the trigger node
+  // during the popupshowing event. It will be cleared below after the event
+  // has fired.
+  mOpeningPopup = aPopup;
+
   nsEventStatus status = nsEventStatus_eIgnore;
   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWING, nsnull, nsMouseEvent::eReal);
 
   // coordinates are relative to the root widget
   nsPresContext* rootPresContext =
     presShell->GetPresContext()->GetRootPresContext();
   if (rootPresContext) {
     rootPresContext->PresShell()->GetViewManager()->
@@ -1092,16 +1106,17 @@ nsXULPopupManager::FirePopupShowingEvent
   }
   else {
     event.widget = nsnull;
   }
 
   event.refPoint = mCachedMousePoint;
   nsEventDispatcher::Dispatch(aPopup, aPresContext, &event, nsnull, &status);
   mCachedMousePoint = nsIntPoint(0, 0);
+  mOpeningPopup = nsnull;
 
   // if a panel, blur whatever has focus so that the panel can take the focus.
   // This is done after the popupshowing event in case that event is cancelled.
   // Using noautofocus="true" will disable this behaviour, which is needed for
   // the autocomplete widget as it manages focus itself.
   if (aPopupType == ePopupTypePanel &&
       !aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
                            nsGkAtoms::_true, eCaseMatters)) {
@@ -1128,25 +1143,30 @@ nsXULPopupManager::FirePopupShowingEvent
   // Flush the notifications so that the frames are up to date before showing
   // the popup, otherwise the new frames will reflow after the popup appears,
   // causing the popup to flicker. Frame code always calls this asynchronously,
   // so this should be safe.
   nsIDocument *document = aPopup->GetCurrentDoc();
   if (document)
     document->FlushPendingNotifications(Flush_Layout);
 
+  // clear these as they are no longer valid
+  mRangeParent = nsnull;
+  mRangeOffset = 0;
+
   // get the frame again in case it went away
   nsIFrame* frame = aPopup->GetPrimaryFrame();
   if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
     nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame *>(frame);
 
-    // if the event was cancelled, don't open the popup, and reset it's
-    // state back to closed
+    // if the event was cancelled, don't open the popup, reset its state back
+    // to closed and clear its trigger content.
     if (status == nsEventStatus_eConsumeNoDefault) {
       popupFrame->SetPopupState(ePopupClosed);
+      popupFrame->SetTriggerContent(nsnull);
     }
     else {
       ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem);
     }
   }
 }
 
 void
@@ -1182,17 +1202,17 @@ nsXULPopupManager::FirePopupHidingEvent(
     }
   }
 
   // get frame again in case it went away
   nsIFrame* frame = aPopup->GetPrimaryFrame();
   if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
     nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame *>(frame);
 
-    // if the event was cancelled, don't hide the popup, and reset it's
+    // if the event was cancelled, don't hide the popup, and reset its
     // state back to open. Only popups in chrome shells can prevent a popup
     // from hiding.
     if (status == nsEventStatus_eConsumeNoDefault &&
         !popupFrame->IsInContentShell()) {
       popupFrame->SetPopupState(ePopupOpenAndVisible);
     }
     else {
       HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
@@ -1236,33 +1256,31 @@ nsXULPopupManager::IsPopupOpen(nsIConten
 
 PRBool
 nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
 {
   nsMenuChainItem* item = GetTopVisibleMenu();
   while (item) {
     nsMenuPopupFrame* popup = item->Frame();
     if (popup && popup->IsOpen()) {
-      nsIFrame* parent = popup->GetParent();
-      if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
-        nsMenuFrame* menuFrame = static_cast<nsMenuFrame *>(parent);
-        if (menuFrame->GetMenuParent() == aMenuParent)
-          return PR_TRUE;
+      nsMenuFrame* menuFrame = popup->GetParentMenu();
+      if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
+        return PR_TRUE;
       }
     }
     item = item->GetParent();
   }
 
   return PR_FALSE;
 }
 
 nsIFrame*
 nsXULPopupManager::GetTopPopup(nsPopupType aType)
 {
-  if (aType == ePopupTypePanel && mNoHidePanels)
+  if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels)
     return mNoHidePanels->Frame();
 
   nsMenuChainItem* item = GetTopVisibleMenu();
   while (item) {
     if (item->PopupType() == aType || aType == ePopupTypeAny)
       return item->Frame();
     item = item->GetParent();
   }
@@ -1287,16 +1305,49 @@ nsXULPopupManager::GetVisiblePopups()
     if (item->Frame()->PopupState() == ePopupOpenAndVisible)
       popups.AppendElement(static_cast<nsIFrame*>(item->Frame()));
     item = item->GetParent();
   }
 
   return popups;
 }
 
+already_AddRefed<nsIDOMNode>
+nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, PRBool aIsTooltip)
+{
+  if (!aDocument)
+    return nsnull;
+
+  nsCOMPtr<nsIDOMNode> node;
+
+  // if mOpeningPopup is set, it means that a popupshowing event is being
+  // fired. In this case, just use the cached node, as the popup is not yet in
+  // the list of open popups.
+  if (mOpeningPopup && mOpeningPopup->GetCurrentDoc() == aDocument &&
+      aIsTooltip == (mOpeningPopup->Tag() == nsGkAtoms::tooltip)) {
+    nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(mOpeningPopup, PR_FALSE);
+    if (popupFrame)
+      node = do_QueryInterface(popupFrame->GetTriggerContent());
+  }
+  else {
+    nsMenuChainItem* item = aIsTooltip ? mNoHidePanels : mPopups;
+    while (item) {
+      // look for a popup of the same type and document.
+      if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
+          item->Content()->GetCurrentDoc() == aDocument) {
+        node = do_QueryInterface(item->Frame()->GetTriggerContent());
+        break;
+      }
+      item = item->GetParent();
+    }
+  }
+
+  return node.forget();
+}
+
 PRBool
 nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
 {
   // if a popup's IsOpen method returns true, then the popup must always be in
   // the popup chain scanned in IsPopupOpen.
   NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
                "popup frame state doesn't match XULPopupManager open state");
 
@@ -1365,19 +1416,18 @@ nsXULPopupManager::MayShowPopup(nsMenuPo
   if (mainWidget) {
     PRInt32 sizeMode;
     mainWidget->GetSizeMode(&sizeMode);
     if (sizeMode == nsSizeMode_Minimized)
       return PR_FALSE;
   }
 
   // cannot open a popup that is a submenu of a menupopup that isn't open.
-  nsIFrame* parent = aPopup->GetParent();
-  if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
-    nsMenuFrame* menuFrame = static_cast<nsMenuFrame *>(parent);
+  nsMenuFrame* menuFrame = aPopup->GetParentMenu();
+  if (menuFrame) {
     nsMenuParent* parentPopup = menuFrame->GetMenuParent();
     if (parentPopup && !parentPopup->IsOpen())
       return PR_FALSE;
   }
 
   return PR_TRUE;
 }
 
@@ -1697,23 +1747,18 @@ nsXULPopupManager::HandleKeyboardNavigat
       // stop if the parent isn't a menu
       if (!nextitem->IsMenu())
         break;
 
       // check to make sure that the parent is actually the parent menu. It won't
       // be if the parent is in a different frame hierarchy, for example, for a
       // context menu opened on another menu.
       nsMenuParent* expectedParent = static_cast<nsMenuParent *>(nextitem->Frame());
-      nsIFrame* parent = item->Frame()->GetParent();
-      if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
-        nsMenuFrame* menuFrame = static_cast<nsMenuFrame *>(parent);
-        if (menuFrame->GetMenuParent() != expectedParent)
-          break;
-      }
-      else {
+      nsMenuFrame* menuFrame = item->Frame()->GetParentMenu();
+      if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) {
         break;
       }
     }
   }
 
   nsIFrame* itemFrame;
   if (item)
     itemFrame = item->Frame();
--- a/layout/xul/base/src/nsXULTooltipListener.cpp
+++ b/layout/xul/base/src/nsXULTooltipListener.cpp
@@ -41,16 +41,17 @@
 #include "nsIDOMEventTarget.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMXULDocument.h"
 #include "nsIDOMXULElement.h"
 #include "nsIDocument.h"
 #include "nsGkAtoms.h"
 #include "nsIFrame.h"
 #include "nsIPopupBoxObject.h"
+#include "nsMenuPopupFrame.h"
 #include "nsIServiceManager.h"
 #ifdef MOZ_XUL
 #include "nsIDOMNSDocument.h"
 #include "nsITreeView.h"
 #endif
 #include "nsGUIEvent.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIScriptContext.h"
@@ -151,44 +152,42 @@ nsXULTooltipListener::MouseOut(nsIDOMEve
     return NS_OK;
   }
 
 #ifdef DEBUG_crap
   if (mNeedTitletip)
     return NS_OK;
 #endif
 
+#ifdef MOZ_XUL
   // check to see if the mouse left the targetNode, and if so,
   // hide the tooltip
   if (currentTooltip) {
     // which node did the mouse leave?
     nsCOMPtr<nsIDOMEventTarget> eventTarget;
     aMouseEvent->GetTarget(getter_AddRefs(eventTarget));
     nsCOMPtr<nsIDOMNode> targetNode(do_QueryInterface(eventTarget));
 
-    // which node is our tooltip on?
-    nsCOMPtr<nsIDOMXULDocument> xulDoc(do_QueryInterface(currentTooltip->GetDocument()));
-    if (!xulDoc)     // remotely possible someone could have 
-      return NS_OK;  // removed tooltip from dom while it was open
-    nsCOMPtr<nsIDOMNode> tooltipNode;
-    xulDoc->TrustedGetTooltipNode (getter_AddRefs(tooltipNode));
-
-    // if they're the same, the mouse left the node the tooltip appeared on,
-    // close the tooltip.
-    if (tooltipNode == targetNode) {
-      HideTooltip();
-#ifdef MOZ_XUL
-      // reset special tree tracking
-      if (mIsSourceTree) {
-        mLastTreeRow = -1;
-        mLastTreeCol = nsnull;
+    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+    if (pm) {
+      nsCOMPtr<nsIDOMNode> tooltipNode =
+        pm->GetLastTriggerTooltipNode(currentTooltip->GetCurrentDoc());
+      if (tooltipNode == targetNode) {
+        // if the target node is the current tooltip target node, the mouse
+        // left the node the tooltip appeared on, so close the tooltip.
+        HideTooltip();
+        // reset special tree tracking
+        if (mIsSourceTree) {
+          mLastTreeRow = -1;
+          mLastTreeCol = nsnull;
+        }
       }
-#endif
     }
   }
+#endif
 
   return NS_OK;
 }
 
 //////////////////////////////////////////////////////////////////////////
 //// nsIDOMMouseMotionListener
 
 NS_IMETHODIMP
@@ -444,18 +443,16 @@ nsXULTooltipListener::ShowTooltip()
     if (sourceNode->GetDocument()) {
 #ifdef MOZ_XUL
       if (!mIsSourceTree) {
         mLastTreeRow = -1;
         mLastTreeCol = nsnull;
       }
 #endif
 
-      nsCOMPtr<nsIDOMNode> targetNode = do_QueryReferent(mTargetNode);
-      xulDoc->SetTooltipNode(targetNode);
       mCurrentTooltip = do_GetWeakReference(tooltipNode);
       LaunchTooltip();
       mTargetNode = nsnull;
 
       nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
       if (!currentTooltip)
         return NS_OK;
 
@@ -702,20 +699,16 @@ nsresult
 nsXULTooltipListener::DestroyTooltip()
 {
   nsCOMPtr<nsIDOMMouseListener> kungFuDeathGrip(this);
   nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip);
   if (currentTooltip) {
     // clear out the tooltip node on the document
     nsCOMPtr<nsIDocument> doc = currentTooltip->GetDocument();
     if (doc) {
-      nsCOMPtr<nsIDOMXULDocument> xulDoc(do_QueryInterface(doc));
-      if (xulDoc)
-        xulDoc->SetTooltipNode(nsnull);
-
       // remove the mousedown and keydown listener from document
       nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(doc));
       evtTarget->RemoveEventListener(NS_LITERAL_STRING("DOMMouseScroll"), static_cast<nsIDOMMouseListener*>(this), PR_TRUE);
       evtTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), static_cast<nsIDOMMouseListener*>(this), PR_TRUE);
       evtTarget->RemoveEventListener(NS_LITERAL_STRING("mouseup"), static_cast<nsIDOMMouseListener*>(this), PR_TRUE);
       evtTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), static_cast<nsIDOMMouseListener*>(this), PR_TRUE);
     }
 
--- a/toolkit/content/tests/widgets/Makefile.in
+++ b/toolkit/content/tests/widgets/Makefile.in
@@ -70,16 +70,17 @@ include $(topsrcdir)/config/rules.mk
 		test_scale.xul \
 		test_radio.xul \
 		test_tabbox.xul \
 		test_tooltip_noautohide.xul \
 		popup_shared.js \
 		popup_trigger.js \
 		window_popup_button.xul \
 		window_popup_attribute.xul \
+		popup_childframe_node.xul \
 		test_tooltip.xul \
 		test_progressmeter.xul \
 		test_props.xul \
 		test_statusbar.xul \
 		test_datepicker.xul \
 		test_timepicker.xul \
 		test_tree.xul \
 		test_tree_view.xul \
@@ -110,16 +111,17 @@ include $(topsrcdir)/config/rules.mk
 		test_mousecapture.xul \
 		test_mousecapture_area.html \
 		test_focus_anons.xul \
 		test_tabindex.xul \
 		test_mousescroll.xul \
 		test_scrollbar.xul \
 		test_sorttemplate.xul \
 		test_contextmenu_list.xul \
+		test_contextmenu_nested.xul \
 		test_videocontrols.html \
 		test_richlist_direction.xul \
 		test_videocontrols_video_direction.html \
 		test_videocontrols_audio_direction.html \
 		videocontrols_direction-1-ref.html \
 		videocontrols_direction-1a.html \
 		videocontrols_direction-1b.html \
 		videocontrols_direction-1c.html \
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/widgets/popup_childframe_node.xul
@@ -0,0 +1,2 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" width="80" height="80"
+        onclick="document.documentElement.setAttribute('data', 'x' + document.popupNode)"/>
--- a/toolkit/content/tests/widgets/popup_shared.js
+++ b/toolkit/content/tests/widgets/popup_shared.js
@@ -29,16 +29,17 @@
 const menuactiveAttribute = "_moz-menuactive";
 
 var gPopupTests = null;
 var gTestIndex = -1;
 var gTestStepIndex = 0;
 var gTestEventIndex = 0;
 var gAutoHide = false;
 var gExpectedEventDetails = null;
+var gExpectedTriggerNode = null;
 var gWindowUtils;
 
 function startPopupTests(tests)
 {
   document.addEventListener("popupshowing", eventOccurred, false);
   document.addEventListener("popupshown", eventOccurred, false);
   document.addEventListener("popuphiding", eventOccurred, false);
   document.addEventListener("popuphidden", eventOccurred, false);
@@ -134,16 +135,28 @@ function eventOccurred(event)
     var expectedState;
     switch (event.type) {
       case "popupshowing": expectedState = "showing"; break;
       case "popupshown": expectedState = "open"; break;
       case "popuphiding": expectedState = "hiding"; break;
       case "popuphidden": expectedState = "closed"; break;
     }
 
+    if (gExpectedTriggerNode && event.type == "popupshowing") {
+      if (gExpectedTriggerNode == "notset") // check against null instead
+        gExpectedTriggerNode = null;
+
+      is(event.originalTarget.triggerNode, gExpectedTriggerNode, test.testname + " popupshowing triggerNode");
+      var isTooltip = (event.target.localName == "tooltip");
+      is(document.popupNode, isTooltip ? null : gExpectedTriggerNode,
+         test.testname + " popupshowing document.popupNode");
+      is(document.tooltipNode, isTooltip ? gExpectedTriggerNode : null,
+         test.testname + " popupshowing document.tooltipNode");
+    }
+
     if (expectedState)
       is(event.originalTarget.state, expectedState,
          test.testname + " " + event.type + " state");
 
     if (matches) {
       gTestEventIndex++
       if (events.length <= gTestEventIndex)
         setTimeout(checkResult, 0);
--- a/toolkit/content/tests/widgets/popup_trigger.js
+++ b/toolkit/content/tests/widgets/popup_trigger.js
@@ -1,42 +1,58 @@
 var gMenuPopup = null;
 var gTrigger = null;
 var gIsMenu = false;
 var gScreenX = -1, gScreenY = -1;
+var gCachedEvent = null;
 
 function runTests()
 {
   if (screen.height < 768) {
     ok(false, "popup tests are likely to fail for screen heights less than 768 pixels");
   }
 
   gMenuPopup = document.getElementById("thepopup");
   gTrigger = document.getElementById("trigger");
 
   gIsMenu = gTrigger.boxObject instanceof Components.interfaces.nsIMenuBoxObject;
 
   var mouseFn = function(event) {
     gScreenX = event.screenX;
     gScreenY = event.screenY;
+    // cache the event so that we can use it in calls to openPopup
+    gCachedEvent = event;
   }
 
   // a hacky way to get the screen position of the document
   window.addEventListener("mousedown", mouseFn, false);
   synthesizeMouse(document.documentElement, 0, 0, { });
   window.removeEventListener("mousedown", mouseFn, false);
   startPopupTests(popupTests);
 }
 
 var popupTests = [
 {
   testname: "mouse click on trigger",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
-  test: function() { synthesizeMouse(gTrigger, 4, 4, { }); },
+  test: function() {
+    // for menus, no trigger will be set. For non-menus using the popup
+    // attribute, the trigger will be set to the node with the popup attribute
+    gExpectedTriggerNode = gIsMenu ? "notset" : gTrigger;
+    synthesizeMouse(gTrigger, 4, 4, { });
+  },
   result: function (testname) {
+    gExpectedTriggerNode = null;
+    is(gMenuPopup.triggerNode, gIsMenu ? null : gTrigger, testname + " triggerNode");
+    is(document.popupNode, gIsMenu ? null : gTrigger, testname + " document.popupNode");
+    is(document.tooltipNode, null, testname + " document.tooltipNode");
+    // check to ensure the popup node for a different document isn't used
+    if (window.opener)
+      is(window.opener.document.popupNode, null, testname + " opener.document.popupNode");
+
     checkActive(gMenuPopup, "", testname);
     checkOpen("trigger", testname);
     // if a menu, the popup should be opened underneath the menu in the
     // 'after_start' position, otherwise it is opened at the mouse position
     if (gIsMenu)
       compareEdge(gTrigger, gMenuPopup, "after_start", 0, 0, testname);
   }
 },
@@ -131,28 +147,41 @@ var popupTests = [
             "DOMMenuItemInactive item1", "DOMMenuInactive thepopup" ],
   test: function() {
     gMenuPopup.hidePopup();
     // XXXndeakin event simulation fires events outside of the platform specific
     // widget code so the popup capturing isn't handled. Thus, the menu won't
     // rollup this way.
     // synthesizeMouse(gTrigger, 0, -12, { });
   },
-  result: function(testname, step) { checkClosed("trigger", testname); }
+  result: function(testname, step) {
+    is(gMenuPopup.triggerNode, null, testname + " triggerNode");
+    is(document.popupNode, null, testname + " document.popupNode");
+    checkClosed("trigger", testname);
+  }
 },
 {
   // these tests check to ensure that passing an anchor and position
   // puts the popup in the right place
   testname: "open popup anchored",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
   autohide: "thepopup",
   steps: ["before_start", "before_end", "after_start", "after_end",
           "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"],
-  test: function(testname, step) { gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); },
-  result: function(testname, step) { compareEdge(gTrigger, gMenuPopup, step, 0, 0, testname); }
+  test: function(testname, step) {
+    gExpectedTriggerNode = "notset";
+    gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false);
+  },
+  result: function(testname, step) {
+    // no triggerNode because it was opened without passing an event
+    gExpectedTriggerNode = null;
+    is(gMenuPopup.triggerNode, null, testname + " triggerNode");
+    is(document.popupNode, null, testname + " document.popupNode");
+    compareEdge(gTrigger, gMenuPopup, step, 0, 0, testname);
+  }
 },
 {
   // these tests check the same but with a 10 pixel margin on the popup
   testname: "open popup anchored with margin",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
   autohide: "thepopup",
   steps: ["before_start", "before_end", "after_start", "after_end",
           "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"],
@@ -199,26 +228,33 @@ var popupTests = [
           "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"],
   test: function(testname, step) {
     gMenuPopup.setAttribute("position", step);
     gMenuPopup.openPopup(gTrigger, "", 0, 0, false, false);
   },
   result: function(testname, step) { compareEdge(gTrigger, gMenuPopup, step, 0, 0, testname); }
 },
 {
-  // this test checks to ensure that attributes override flag to openPopup
-  // can be used to override the popup's position
+  // this test checks to ensure that the attributes override flag to openPopup
+  // can be used to override the popup's position. This test also passes an
+  // event to openPopup to check the trigger node.
   testname: "open popup anchored with override",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
   test: function(testname, step) {
     // attribute overrides the position passed in
     gMenuPopup.setAttribute("position", "end_after");
-    gMenuPopup.openPopup(gTrigger, "before_start", 0, 0, false, true);
+    gExpectedTriggerNode = gCachedEvent.target;
+    gMenuPopup.openPopup(gTrigger, "before_start", 0, 0, false, true, gCachedEvent);
   },
-  result: function(testname, step) { compareEdge(gTrigger, gMenuPopup, "end_after", 0, 0, testname); }
+  result: function(testname, step) {
+    gExpectedTriggerNode = null;
+    is(gMenuPopup.triggerNode, gCachedEvent.target, testname + " triggerNode");
+    is(document.popupNode, gCachedEvent.target, testname + " document.popupNode");
+    compareEdge(gTrigger, gMenuPopup, "end_after", 0, 0, testname);
+  }
 },
 {
   testname: "close popup with escape",
   events: [ "popuphiding thepopup", "popuphidden thepopup",
             "DOMMenuInactive thepopup", ],
   test: function(testname, step) {
     synthesizeKey("VK_ESCAPE", { });
     checkClosed("trigger", testname);
@@ -314,19 +350,23 @@ var popupTests = [
   events: [ "popuphiding thepopup", "popuphidden thepopup",
             "DOMMenuInactive thepopup" ],
   test: function(testname, step) { gMenuPopup.hidePopup(); }
 },
 {
   testname: "open popup at screen",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
   test: function(testname, step) {
+    gExpectedTriggerNode = "notset";
     gMenuPopup.openPopupAtScreen(gScreenX + 24, gScreenY + 20, false);
   },
   result: function(testname, step) {
+    gExpectedTriggerNode = null;
+    is(gMenuPopup.triggerNode, null, testname + " triggerNode");
+    is(document.popupNode, null, testname + " document.popupNode");
     var rect = gMenuPopup.getBoundingClientRect();
     is(rect.left, 24, testname + " left");
     is(rect.top, 20, testname + " top");
     ok(rect.right, testname + " right is " + rect.right);
     ok(rect.bottom, testname + " bottom is " + rect.bottom);
   }
 },
 {
@@ -341,19 +381,40 @@ var popupTests = [
            ],
   test: function() { synthesizeKey("M", { }); },
   result: function(testname) { checkClosed("trigger", testname); }
 },
 {
   testname: "open context popup at screen",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
   test: function(testname, step) {
-    gMenuPopup.openPopupAtScreen(gScreenX + 8, gScreenY + 16, true);
+    gExpectedTriggerNode = gCachedEvent.target;
+    gMenuPopup.openPopupAtScreen(gScreenX + 8, gScreenY + 16, true, gCachedEvent);
   },
   result: function(testname, step) {
+    gExpectedTriggerNode = null;
+    is(gMenuPopup.triggerNode, gCachedEvent.target, testname + " triggerNode");
+    is(document.popupNode, gCachedEvent.target, testname + " document.popupNode");
+
+    var childframe = document.getElementById("childframe");
+    if (childframe) {
+      for (var t = 0; t < 2; t++) {
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        var child = childframe.contentDocument; 
+        var evt = child.createEvent("Event");
+        evt.initEvent("click", true, true);
+        child.documentElement.dispatchEvent(evt);
+        is(child.documentElement.getAttribute("data"), "xnull",
+           "cannot get popupNode from other document");
+        child.documentElement.setAttribute("data", "none");
+        // now try again with document.popupNode set explicitly
+        document.popupNode = gCachedEvent.target;
+      }
+    }
+
     var rect = gMenuPopup.getBoundingClientRect();
     is(rect.left, 10, testname + " left");
     is(rect.top, 18, testname + " top");
     ok(rect.right, testname + " right is " + rect.right);
     ok(rect.bottom, testname + " bottom is " + rect.bottom);
   }
 },
 {
@@ -676,11 +737,11 @@ var popupTests = [
   // remove the content nodes for the popup
   testname: "remove content",
   test: function(testname, step) {
     var submenupopup = document.getElementById("submenupopup");
     submenupopup.parentNode.removeChild(submenupopup);
     var popup = document.getElementById("thepopup");
     popup.parentNode.removeChild(popup);
   }
-},
+}
 
 ];
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/widgets/test_contextmenu_nested.xul
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Nested Context Menu Tests"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>      
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>      
+  <script type="application/javascript" src="popup_shared.js"></script>      
+
+<menupopup id="outercontext">
+  <menuitem label="Context One"/>
+  <menu id="outercontextmenu" label="Sub">
+    <menupopup id="innercontext">
+      <menuitem id="innercontextmenu" label="Sub Context One"/>
+    </menupopup>
+  </menu>
+</menupopup>
+
+<menupopup id="outermain">
+  <menuitem label="One"/>
+  <menu id="outermenu" label="Sub">
+    <menupopup id="innermain">
+      <menuitem id="innermenu" label="Sub One" context="outercontext"/>
+    </menupopup>
+  </menu>
+</menupopup>
+
+<button label="Check"/>
+
+<vbox id="popuparea" popup="outermain" width="20" height="20"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+var popupTests = [
+{
+  testname: "open outer popup",
+  events: [ "popupshowing outermain", "popupshown outermain" ],
+  test: function () synthesizeMouse($("popuparea"), 4, 4, {}),
+  result: function (testname) is($("outermain").triggerNode, $("popuparea"), testname)
+},
+{
+  testname: "open inner popup",
+  events: [ "DOMMenuItemActive outermenu", "popupshowing innermain", "popupshown innermain" ],
+  test: function () {
+    synthesizeMouse($("outermenu"), 4, 4, { type: "mousemove" });
+    synthesizeMouse($("outermenu"), 2, 2, { type: "mousemove" });
+  },
+  result: function (testname) {
+    is($("outermain").triggerNode, $("popuparea"), testname + " outer");
+    is($("innermain").triggerNode, $("popuparea"), testname + " inner");
+    is($("outercontext").triggerNode, null, testname + " outer context");
+  }
+},
+{
+  testname: "open outer context",
+  condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+  events: [ "popupshowing outercontext", "popupshown outercontext" ],
+  test: function () synthesizeMouse($("innermenu"), 4, 4, { type: "contextmenu", button: 2 }),
+  result: function (testname) {
+    is($("outermain").triggerNode, $("popuparea"), testname + " outer");
+    is($("innermain").triggerNode, $("popuparea"), testname + " inner");
+    is($("outercontext").triggerNode, $("innermenu"), testname + " outer context");
+  }
+},
+{
+  testname: "open inner context",
+  condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+  events: [ "DOMMenuItemActive outercontextmenu", "popupshowing innercontext", "popupshown innercontext" ],
+  test: function () {
+    synthesizeMouse($("outercontextmenu"), 4, 4, { type: "mousemove" });
+    synthesizeMouse($("outercontextmenu"), 2, 2, { type: "mousemove" });
+  },
+  result: function (testname) {
+    is($("outermain").triggerNode, $("popuparea"), testname + " outer");
+    is($("innermain").triggerNode, $("popuparea"), testname + " inner");
+    is($("outercontext").triggerNode, $("innermenu"), testname + " outer context");
+    is($("innercontext").triggerNode, $("innermenu"), testname + " inner context");
+  }
+},
+{
+  testname: "close context",
+  condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+  events: [ "popuphiding innercontext", "popuphidden innercontext",
+            "popuphiding outercontext", "popuphidden outercontext",
+            "DOMMenuInactive innercontext",
+            "DOMMenuItemInactive outercontextmenu", "DOMMenuItemInactive outercontextmenu",
+            "DOMMenuInactive outercontext" ],
+  test: function () $("outercontext").hidePopup()
+},
+{
+  testname: "hide menus",
+  events: [ "popuphiding innermain", "popuphidden innermain",
+            "popuphiding outermain", "popuphidden outermain",
+            "DOMMenuInactive innermain",
+            "DOMMenuItemInactive outermenu", "DOMMenuItemInactive outermenu",
+            "DOMMenuInactive outermain" ],
+
+  test: function () $("outermain").hidePopup(),
+  result: function (testname) {
+    is($("outermain").triggerNode, null, testname + " outer");
+    is($("innermain").triggerNode, null, testname + " inner");
+    is($("outercontext").triggerNode, null, testname + " outer context");
+    is($("innercontext").triggerNode, null, testname + " inner context");
+  }
+}
+];
+
+SimpleTest.waitForFocus(function runTest() startPopupTests(popupTests));
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml"><p id="display"/></body>
+
+</window>
--- a/toolkit/content/tests/widgets/test_tooltip.xul
+++ b/toolkit/content/tests/widgets/test_tooltip.xul
@@ -19,16 +19,18 @@
 <box tooltiptext="Box Tooltip">
   <button id="withtext" label="Tooltip Text" tooltiptext="Button Tooltip"
           style="-moz-appearance: none; padding: 0;"/>
   <button id="without" label="No Tooltip" style="-moz-appearance: none; padding: 0;"/>
   <!-- remove the native theme and borders to avoid some platform
        specific sizing differences -->
   <button id="withtooltip" label="Tooltip Element" tooltip="thetooltip"
           class="plain" style="-moz-appearance: none; padding: 0;"/>
+  <iframe id="childframe" type="content" width="10" height="10"
+          src="http://sectest2.example.org:80/tests/toolkit/content/tests/widgets/popup_childframe_node.xul"/>
 </box>
 
 <script class="testbody" type="application/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 var gOriginalWidth = -1;
 var gOriginalHeight = -1;
@@ -71,17 +73,17 @@ var popupTests = [
 {
   testname: "close tooltip",
   events: [ "popuphiding #tooltip", "popuphidden #tooltip",
             "DOMMenuInactive #tooltip" ],
   test: function() {
     disableNonTestMouse(true);
     synthesizeMouse(document.documentElement, 2, 2, { type: "mousemove" });
     disableNonTestMouse(false);
-  },
+  }
 },
 {
   testname: "hover inherited tooltip",
   events: [ "popupshowing #tooltip", "popupshown #tooltip" ],
   test: function() {
     gButton = document.getElementById("without");
     disableNonTestMouse(true);
     synthesizeMouse(gButton, 2, 2, { type: "mouseover" });
@@ -92,25 +94,40 @@ var popupTests = [
 },
 {
   testname: "hover tooltip attribute",
   events: [ "popuphiding #tooltip", "popuphidden #tooltip",
             "DOMMenuInactive #tooltip",
             "popupshowing thetooltip", "popupshown thetooltip" ],
   test: function() {
     gButton = document.getElementById("withtooltip");
+    gExpectedTriggerNode = gButton;
     disableNonTestMouse(true);
     synthesizeMouse(gButton, 2, 2, { type: "mouseover" });
     synthesizeMouse(gButton, 4, 4, { type: "mousemove" });
     synthesizeMouse(gButton, 6, 6, { type: "mousemove" });
     disableNonTestMouse(false);
   },
   result: function(testname) {
+    var tooltip = document.getElementById("thetooltip");
+    gExpectedTriggerNode = null;
+    is(tooltip.triggerNode, gButton, testname + " triggerNode");
+    is(document.popupNode, null, testname + " document.popupNode");
+    is(document.tooltipNode, gButton, testname + " document.tooltipNode");
+
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var child = $("childframe").contentDocument; 
+    var evt = child.createEvent("Event");
+    evt.initEvent("click", true, true);
+    child.documentElement.dispatchEvent(evt);
+    is(child.documentElement.getAttribute("data"), "xnull",
+       "cannot get tooltipNode from other document");
+
     var buttonrect = document.getElementById("withtooltip").getBoundingClientRect();
-    var rect = document.getElementById("thetooltip").getBoundingClientRect();
+    var rect = tooltip.getBoundingClientRect();
     var popupstyle = window.getComputedStyle(document.getElementById("thetooltip"), "");
 
     is(Math.round(rect.left),
        Math.round(buttonrect.left + parseFloat(popupstyle.marginLeft) + 6),
        testname + " top position of tooltip");
     is(Math.round(rect.top),
        Math.round(buttonrect.top + parseFloat(popupstyle.marginTop) + 6),
        testname + " top position of tooltip");
@@ -126,16 +143,22 @@ var popupTests = [
 {
   testname: "click to close tooltip",
   events: [ "popuphiding thetooltip", "popuphidden thetooltip",
             "command withtooltip", "DOMMenuInactive thetooltip" ],
   test: function() {
     gButton = document.getElementById("withtooltip");
     synthesizeMouse(gButton, 2, 2, { });
   },
+  result: function(testname) {
+    var tooltip = document.getElementById("thetooltip");
+    is(tooltip.triggerNode, null, testname + " triggerNode");
+    is(document.popupNode, null, testname + " document.popupNode");
+    is(document.tooltipNode, null, testname + " document.tooltipNode");
+  }
 },
 {
   testname: "hover tooltip after size increased",
   events: [ "popupshowing thetooltip", "popupshown thetooltip" ],
   test: function() {
     var label = document.getElementById("label");
     label.removeAttribute("value");
     label.textContent = "This is a longer tooltip than before\nIt has multiple lines\nIt is testing tooltip sizing\n";
@@ -206,17 +229,17 @@ var popupTests = [
 
     var labelrect = document.getElementById("label").getBoundingClientRect();
     ok(labelrect.right < rect.right, testname + " tooltip width");
     ok(labelrect.bottom < rect.bottom, testname + " tooltip height");
 
     is(gOriginalWidth, rect.right - rect.left, testname + " tooltip is original width");
     is(gOriginalHeight, rect.bottom - rect.top, testname + " tooltip is original height");
   }
-},
+}
 
 ];
 
 SimpleTest.waitForFocus(runTest);
 ]]>
 </script>
 
 <body xmlns="http://www.w3.org/1999/xhtml">
--- a/toolkit/content/tests/widgets/window_popup_attribute.xul
+++ b/toolkit/content/tests/widgets/window_popup_attribute.xul
@@ -11,16 +11,20 @@
 
 <script>
 window.opener.SimpleTest.waitForFocus(runTests, window);
 </script>
 
 <hbox style="margin-left: 325px; margin-top: 325px;">
   <label id="trigger" popup="thepopup" value="Popup"/>
 </hbox>
+<!-- this frame is used to check that document.popupNode
+     is inaccessible from different sources -->
+<iframe id="childframe" type="content" width="10" height="10"
+        src="http://sectest2.example.org:80/tests/toolkit/content/tests/widgets/popup_childframe_node.xul"/>
 
 <menupopup id="thepopup">
   <menuitem id="item1" label="First"/>
   <menuitem id="item2" label="Main Item"/>
   <menuitem id="amenu" label="A Menu" accesskey="M"/>
   <menuitem id="item3" label="Third"/>
   <menuitem id="one" label="One"/>
   <menuitem id="fancier" label="Fancier Menu"/>
--- a/toolkit/content/tests/widgets/window_popup_button.xul
+++ b/toolkit/content/tests/widgets/window_popup_button.xul
@@ -29,9 +29,14 @@ window.opener.SimpleTest.waitForFocus(ru
       </menu>
       <menuitem id="other" disabled="true" label="Other Menu"/>
       <menuitem id="secondlast" label="Second Last Menu" accesskey="T"/>
       <menuitem id="last" label="One Other Menu"/>
     </menupopup>
   </button>
 </hbox>
 
+<!-- this frame is used to check that document.popupNode
+     is inaccessible from different sources -->
+<iframe id="childframe" type="content" width="10" height="10"
+        src="http://sectest2.example.org:80/tests/toolkit/content/tests/widgets/popup_childframe_node.xul"/>
+
 </window>
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -19,45 +19,50 @@
         <getter>
           return this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject);
         </getter>
       </property>
 
       <property name="state" readonly="true"
                 onget="return this.popupBoxObject.popupState"/>
 
+      <property name="triggerNode" readonly="true"
+                onget="return this.popupBoxObject.triggerNode"/>
+
       <method name="openPopup">
         <parameter name="aAnchorElement"/>
         <parameter name="aPosition"/>
         <parameter name="aX"/>
         <parameter name="aY"/>
         <parameter name="aIsContextMenu"/>
         <parameter name="aAttributesOverride"/>
+        <parameter name="aTriggerEvent"/>
         <body>
         <![CDATA[
           try {
             var popupBox = this.popupBoxObject;
             if (popupBox)
               popupBox.openPopup(aAnchorElement, aPosition, aX, aY,
-                                 aIsContextMenu, aAttributesOverride);
+                                 aIsContextMenu, aAttributesOverride, aTriggerEvent);
           } catch(e) {}
         ]]>
         </body>
       </method>
 
       <method name="openPopupAtScreen">
         <parameter name="aX"/>
         <parameter name="aY"/>
         <parameter name="aIsContextMenu"/>
+        <parameter name="aTriggerEvent"/>
         <body>
         <![CDATA[
           try {
             var popupBox = this.popupBoxObject;
             if (popupBox)
-              popupBox.openPopupAtScreen(aX, aY, aIsContextMenu);
+              popupBox.openPopupAtScreen(aX, aY, aIsContextMenu, aTriggerEvent);
           } catch(e) {}
         ]]>
         </body>
       </method>
       
       <method name="showPopup">
         <parameter name="element"/>
         <parameter name="xpos"/>