Bug 383930/552341, allow usage of a property on popups instead of using document.popupNode, should fix leak of popupNode, r=neil,sr=roc

This commit is contained in:
Neil Deakin 2010-08-09 12:17:19 -04:00
Родитель c4ca33a0c8
Коммит d20855b708
25 изменённых файлов: 525 добавлений и 173 удалений

Просмотреть файл

@ -221,18 +221,6 @@ nsXULPopupListener::PreLaunchPopup(nsIDOMEvent* aMouseEvent)
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) {

Просмотреть файл

@ -378,7 +378,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXULDocument, nsXMLDocument)
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())
@ -388,7 +387,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXULDocument, nsXMLDocument)
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)
@ -1527,26 +1525,23 @@ nsXULDocument::GetHeight(PRInt32* aHeight)
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;
}
@ -1615,25 +1610,22 @@ nsXULDocument::GetPopupRangeOffset(PRInt32* aRangeOffset)
NS_IMETHODIMP
nsXULDocument::GetTooltipNode(nsIDOMNode** aNode)
{
if (mTooltipNode && !nsContentUtils::CanCallerAccess(mTooltipNode)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
*aNode = mTooltipNode;
NS_IF_ADDREF(*aNode);
return NS_OK;
}
*aNode = nsnull;
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
nsCOMPtr<nsIDOMNode> node = pm->GetLastTriggerTooltipNode(this);
if (node && nsContentUtils::CanCallerAccess(node))
node.swap(*aNode);
}
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;
}

Просмотреть файл

@ -19,7 +19,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=449457
<script type="application/javascript"><![CDATA[
/** Test for Bug 449457 **/
document.tooltipNode = document;
document.popupNode = document;
ok(true, "This is just a leak test");
]]></script>

Просмотреть файл

@ -48,10 +48,10 @@ 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:
@ -59,7 +59,8 @@ public:
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,

Просмотреть файл

@ -355,10 +355,10 @@ nsWindowRoot::GetControllerForCommand(const char * aCommand,
return NS_OK;
}
void
nsWindowRoot::GetPopupNode(nsIDOMNode** aNode)
nsIDOMNode*
nsWindowRoot::GetPopupNode()
{
NS_IF_ADDREF(*aNode = mPopupNode);
return mPopupNode;
}
void

Просмотреть файл

@ -95,7 +95,7 @@ public:
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)

Просмотреть файл

@ -43,7 +43,7 @@ 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;
@ -101,16 +101,4 @@ interface nsIDOMXULDocument : nsISupports
* - 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();
};

Просмотреть файл

@ -3340,7 +3340,7 @@ DocumentViewerImpl::GetPopupNode(nsIDOMNode** aNode)
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;

Просмотреть файл

@ -40,8 +40,10 @@
#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
{
/**
@ -136,12 +138,14 @@ interface nsIPopupBoxObject : nsISupports
* @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
@ -153,8 +157,11 @@ interface nsIPopupBoxObject : nsISupports
* @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:
@ -164,6 +171,12 @@ interface nsIPopupBoxObject : nsISupports
* 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++

Просмотреть файл

@ -404,9 +404,8 @@ public:
* 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.
*/
@ -518,6 +517,21 @@ public:
*/
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
@ -616,7 +630,7 @@ protected:
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();
@ -628,9 +642,9 @@ protected:
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,
@ -712,6 +726,8 @@ private:
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
@ -764,6 +780,10 @@ protected:
// 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

Просмотреть файл

@ -6,8 +6,8 @@ function boom() {
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");

Просмотреть файл

@ -78,6 +78,7 @@
#include "nsIEventStateManager.h"
#include "nsIBoxLayout.h"
#include "nsIPopupBoxObject.h"
#include "nsPIWindowRoot.h"
#include "nsIReflowCallback.h"
#include "nsBindingManager.h"
#include "nsIDocShellTreeOwner.h"
@ -356,7 +357,7 @@ nsMenuPopupFrame::GetShadowStyle()
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:
@ -512,6 +513,8 @@ void
nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
const nsAString& aAlign)
{
mTriggerContent = nsnull;
if (aAnchor.EqualsLiteral("topleft"))
mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
else if (aAnchor.EqualsLiteral("topright"))
@ -537,6 +540,7 @@ nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
void
nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
nsIContent* aTriggerContent,
const nsAString& aPosition,
PRInt32 aXPos, PRInt32 aYPos,
PRBool aAttributesOverride)
@ -545,6 +549,7 @@ nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
mPopupState = ePopupShowing;
mAnchorContent = aAnchorContent;
mTriggerContent = aTriggerContent;
mXPos = aXPos;
mYPos = aYPos;
mAdjustOffsetForContextMenu = PR_FALSE;
@ -643,13 +648,15 @@ nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
}
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;
@ -734,10 +741,10 @@ nsMenuPopupFrame::ShowPopup(PRBool aIsContextMenu, PRBool aSelectFirstItem)
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;
}
@ -775,6 +782,27 @@ nsMenuPopupFrame::HidePopup(PRBool aDeselectMenu, nsPopupState aNewState)
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) {
@ -813,9 +841,9 @@ nsMenuPopupFrame::HidePopup(PRBool aDeselectMenu, nsPopupState aNewState)
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);
}
}

Просмотреть файл

@ -230,6 +230,18 @@ public:
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; }
@ -237,6 +249,7 @@ public:
// 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);
@ -246,7 +259,8 @@ public:
* 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,
@ -366,6 +380,10 @@ protected:
// 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

Просмотреть файл

@ -124,24 +124,27 @@ 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;
}
@ -260,6 +263,33 @@ nsPopupBoxObject::GetPopupState(nsAString& aState)
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 ///////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -46,6 +46,7 @@
#include "nsIDOMNSEvent.h"
#include "nsIDOMNSUIEvent.h"
#include "nsIDOMXULElement.h"
#include "nsIXULDocument.h"
#include "nsIXULTemplateBuilder.h"
#include "nsIPrivateDOMEvent.h"
#include "nsEventDispatcher.h"
@ -67,6 +68,7 @@
#include "nsCaret.h"
#include "nsIDocument.h"
#include "nsPIDOMWindow.h"
#include "nsPIWindowRoot.h"
#include "nsFrameManager.h"
const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
@ -379,10 +381,10 @@ nsXULPopupManager::GetMenuFrameForContent(nsIContent* aContent)
}
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*
@ -403,10 +405,23 @@ nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, PRInt32* aOffset)
}
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));
@ -524,10 +539,12 @@ nsXULPopupManager::ShowMenu(nsIContent *aMenu,
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);
@ -551,14 +568,15 @@ nsXULPopupManager::ShowPopup(nsIContent* aPopup,
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);
@ -570,13 +588,14 @@ nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
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);
@ -590,11 +609,11 @@ nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
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);
@ -641,10 +660,6 @@ nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
PRBool aIsContextMenu,
PRBool aSelectFirstItem)
{
// clear these as they are no longer valid
mRangeParent = nsnull;
mRangeOffset = 0;
nsPopupType popupType = aPopupFrame->PopupType();
PRBool ismenu = (popupType == ePopupTypeMenu);
@ -663,9 +678,8 @@ nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
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());
}
}
@ -800,7 +814,7 @@ nsXULPopupManager::HidePopup(nsIContent* aPopup,
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,
@ -1075,11 +1089,11 @@ nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
{
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);
@ -1097,6 +1111,7 @@ nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
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.
@ -1133,15 +1148,20 @@ nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
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);
@ -1187,7 +1207,7 @@ nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
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 &&
@ -1241,11 +1261,9 @@ nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
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();
@ -1257,7 +1275,7 @@ nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
nsIFrame*
nsXULPopupManager::GetTopPopup(nsPopupType aType)
{
if (aType == ePopupTypePanel && mNoHidePanels)
if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels)
return mNoHidePanels->Frame();
nsMenuChainItem* item = GetTopVisibleMenu();
@ -1292,6 +1310,39 @@ nsXULPopupManager::GetVisiblePopups()
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)
{
@ -1370,9 +1421,8 @@ nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
}
// 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;
@ -1702,13 +1752,8 @@ nsXULPopupManager::HandleKeyboardNavigation(PRUint32 aKeyCode)
// 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;
}
}

Просмотреть файл

@ -46,6 +46,7 @@
#include "nsGkAtoms.h"
#include "nsIFrame.h"
#include "nsIPopupBoxObject.h"
#include "nsMenuPopupFrame.h"
#include "nsIServiceManager.h"
#ifdef MOZ_XUL
#include "nsIDOMNSDocument.h"
@ -156,6 +157,7 @@ nsXULTooltipListener::MouseOut(nsIDOMEvent* aMouseEvent)
return NS_OK;
#endif
#ifdef MOZ_XUL
// check to see if the mouse left the targetNode, and if so,
// hide the tooltip
if (currentTooltip) {
@ -164,26 +166,23 @@ nsXULTooltipListener::MouseOut(nsIDOMEvent* aMouseEvent)
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;
}
@ -449,8 +448,6 @@ nsXULTooltipListener::ShowTooltip()
}
#endif
nsCOMPtr<nsIDOMNode> targetNode = do_QueryReferent(mTargetNode);
xulDoc->SetTooltipNode(targetNode);
mCurrentTooltip = do_GetWeakReference(tooltipNode);
LaunchTooltip();
mTargetNode = nsnull;
@ -707,10 +704,6 @@ nsXULTooltipListener::DestroyTooltip()
// 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);

Просмотреть файл

@ -75,6 +75,7 @@ _TEST_FILES = test_bug360220.xul \
popup_trigger.js \
window_popup_button.xul \
window_popup_attribute.xul \
popup_childframe_node.xul \
test_tooltip.xul \
test_progressmeter.xul \
test_props.xul \
@ -115,6 +116,7 @@ _TEST_FILES = test_bug360220.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 \

Просмотреть файл

@ -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)"/>

Просмотреть файл

@ -34,6 +34,7 @@ var gTestStepIndex = 0;
var gTestEventIndex = 0;
var gAutoHide = false;
var gExpectedEventDetails = null;
var gExpectedTriggerNode = null;
var gWindowUtils;
function startPopupTests(tests)
@ -139,6 +140,18 @@ function eventOccurred(event)
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");

Просмотреть файл

@ -2,6 +2,7 @@ var gMenuPopup = null;
var gTrigger = null;
var gIsMenu = false;
var gScreenX = -1, gScreenY = -1;
var gCachedEvent = null;
function runTests()
{
@ -17,6 +18,8 @@ function runTests()
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
@ -30,8 +33,21 @@ 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
@ -136,7 +152,11 @@ var popupTests = [
// 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
@ -146,8 +166,17 @@ var popupTests = [
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
@ -204,16 +233,23 @@ var popupTests = [
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",
@ -319,9 +355,13 @@ var popupTests = [
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");
@ -346,9 +386,30 @@ var popupTests = [
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");
@ -681,6 +742,6 @@ var popupTests = [
var popup = document.getElementById("thepopup");
popup.parentNode.removeChild(popup);
}
},
}
];

Просмотреть файл

@ -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>

Просмотреть файл

@ -24,6 +24,8 @@
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">
@ -76,7 +78,7 @@ var popupTests = [
disableNonTestMouse(true);
synthesizeMouse(document.documentElement, 2, 2, { type: "mousemove" });
disableNonTestMouse(false);
},
}
},
{
testname: "hover inherited tooltip",
@ -97,6 +99,7 @@ var popupTests = [
"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" });
@ -104,8 +107,22 @@ var popupTests = [
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),
@ -131,6 +148,12 @@ var popupTests = [
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",
@ -211,7 +234,7 @@ var popupTests = [
is(gOriginalWidth, rect.right - rect.left, testname + " tooltip is original width");
is(gOriginalHeight, rect.bottom - rect.top, testname + " tooltip is original height");
}
},
}
];

Просмотреть файл

@ -16,6 +16,10 @@ window.opener.SimpleTest.waitForFocus(runTests, window);
<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"/>

Просмотреть файл

@ -34,4 +34,9 @@ window.opener.SimpleTest.waitForFocus(runTests, window);
</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>

Просмотреть файл

@ -24,6 +24,9 @@
<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"/>
@ -31,13 +34,14 @@
<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>
@ -47,12 +51,13 @@
<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>