diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2014-05-21 11:38:25 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2014-05-21 11:38:25 +0200 |
commit | d25ba7d760b017b038e5aa6c0a605b4a330eb68d (patch) | |
tree | 16ec27edc7d5f83986f16236d3a36a2682a0f37e /layout/xul | |
parent | a942906574671868daf122284a9c4689e6924f74 (diff) | |
download | palemoon-gre-d25ba7d760b017b038e5aa6c0a605b4a330eb68d.tar.gz |
Recommit working copy to repo with proper line endings.
Diffstat (limited to 'layout/xul')
359 files changed, 50460 insertions, 0 deletions
diff --git a/layout/xul/base/public/moz.build b/layout/xul/base/public/moz.build new file mode 100644 index 000000000..f4963b000 --- /dev/null +++ b/layout/xul/base/public/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsIBoxObject.idl', + 'nsIBrowserBoxObject.idl', + 'nsIContainerBoxObject.idl', + 'nsIEditorBoxObject.idl', + 'nsIIFrameBoxObject.idl', + 'nsIListBoxObject.idl', + 'nsIMenuBoxObject.idl', + 'nsIPopupBoxObject.idl', + 'nsIScrollBoxObject.idl', + 'nsISliderListener.idl', +] + +XPIDL_MODULE = 'layout_xul' + +MODULE = 'layout' + +EXPORTS += [ + 'nsIScrollbarMediator.h', + 'nsPIBoxObject.h', + 'nsXULPopupManager.h', +] + diff --git a/layout/xul/base/public/nsIBoxObject.idl b/layout/xul/base/public/nsIBoxObject.idl new file mode 100644 index 000000000..64b1a46f8 --- /dev/null +++ b/layout/xul/base/public/nsIBoxObject.idl @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMElement; + +[scriptable, uuid(ce572460-b0f2-4650-a9e7-c53a99d3b6ad)] +interface nsIBoxObject : nsISupports +{ + readonly attribute nsIDOMElement element; + + readonly attribute long x; + readonly attribute long y; + readonly attribute long screenX; + readonly attribute long screenY; + readonly attribute long width; + readonly attribute long height; + + nsISupports getPropertyAsSupports(in wstring propertyName); + void setPropertyAsSupports(in wstring propertyName, in nsISupports value); + wstring getProperty(in wstring propertyName); + void setProperty(in wstring propertyName, in wstring propertyValue); + void removeProperty(in wstring propertyName); + + // for stepping through content in the expanded dom with box-ordinal-group order + readonly attribute nsIDOMElement parentBox; + readonly attribute nsIDOMElement firstChild; + readonly attribute nsIDOMElement lastChild; + readonly attribute nsIDOMElement nextSibling; + readonly attribute nsIDOMElement previousSibling; +}; + +%{C++ +nsresult +NS_NewBoxObject(nsIBoxObject** aResult); + +%} diff --git a/layout/xul/base/public/nsIBrowserBoxObject.idl b/layout/xul/base/public/nsIBrowserBoxObject.idl new file mode 100644 index 000000000..f4f508a32 --- /dev/null +++ b/layout/xul/base/public/nsIBrowserBoxObject.idl @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIContainerBoxObject.idl" + +/** + * @deprecated Please consider using nsIContainerBoxObject. + */ + +[scriptable, uuid(db436f2f-c656-4754-b0fa-99bc353bd63f)] +interface nsIBrowserBoxObject : nsIContainerBoxObject +{ +}; + diff --git a/layout/xul/base/public/nsIContainerBoxObject.idl b/layout/xul/base/public/nsIContainerBoxObject.idl new file mode 100644 index 000000000..3d546cf6f --- /dev/null +++ b/layout/xul/base/public/nsIContainerBoxObject.idl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDocShell; +interface nsIBoxObject; + +[scriptable, uuid(35d4c04b-3bd3-4375-92e2-a818b4b4acb6)] +interface nsIContainerBoxObject : nsISupports +{ + readonly attribute nsIDocShell docShell; +}; + +%{C++ +nsresult +NS_NewContainerBoxObject(nsIBoxObject** aResult); +%} diff --git a/layout/xul/base/public/nsIEditorBoxObject.idl b/layout/xul/base/public/nsIEditorBoxObject.idl new file mode 100644 index 000000000..c5e54e409 --- /dev/null +++ b/layout/xul/base/public/nsIEditorBoxObject.idl @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIContainerBoxObject.idl" + +/** + * @deprecated Please consider using nsIContainerBoxObject. + */ + +[scriptable, uuid(e3800a23-5b83-49aa-b18c-efa1ac5416e0)] +interface nsIEditorBoxObject : nsIContainerBoxObject +{ +}; + diff --git a/layout/xul/base/public/nsIIFrameBoxObject.idl b/layout/xul/base/public/nsIIFrameBoxObject.idl new file mode 100644 index 000000000..966fce996 --- /dev/null +++ b/layout/xul/base/public/nsIIFrameBoxObject.idl @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIContainerBoxObject.idl" + +/** + * @deprecated Please consider using nsIContainerBoxObject. + */ + +[scriptable, uuid(30114c44-d398-44a5-9e01-b48b711291cd)] +interface nsIIFrameBoxObject : nsIContainerBoxObject +{ +}; + diff --git a/layout/xul/base/public/nsIListBoxObject.idl b/layout/xul/base/public/nsIListBoxObject.idl new file mode 100644 index 000000000..7d44f24cc --- /dev/null +++ b/layout/xul/base/public/nsIListBoxObject.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIBoxObject.idl" + +interface nsIDOMElement; + +[scriptable, uuid(AA9DEF4E-2E59-412d-A6DF-B76F52167795)] +interface nsIListBoxObject : nsISupports +{ + long getRowCount(); + long getNumberOfVisibleRows(); + long getIndexOfFirstVisibleRow(); + + void ensureIndexIsVisible(in long rowIndex); + void scrollToIndex(in long rowIndex); + void scrollByLines(in long numLines); + + nsIDOMElement getItemAtIndex(in long index); + long getIndexOfItem(in nsIDOMElement item); +}; + +%{C++ +nsresult +NS_NewListBoxObject(nsIBoxObject** aResult); + +%} diff --git a/layout/xul/base/public/nsIMenuBoxObject.idl b/layout/xul/base/public/nsIMenuBoxObject.idl new file mode 100644 index 000000000..3a3e85409 --- /dev/null +++ b/layout/xul/base/public/nsIMenuBoxObject.idl @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIBoxObject.idl" + +interface nsIDOMElement; +interface nsIDOMKeyEvent; + +[scriptable, uuid(689ebf3d-0184-450a-9bfa-5a26be0e7a8c)] +interface nsIMenuBoxObject : nsISupports +{ + void openMenu(in boolean openFlag); + + attribute nsIDOMElement activeChild; + + boolean handleKeyPress(in nsIDOMKeyEvent keyEvent); + + // true if the menu or menubar was opened via a keypress. + readonly attribute boolean openedWithKey; +}; + +%{C++ +nsresult +NS_NewMenuBoxObject(nsIBoxObject** aResult); + +%} diff --git a/layout/xul/base/public/nsIPopupBoxObject.idl b/layout/xul/base/public/nsIPopupBoxObject.idl new file mode 100644 index 000000000..a6b79cd5a --- /dev/null +++ b/layout/xul/base/public/nsIPopupBoxObject.idl @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIBoxObject.idl" + +interface nsIDOMElement; +interface nsIDOMNode; +interface nsIDOMEvent; +interface nsIDOMClientRect; + +[scriptable, uuid(b1192cac-467b-42ca-88d6-fcdec5bb4ef7)] +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, + in wstring popupAlignment); + + /** + * Hide the popup if it is open. + */ + void hidePopup(); + + /** + * Allow the popup to automatically position itself. + */ + attribute boolean autoPosition; + + /** + * If keyboard navigation is enabled, the keyboard may be used to navigate + * the menuitems on the popup. Enabling keyboard navigation is the default + * behaviour and will install capturing key event listeners on the popup + * that do not propagate key events to the contents. If you wish to place + * elements in a popup which accept key events, such as textboxes, keyboard + * navigation should be disabled. + * + * Setting ignorekeys="true" on the popup element also disables keyboard + * navigation, and is recommended over calling this method. + */ + void enableKeyboardNavigator(in boolean enableKeyboardNavigator); + + /** + * Enable automatic popup dismissal. This only has effect when called + * on an open popup. + */ + void enableRollup(in boolean enableRollup); + + /** + * Control whether the event that caused the popup to be automatically + * dismissed ("rolled up") should be consumed, or dispatched as a + * normal event. This should be set immediately before calling showPopup() + * if non-default behavior is desired. + */ + const uint32_t ROLLUP_DEFAULT = 0; /* widget/platform default */ + const uint32_t ROLLUP_CONSUME = 1; /* consume the rollup event */ + const uint32_t ROLLUP_NO_CONSUME = 2; /* don't consume the rollup event */ + void setConsumeRollupEvent(in uint32_t consume); + + /** + * Size the popup to the given dimensions + */ + void sizeTo(in long width, in long height); + + /** + * Move the popup to a point on screen in CSS pixels. + */ + void moveTo(in long left, in long top); + + /** + * Open the popup relative to a specified node at a specific location. + * + * The popup may be either anchored to another node or opened freely. + * To anchor a popup to a node, supply an anchor node and set the position + * to a string indicating the manner in which the popup should be anchored. + * Possible values for position are: + * before_start, before_end, after_start, after_end, + * start_before, start_after, end_before, end_after, + * overlap, after_pointer + * + * The anchor node does not need to be in the same document as the popup. + * + * If the attributesOverride argument is true, the popupanchor, popupalign + * and position attributes on the popup node override the position value + * argument. If attributesOverride is false, the attributes are only used + * if position is empty. + * + * For an anchored popup, the x and y arguments may be used to offset the + * popup from its anchored position by some distance, measured in CSS pixels. + * x increases to the right and y increases down. Negative values may also + * be used to move to the left and upwards respectively. + * + * Unanchored popups may be created by supplying null as the anchor node. + * An unanchored popup appears at the position specified by x and y, + * relative to the viewport of the document containing the popup node. In + * 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 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, + 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; + + /** + * Retrieve the anchor that was specified to openPopup or for menupopups in a + * menu, the parent menu. + */ + readonly attribute nsIDOMElement anchorNode; + + /** + * Retrieve the screen rectangle of the popup, including the area occupied by + * any titlebar or borders present. + */ + nsIDOMClientRect getOuterScreenRect(); + + /** + * Move an open popup to the given anchor position. The arguments have the same + * meaning as the corresponding argument to openPopup. This method has no effect + * on popups that are not open. + */ + void moveToAnchor(in nsIDOMElement anchorElement, + in AString position, + in long x, in long y, + in boolean attributesOverride); + + /** Returns the alignment position where the popup has appeared relative to its + * anchor node or point, accounting for any flipping that occurred. + */ + readonly attribute AString alignmentPosition; + readonly attribute long alignmentOffset; +}; + +%{C++ +nsresult +NS_NewPopupBoxObject(nsIBoxObject** aResult); + +%} diff --git a/layout/xul/base/public/nsIScrollBoxObject.idl b/layout/xul/base/public/nsIScrollBoxObject.idl new file mode 100644 index 000000000..5acb3a945 --- /dev/null +++ b/layout/xul/base/public/nsIScrollBoxObject.idl @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIBoxObject.idl" + +interface nsIDOMElement; + + +[scriptable, uuid(56E2ADA8-4631-11d4-BA11-001083023C1E)] +interface nsIScrollBoxObject : nsISupports +{ + /** + * Scroll to the given coordinates, in css pixels. + * (0,0) will put the top left corner of the scrolled element's padding-box + * at the top left corner of the scrollport (which is its inner-border-box). + * Values will be clamped to legal values. + */ + void scrollTo(in long x, in long y); + + /** + * Scroll the given amount of device pixels to the right and down. + * Values will be clamped to make the resuling position legal. + */ + void scrollBy(in long dx, in long dy); + + void scrollByLine(in long dlines); + void scrollByIndex(in long dindexes); + void scrollToLine(in long line); + void scrollToElement(in nsIDOMElement child); + void scrollToIndex(in long index); + + /** + * Get the current scroll position in css pixels. + * @see scrollTo for the definition of x and y. + */ + void getPosition(out long x, out long y); + + void getScrolledSize(out long width, out long height); + void ensureElementIsVisible(in nsIDOMElement child); + void ensureIndexIsVisible(in long index); + void ensureLineIsVisible(in long line); +}; + +%{C++ +nsresult +NS_NewScrollBoxObject(nsIBoxObject** aResult); + +%} diff --git a/layout/xul/base/public/nsIScrollbarMediator.h b/layout/xul/base/public/nsIScrollbarMediator.h new file mode 100644 index 000000000..873341666 --- /dev/null +++ b/layout/xul/base/public/nsIScrollbarMediator.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIScrollbarMediator_h___ +#define nsIScrollbarMediator_h___ + +#include "nsQueryFrame.h" + +class nsScrollbarFrame; + +class nsIScrollbarMediator +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsIScrollbarMediator) + + // The aScrollbar argument denotes the scrollbar that's firing the notification. + NS_IMETHOD PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) = 0; + NS_IMETHOD ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) = 0; + + NS_IMETHOD VisibilityChanged(bool aVisible) = 0; +}; + +#endif + diff --git a/layout/xul/base/public/nsISliderListener.idl b/layout/xul/base/public/nsISliderListener.idl new file mode 100644 index 000000000..9605782f0 --- /dev/null +++ b/layout/xul/base/public/nsISliderListener.idl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Used for <scale> to listen to slider changes to avoid mutation listeners + */ +[scriptable, uuid(e5b3074e-ee18-4538-83b9-2487d90a2a34)] +interface nsISliderListener : nsISupports +{ + /** + * Called when the current, minimum or maximum value has been changed to + * newValue. The which parameter will either be 'curpos', 'minpos' or 'maxpos'. + * If userChanged is true, then the user changed ths slider, otherwise it + * was changed via some other means. + */ + void valueChanged(in AString which, in long newValue, in boolean userChanged); + + /** + * Called when the user begins or ends dragging the thumb. + */ + void dragStateChanged(in boolean isDragging); +}; diff --git a/layout/xul/base/public/nsPIBoxObject.h b/layout/xul/base/public/nsPIBoxObject.h new file mode 100644 index 000000000..94f0b71ce --- /dev/null +++ b/layout/xul/base/public/nsPIBoxObject.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPIBoxObject_h___ +#define nsPIBoxObject_h___ + +// {2b8bb262-1b0f-4572-ba87-5d4ae4954445} +#define NS_PIBOXOBJECT_IID \ +{ 0x2b8bb262, 0x1b0f, 0x4572, \ + { 0xba, 0x87, 0x5d, 0x4a, 0xe4, 0x95, 0x44, 0x45 } } + + +class nsIContent; + +class nsPIBoxObject : public nsIBoxObject +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIBOXOBJECT_IID) + + virtual nsresult Init(nsIContent* aContent) = 0; + + // Drop the weak ref to the content node as needed + virtual void Clear() = 0; + + // The values cached by the implementation of this interface should be + // cleared when this method is called. + virtual void ClearCachedValues() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsPIBoxObject, NS_PIBOXOBJECT_IID) + +#endif + diff --git a/layout/xul/base/public/nsXULPopupManager.h b/layout/xul/base/public/nsXULPopupManager.h new file mode 100644 index 000000000..ee3fd1036 --- /dev/null +++ b/layout/xul/base/public/nsXULPopupManager.h @@ -0,0 +1,754 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The XUL Popup Manager keeps track of all open popups. + */ + +#ifndef nsXULPopupManager_h__ +#define nsXULPopupManager_h__ + +#include "prlog.h" +#include "nsGUIEvent.h" +#include "nsIContent.h" +#include "nsIRollupListener.h" +#include "nsIDOMEventListener.h" +#include "nsPoint.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsITimer.h" +#include "nsIReflowCallback.h" +#include "nsThreadUtils.h" +#include "nsStyleConsts.h" +#include "mozilla/Attributes.h" + +// X.h defines KeyPress +#ifdef KeyPress +#undef KeyPress +#endif + +/** + * There are two types that are used: + * - dismissable popups such as menus, which should close up when there is a + * click outside the popup. In this situation, the entire chain of menus + * above should also be closed. + * - panels, which stay open until a request is made to close them. This + * type is used by tooltips. + * + * When a new popup is opened, it is appended to the popup chain, stored in a + * linked list in mPopups for dismissable menus and panels or mNoHidePanels + * for tooltips and panels with noautohide="true". + * Popups are stored in this list linked from newest to oldest. When a click + * occurs outside one of the open dismissable popups, the chain is closed by + * calling Rollup. + */ + +class nsMenuFrame; +class nsMenuPopupFrame; +class nsMenuBarFrame; +class nsMenuParent; +class nsIDOMKeyEvent; +class nsIDocShellTreeItem; + +// when a menu command is executed, the closemenu attribute may be used +// to define how the menu should be closed up +enum CloseMenuMode { + CloseMenuMode_Auto, // close up the chain of menus, default value + CloseMenuMode_None, // don't close up any menus + CloseMenuMode_Single // close up only the menu the command is inside +}; + +/** + * nsNavigationDirection: an enum expressing navigation through the menus in + * terms which are independent of the directionality of the chrome. The + * terminology, derived from XSL-FO and CSS3 (e.g. + * http://www.w3.org/TR/css3-text/#TextLayout), is BASE (Before, After, Start, + * End), with the addition of First and Last (mapped to Home and End + * respectively). + * + * In languages such as English where the inline progression is left-to-right + * and the block progression is top-to-bottom (lr-tb), these terms will map out + * as in the following diagram + * + * --- inline progression ---> + * + * First | + * ... | + * Before | + * +--------+ block + * Start | | End progression + * +--------+ | + * After | + * ... | + * Last V + * + */ + +enum nsNavigationDirection { + eNavigationDirection_Last, + eNavigationDirection_First, + eNavigationDirection_Start, + eNavigationDirection_Before, + eNavigationDirection_End, + eNavigationDirection_After +}; + +#define NS_DIRECTION_IS_INLINE(dir) (dir == eNavigationDirection_Start || \ + dir == eNavigationDirection_End) +#define NS_DIRECTION_IS_BLOCK(dir) (dir == eNavigationDirection_Before || \ + dir == eNavigationDirection_After) +#define NS_DIRECTION_IS_BLOCK_TO_EDGE(dir) (dir == eNavigationDirection_First || \ + dir == eNavigationDirection_Last) + +PR_STATIC_ASSERT(NS_STYLE_DIRECTION_LTR == 0 && NS_STYLE_DIRECTION_RTL == 1); +PR_STATIC_ASSERT((NS_VK_HOME == NS_VK_END + 1) && + (NS_VK_LEFT == NS_VK_END + 2) && + (NS_VK_UP == NS_VK_END + 3) && + (NS_VK_RIGHT == NS_VK_END + 4) && + (NS_VK_DOWN == NS_VK_END + 5)); + +/** + * DirectionFromKeyCodeTable: two arrays, the first for left-to-right and the + * other for right-to-left, that map keycodes to values of + * nsNavigationDirection. + */ +extern const nsNavigationDirection DirectionFromKeyCodeTable[2][6]; + +#define NS_DIRECTION_FROM_KEY_CODE(frame, keycode) \ + (DirectionFromKeyCodeTable[frame->StyleVisibility()->mDirection] \ + [keycode - NS_VK_END]) + +// nsMenuChainItem holds info about an open popup. Items are stored in a +// doubly linked list. Note that the linked list is stored beginning from +// the lowest child in a chain of menus, as this is the active submenu. +class nsMenuChainItem +{ +private: + nsMenuPopupFrame* mFrame; // the popup frame + nsPopupType mPopupType; // the popup type of the frame + bool mIsContext; // true for context menus + bool mOnMenuBar; // true if the menu is on a menu bar + bool mIgnoreKeys; // true if keyboard listeners should not be used + + nsMenuChainItem* mParent; + nsMenuChainItem* mChild; + +public: + nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aIsContext, nsPopupType aPopupType) + : mFrame(aFrame), + mPopupType(aPopupType), + mIsContext(aIsContext), + mOnMenuBar(false), + mIgnoreKeys(false), + mParent(nullptr), + mChild(nullptr) + { + NS_ASSERTION(aFrame, "null frame passed to nsMenuChainItem constructor"); + MOZ_COUNT_CTOR(nsMenuChainItem); + } + + ~nsMenuChainItem() + { + MOZ_COUNT_DTOR(nsMenuChainItem); + } + + nsIContent* Content(); + nsMenuPopupFrame* Frame() { return mFrame; } + nsPopupType PopupType() { return mPopupType; } + bool IsMenu() { return mPopupType == ePopupTypeMenu; } + bool IsContextMenu() { return mIsContext; } + bool IgnoreKeys() { return mIgnoreKeys; } + bool IsOnMenuBar() { return mOnMenuBar; } + void SetIgnoreKeys(bool aIgnoreKeys) { mIgnoreKeys = aIgnoreKeys; } + void SetOnMenuBar(bool aOnMenuBar) { mOnMenuBar = aOnMenuBar; } + nsMenuChainItem* GetParent() { return mParent; } + nsMenuChainItem* GetChild() { return mChild; } + + // set the parent of this item to aParent, also changing the parent + // to have this as a child. + void SetParent(nsMenuChainItem* aParent); + + // removes an item from the chain. The root pointer must be supplied in case + // the item is the first item in the chain in which case the pointer will be + // set to the next item, or null if there isn't another item. After detaching, + // this item will not have a parent or a child. + void Detach(nsMenuChainItem** aRoot); +}; + +// this class is used for dispatching popupshowing events asynchronously. +class nsXULPopupShowingEvent : public nsRunnable +{ +public: + nsXULPopupShowingEvent(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem) + : mPopup(aPopup), + mIsContextMenu(aIsContextMenu), + mSelectFirstItem(aSelectFirstItem) + { + NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor"); + } + + NS_IMETHOD Run() MOZ_OVERRIDE; + +private: + nsCOMPtr<nsIContent> mPopup; + bool mIsContextMenu; + bool mSelectFirstItem; +}; + +// this class is used for dispatching popuphiding events asynchronously. +class nsXULPopupHidingEvent : public nsRunnable +{ +public: + nsXULPopupHidingEvent(nsIContent *aPopup, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPopupType aPopupType, + bool aDeselectMenu) + : mPopup(aPopup), + mNextPopup(aNextPopup), + mLastPopup(aLastPopup), + mPopupType(aPopupType), + mDeselectMenu(aDeselectMenu) + { + NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupHidingEvent constructor"); + // aNextPopup and aLastPopup may be null + } + + NS_IMETHOD Run() MOZ_OVERRIDE; + +private: + nsCOMPtr<nsIContent> mPopup; + nsCOMPtr<nsIContent> mNextPopup; + nsCOMPtr<nsIContent> mLastPopup; + nsPopupType mPopupType; + bool mDeselectMenu; +}; + +// this class is used for dispatching menu command events asynchronously. +class nsXULMenuCommandEvent : public nsRunnable +{ +public: + nsXULMenuCommandEvent(nsIContent *aMenu, + bool aIsTrusted, + bool aShift, + bool aControl, + bool aAlt, + bool aMeta, + bool aUserInput, + bool aFlipChecked) + : mMenu(aMenu), + mIsTrusted(aIsTrusted), + mShift(aShift), + mControl(aControl), + mAlt(aAlt), + mMeta(aMeta), + mUserInput(aUserInput), + mFlipChecked(aFlipChecked), + mCloseMenuMode(CloseMenuMode_Auto) + { + NS_ASSERTION(aMenu, "null menu supplied to nsXULMenuCommandEvent constructor"); + } + + NS_IMETHOD Run() MOZ_OVERRIDE; + + void SetCloseMenuMode(CloseMenuMode aCloseMenuMode) { mCloseMenuMode = aCloseMenuMode; } + +private: + nsCOMPtr<nsIContent> mMenu; + bool mIsTrusted; + bool mShift; + bool mControl; + bool mAlt; + bool mMeta; + bool mUserInput; + bool mFlipChecked; + CloseMenuMode mCloseMenuMode; +}; + +class nsXULPopupManager MOZ_FINAL : public nsIDOMEventListener, + public nsIRollupListener, + public nsITimerCallback, + public nsIObserver +{ + +public: + friend class nsXULPopupShowingEvent; + friend class nsXULPopupHidingEvent; + friend class nsXULMenuCommandEvent; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIDOMEVENTLISTENER + + // nsIRollupListener + virtual bool Rollup(uint32_t aCount, nsIContent** aLastRolledUp) MOZ_OVERRIDE; + virtual bool ShouldRollupOnMouseWheelEvent() MOZ_OVERRIDE; + virtual bool ShouldConsumeOnMouseWheelEvent() MOZ_OVERRIDE; + virtual bool ShouldRollupOnMouseActivate() MOZ_OVERRIDE; + virtual uint32_t GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain) MOZ_OVERRIDE; + virtual void NotifyGeometryChange() MOZ_OVERRIDE {} + virtual nsIWidget* GetRollupWidget() MOZ_OVERRIDE; + + static nsXULPopupManager* sInstance; + + // initialize and shutdown methods called by nsLayoutStatics + static nsresult Init(); + static void Shutdown(); + + // returns a weak reference to the popup manager instance, could return null + // if a popup manager could not be allocated + static nsXULPopupManager* GetInstance(); + + void AdjustPopupsOnWindowChange(nsPIDOMWindow* aWindow); + + // given a menu frame, find the prevous or next menu frame. If aPopup is + // true then navigate a menupopup, from one item on the menu to the previous + // or next one. This is used for cursor navigation between items in a popup + // menu. If aIsPopup is false, the navigation is on a menubar, so navigate + // between menus on the menubar. This is used for left/right cursor navigation. + // + // Items that are not valid, such as non-menu or non-menuitem elements are + // skipped, and the next or previous item after that is checked. + // + // If aStart is null, the first valid item is retrieved by GetNextMenuItem + // and the last valid item is retrieved by GetPreviousMenuItem. + // + // Both methods will loop around the beginning or end if needed. + // + // aParent - the parent menubar or menupopup + // aStart - the menu/menuitem to start navigation from. GetPreviousMenuItem + // returns the item before it, while GetNextMenuItem returns the + // item after it. + // aIsPopup - true for menupopups, false for menubars + static nsMenuFrame* GetPreviousMenuItem(nsIFrame* aParent, + nsMenuFrame* aStart, + bool aIsPopup); + static nsMenuFrame* GetNextMenuItem(nsIFrame* aParent, + nsMenuFrame* aStart, + bool aIsPopup); + + // returns true if the menu item aContent is a valid menuitem which may + // be navigated to. aIsPopup should be true for items on a popup, or false + // for items on a menubar. + static bool IsValidMenuItem(nsPresContext* aPresContext, + nsIContent* aContent, + bool aOnPopup); + + // inform the popup manager that a menu bar has been activated or deactivated, + // either because one of its menus has opened or closed, or that the menubar + // has been focused such that its menus may be navigated with the keyboard. + // aActivate should be true when the menubar should be focused, and false + // when the active menu bar should be defocused. In the latter case, if + // aMenuBar isn't currently active, yet another menu bar is, that menu bar + // will remain active. + void SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate); + + // retrieve the node and offset of the last mouse event used to open a + // context menu. This information is determined from the rangeParent and + // the rangeOffset of the event supplied to ShowPopup or ShowPopupAtScreen. + // This is used by the implementation of nsIDOMXULDocument::GetPopupRangeParent + // and nsIDOMXULDocument::GetPopupRangeOffset. + void GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset); + + /** + * Open a <menu> given its content node. If aSelectFirstItem is + * set to true, the first item on the menu will automatically be + * selected. If aAsynchronous is true, the event will be dispatched + * asynchronously. This should be true when called from frame code. + */ + void ShowMenu(nsIContent *aMenu, bool aSelectFirstItem, bool 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 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, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + bool aAttributesOverride, + bool aSelectFirstItem, + nsIDOMEvent* aTriggerEvent); + + /** + * Open a popup at a specific screen position specified by aXPos and aYPos, + * measured in CSS pixels. + * + * This fires the popupshowing event synchronously. + * + * If aIsContextMenu is true, the popup is positioned at a slight + * offset from aXPos/aYPos to ensure that it is not under the mouse + * cursor. + */ + void ShowPopupAtScreen(nsIContent* aPopup, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + nsIDOMEvent* aTriggerEvent); + + /** + * Open a tooltip at a specific screen position specified by aXPos and aYPos, + * measured in CSS pixels. + * + * This fires the popupshowing event synchronously. + */ + void ShowTooltipAtScreen(nsIContent* aPopup, + nsIContent* aTriggerContent, + int32_t aXPos, int32_t aYPos); + + /** + * This method is provided only for compatibility with an older popup API. + * New code should not call this function and should call ShowPopup instead. + * + * This fires the popupshowing event synchronously. + */ + void ShowPopupWithAnchorAlign(nsIContent* aPopup, + nsIContent* aAnchorContent, + nsAString& aAnchor, + nsAString& aAlign, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu); + + /* + * Hide a popup aPopup. If the popup is in a <menu>, then also inform the + * menu that the popup is being hidden. + * + * aHideChain - true if the entire chain of menus should be closed. If false, + * only this popup is closed. + * aDeselectMenu - true if the parent <menu> of the popup should be deselected. + * This will be false when the menu is closed by pressing the + * Escape key. + * aAsynchronous - true if the first popuphiding event should be sent + * asynchrously. This should be true if HidePopup is called + * from a frame. + * aLastPopup - optional popup to close last when hiding a chain of menus. + * If null, then all popups will be closed. + */ + void HidePopup(nsIContent* aPopup, + bool aHideChain, + bool aDeselectMenu, + bool aAsynchronous, + nsIContent* aLastPopup = nullptr); + + /** + * Hide the popup aFrame. This method is called by the view manager when the + * close button is pressed. + */ + void HidePopup(nsIFrame* aFrame); + + /** + * Hide a popup after a short delay. This is used when rolling over menu items. + * This timer is stored in mCloseTimer. The timer may be cancelled and the popup + * closed by calling KillMenuTimer. + */ + void HidePopupAfterDelay(nsMenuPopupFrame* aPopup); + + /** + * Hide all of the popups from a given docshell. This should be called when the + * document is hidden. + */ + void HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide); + + /** + * Execute a menu command from the triggering event aEvent. + * + * aMenu - a menuitem to execute + * aEvent - an nsXULMenuCommandEvent that contains all the info from the mouse + * event which triggered the menu to be executed, may not be null + */ + void ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent); + + /** + * Return true if the popup for the supplied content node is open. + */ + bool IsPopupOpen(nsIContent* aPopup); + + /** + * Return true if the popup for the supplied menu parent is open. + */ + bool IsPopupOpenForMenuParent(nsMenuParent* aMenuParent); + + /** + * Return the frame for the topmost open popup of a given type, or null if + * no popup of that type is open. If aType is ePopupTypeAny, a menu of any + * type is returned, except for popups in the mNoHidePanels list. + */ + nsIFrame* GetTopPopup(nsPopupType aType); + + /** + * Return an array of all the open and visible popup frames for + * menus, in order from top to bottom. + */ + void GetVisiblePopups(nsTArray<nsIFrame *>& aPopups); + + /** + * 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, false); + } + + already_AddRefed<nsIDOMNode> GetLastTriggerTooltipNode(nsIDocument* aDocument) + { + return GetLastTriggerNode(aDocument, 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. + */ + bool MayShowPopup(nsMenuPopupFrame* aFrame); + + /** + * Indicate that the popup associated with aView has been moved to the + * specified screen coordiates. + */ + void PopupMoved(nsIFrame* aFrame, nsIntPoint aPoint); + + /** + * Indicate that the popup associated with aView has been resized to the + * specified screen width and height. + */ + void PopupResized(nsIFrame* aFrame, nsIntSize ASize); + + /** + * Called when a popup frame is destroyed. In this case, just remove the + * item and later popups from the list. No point going through HidePopup as + * the frames have gone away. + */ + void PopupDestroyed(nsMenuPopupFrame* aFrame); + + /** + * Returns true if there is a context menu open. If aPopup is specified, + * then the context menu must be later in the chain than aPopup. If aPopup + * is null, returns true if any context menu at all is open. + */ + bool HasContextMenu(nsMenuPopupFrame* aPopup); + + /** + * Update the commands for the menus within the menu popup for a given + * content node. aPopup should be a XUL menupopup element. This method + * changes attributes on the children of aPopup, and deals only with the + * content of the popup, not the frames. + */ + void UpdateMenuItems(nsIContent* aPopup); + + /** + * Stop the timer which hides a popup after a delay, started by a previous + * call to HidePopupAfterDelay. In addition, the popup awaiting to be hidden + * is closed asynchronously. + */ + void KillMenuTimer(); + + /** + * Cancel the timer which closes menus after delay, but only if the menu to + * close is aMenuParent. When a submenu is opened, the user might move the + * mouse over a sibling menuitem which would normally close the menu. This + * menu is closed via a timer. However, if the user moves the mouse over the + * submenu before the timer fires, we should instead cancel the timer. This + * ensures that the user can move the mouse diagonally over a menu. + */ + void CancelMenuTimer(nsMenuParent* aMenuParent); + + /** + * Handles navigation for menu accelkeys. Returns true if the key has + * been handled. If aFrame is specified, then the key is handled by that + * popup, otherwise if aFrame is null, the key is handled by the active + * popup or menubar. + */ + bool HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, + nsMenuPopupFrame* aFrame); + + /** + * Handles cursor navigation within a menu. Returns true if the key has + * been handled. + */ + bool HandleKeyboardNavigation(uint32_t aKeyCode); + + /** + * Handle keyboard navigation within a menu popup specified by aFrame. + * Returns true if the key was handled and other default handling + * should not occur. + */ + bool HandleKeyboardNavigationInPopup(nsMenuPopupFrame* aFrame, + nsNavigationDirection aDir) + { + return HandleKeyboardNavigationInPopup(nullptr, aFrame, aDir); + } + + nsresult KeyUp(nsIDOMKeyEvent* aKeyEvent); + nsresult KeyDown(nsIDOMKeyEvent* aKeyEvent); + nsresult KeyPress(nsIDOMKeyEvent* aKeyEvent); + +protected: + nsXULPopupManager(); + ~nsXULPopupManager(); + + // get the nsMenuPopupFrame, if any, for the given content node + nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent, bool 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, + bool aDeselectMenu); + + // 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, + bool aIsContextMenu, + bool aSelectFirstItem); + void HidePopupCallback(nsIContent* aPopup, + nsMenuPopupFrame* aPopupFrame, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPopupType aPopupType, + bool aDeselectMenu); + + /** + * Fire a popupshowing event on the popup and then open the popup. + * + * aPopup - the popup to open + * aIsContextMenu - true for context menus + * aSelectFirstItem - true to select the first item in the menu + */ + void FirePopupShowingEvent(nsIContent* aPopup, + bool aIsContextMenu, + bool aSelectFirstItem); + + /** + * Fire a popuphiding event and then hide the popup. This will be called + * recursively if aNextPopup and aLastPopup are set in order to hide a chain + * of open menus. If these are not set, only one popup is closed. However, + * if the popup type indicates a menu, yet the next popup is not a menu, + * then this ends the closing of popups. This allows a menulist inside a + * non-menu to close up the menu but not close up the panel it is contained + * within. + * + * The caller must keep a strong reference to aPopup, aNextPopup and aLastPopup. + * + * aPopup - the popup to hide + * aNextPopup - the next popup to hide + * aLastPopup - the last popup in the chain to hide + * aPresContext - nsPresContext for the popup's frame + * aPopupType - the PopupType of the frame. + * aDeselectMenu - true to unhighlight the menu when hiding it + */ + void FirePopupHidingEvent(nsIContent* aPopup, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPresContext *aPresContext, + nsPopupType aPopupType, + bool aDeselectMenu); + + /** + * Handle keyboard navigation within a menu popup specified by aItem. + */ + bool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem, + nsNavigationDirection aDir) + { + return HandleKeyboardNavigationInPopup(aItem, aItem->Frame(), aDir); + } + +private: + /** + * Handle keyboard navigation within a menu popup aFrame. If aItem is + * supplied, then it is expected to have a frame equal to aFrame. + * If aItem is non-null, then the navigation may be redirected to + * an open submenu if one exists. Returns true if the key was + * handled and other default handling should not occur. + */ + bool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem, + nsMenuPopupFrame* aFrame, + nsNavigationDirection aDir); + +protected: + + already_AddRefed<nsIDOMNode> GetLastTriggerNode(nsIDocument* aDocument, bool 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); + + /** + * Key event listeners are attached to the document containing the current + * menu for menu and shortcut navigation. Only one listener is needed at a + * time, stored in mKeyListener, so switch it only if the document changes. + * Having menus in different documents is very rare, so the listeners will + * usually only be attached when the first menu opens and removed when all + * menus have closed. + * + * This is also used when only a menubar is active without any open menus, + * so that keyboard navigation between menus on the menubar may be done. + */ + void UpdateKeyboardListeners(); + + /* + * Returns true if the docshell for aDoc is aExpected or a child of aExpected. + */ + bool IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected); + + // the document the key event listener is attached to + nsCOMPtr<mozilla::dom::EventTarget> mKeyListener; + + // widget that is currently listening to rollup events + nsCOMPtr<nsIWidget> mWidget; + + // range parent and offset set in SetTriggerEvent + nsCOMPtr<nsIDOMNode> mRangeParent; + int32_t mRangeOffset; + // Device pixels relative to the showing popup's presshell's + // root prescontext's root frame. + nsIntPoint mCachedMousePoint; + + // cached modifiers + mozilla::widget::Modifiers mCachedModifiers; + + // set to the currently active menu bar, if any + nsMenuBarFrame* mActiveMenuBar; + + // linked list of normal menus and panels. + nsMenuChainItem* mPopups; + + // 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; +}; + +#endif diff --git a/layout/xul/base/reftest/image-scaling-min-height-1-ref.xul b/layout/xul/base/reftest/image-scaling-min-height-1-ref.xul new file mode 100644 index 000000000..595450fe4 --- /dev/null +++ b/layout/xul/base/reftest/image-scaling-min-height-1-ref.xul @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> +<html:style><![CDATA[ + +window { -moz-box-align: start; -moz-box-pack: start } +hbox { background: yellow } +vbox { background: blue; width: 15px; height: 15px } + +]]></html:style> + +<hbox><vbox /><label value="a b c d e f" /></hbox> + +</window> diff --git a/layout/xul/base/reftest/image-scaling-min-height-1.xul b/layout/xul/base/reftest/image-scaling-min-height-1.xul new file mode 100644 index 000000000..5c45d6b0c --- /dev/null +++ b/layout/xul/base/reftest/image-scaling-min-height-1.xul @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> +<html:style><![CDATA[ + +window { -moz-box-align: start; -moz-box-pack: start } +hbox { background: yellow } +image { background: blue; min-width: 15px; min-height: 15px } + +]]></html:style> + +<hbox><image /><label value="a b c d e f" /></hbox> + +</window> diff --git a/layout/xul/base/reftest/image-size-ref.xul b/layout/xul/base/reftest/image-size-ref.xul new file mode 100644 index 000000000..b65797025 --- /dev/null +++ b/layout/xul/base/reftest/image-size-ref.xul @@ -0,0 +1,115 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + +<html:style> +div { margin: 0px; line-height: 0px; } +div div { background: blue; display: inline; float: left; } +</html:style> + +<html:div><html:img + src="image4x3.png" style="width: 40px; height: 30px;"/><html:img + src="image4x3.png" style="width: 80px; height: 20px;"/><html:img + src="image4x3.png" style="width: 10px; height: 70px;"/><html:img + src="image4x3.png" style="width: 80px; height: 60px;"/><html:img + src="image4x3.png" style="width: 80px; height: 60px;"/><html:img + src="image4x3.png" style="width: 20px; height: 15px;"/><html:img + src="image4x3.png" style="width: 20px; height: 15px;"/><html:img + src="image4x3.png" style="width: 40px; height: 30px; border: 8px solid green;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 80px; height: 64px; border: 8px solid yellow;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 72px; height: 58px; border: 8px solid green;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 24px; height: 22px; border: 8px solid yellow;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 74px; height: 53px; border: solid yellow; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 18px; height: 11px; border: solid green; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width: 40px; height: 30px;"/><html:img + src="image4x3.png" style="width: 80px; height: 20px;"/><html:img + src="image4x3.png" style="width: 10px; height: 70px;"/><html:img + src="image4x3.png" style="width: 80px; height: 60px;"/><html:img + src="image4x3.png" style="height: 80px; height: 60px;"/><html:img + src="image4x3.png" style="width: 20px; height: 15px;"/><html:img + src="image4x3.png" style="width: 20px; height: 15px;"/><html:img + src="image4x3.png" style="width: 60px; height: 25px;"/><html:img + src="image4x3.png" style="width: 20px; height: 75px;"/><html:img + src="image4x3.png" style="width: 80px; height: 64px; padding: 8px; -moz-box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 72px; height: 58px; padding: 8px; -moz-box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 24px; height: 22px; padding: 8px; -moz-box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 24px; height: 22px; padding: 8px; -moz-box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 67px; height: 60px; padding: 4px 2px 8px 1px; -moz-box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 11px; height: 18px; padding: 4px 2px 8px 1px; -moz-box-sizing: border-box;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width: 20px; height: 15px;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width: 20px; height: 15px;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width: 30px; height: 22.5px"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width: 20px; height: 15px;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width: 20px; height: 15px;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width 30px; height: 22.5px;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width: 40px; height: 30px;"/><html:img + src="image4x3.png" style="width: 40px; height: 30px;"/><html:img + src="image4x3.png" style="width: 40px; height: 30px;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 60px; height: 49px; border: 8px solid green;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 112px; height: 88px; border: 8px solid yellow;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 96px; height: 76px; border: 8px solid green;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 112px; height: 88px; border: 8px solid yellow;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 106px; height: 77px; border: solid yellow; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="width: 60px; height: 45px;"/><html:img + src="image4x3.png" style="width: 120px; height: 90px;"/><html:img + src="image4x3.png" style="width 60px; height: 45px;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 60px; height: 49px; padding: 8px;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 112px; height: 88px; padding: 8px;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 96px; height: 76px; padding: 8px;"/><html:img + src="image4x3.png" style="-moz-box-sizing: border-box; width: 112px; height: 88px; padding: 8px;"/> +</html:div> + +<html:div><html:div + style="width: 20px; height: 15px;"/><html:div + style="width: 80px; height: 60px;"/><html:div + style="width: 40px; height: 30px;"/><html:div + style="width: 10px; height: 8px;"/><html:div + style="width: 10px; height: 8px;"/> +</html:div> + +<html:div><html:div style="width: 20px; height: 15px;"/></html:div> + +<html:div><html:div style="width: 20px; height: 15px;"/></html:div> + +<html:div><html:div style="-moz-box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/></html:div> + +<html:div><html:div style="-moz-box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/></html:div> + +</window> diff --git a/layout/xul/base/reftest/image-size.xul b/layout/xul/base/reftest/image-size.xul new file mode 100644 index 000000000..732dc51f3 --- /dev/null +++ b/layout/xul/base/reftest/image-size.xul @@ -0,0 +1,123 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<hbox align="end"> + <image src="image4x3.png"/> + <image src="image4x3.png" width="80" height="20"/> + <image src="image4x3.png" width="10" height="70"/> + <image src="image4x3.png" width="80"/> + <image src="image4x3.png" height="60"/> + <image src="image4x3.png" width="20"/> + <image src="image4x3.png" height="15"/> + <image src="image4x3.png" style="border: 8px solid green;"/> + <image src="image4x3.png" width="80" style="border: 8px solid yellow;"/> + <image src="image4x3.png" height="58" style="border: 8px solid green;"/> + <image src="image4x3.png" width="24" style="border: 8px solid yellow;"/> + <image src="image4x3.png" height="22" style="border: 8px solid green;"/> + <image src="image4x3.png" width="74" + style="border: 1px solid yellow; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/> + <image src="image4x3.png" height="11" + style="border: 1px solid green; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" style="width: auto; height: auto;"/> + <image src="image4x3.png" style="width: 80px; height: 20px;"/> + <image src="image4x3.png" style="width: 10px; height: 70px;"/> + <image src="image4x3.png" style="width: 80px;"/> + <image src="image4x3.png" style="height: 60px;"/> + <image src="image4x3.png" style="width: 20px;"/> + <image src="image4x3.png" style="height: 15px;"/> + <image src="image4x3.png" style="width: 80px; height: 20px;" width="60" height="25"/> + <image src="image4x3.png" style="width: 10px; height: 70px;" width="20" height="75"/> + <image src="image4x3.png" style="width: 80px; padding: 8px;"/> + <image src="image4x3.png" style="height: 58px; padding: 8px;"/> + <image src="image4x3.png" style="width: 24px; padding: 8px;"/> + <image src="image4x3.png" style="height: 22px; padding: 8px;"/> + <image src="image4x3.png" style="width: 67px; padding: 4px 2px 8px 1px"/> + <image src="image4x3.png" style="height: 18px; padding: 4px 2px 8px 1px"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" maxwidth="20"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" maxheight="15"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" maxwidth="30" maxheight="25"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" style="max-width: 20px;"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" style="max-height: 15px;"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" style="max-width: 30px; max-height: 25px;"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" maxwidth="24" style="border: 8px solid green;"/> +</hbox> +<hbox align="end"> + <image src="image4x3.png" maxheight="22" style="border: 8px solid green;"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" minwidth="20"/> + <image src="image4x3.png" minheight="20"/> + <image src="image4x3.png" minwidth="20" minheight="25"/> + <image src="image4x3.png" minwidth="60" style="border: 8px solid green;"/> + <image src="image4x3.png" minheight="88" style="border: 8px solid yellow;"/> + <image src="image4x3.png" minwidth="90" minheight="76" style="border: 8px solid green;"/> + <image src="image4x3.png" minwidth="112" minheight="76" style="border: 8px solid yellow;"/> + <image src="image4x3.png" minwidth="106" + style="border: 1px solid yellow; border-top-width: 1px; border-right-width: 2px; border-bottom-width: 4px; border-left-width: 8px;"/> +</hbox> + +<hbox align="end"> + <image src="image4x3.png" style="min-width: 60px;"/> + <image src="image4x3.png" style="min-height: 90px;"/> + <image src="image4x3.png" style="min-width 41px; min-height: 45px;"/> + <image src="image4x3.png" style="min-width: 60px; padding: 8px;"/> + <image src="image4x3.png" style="min-height: 88px; padding: 8px;"/> + <image src="image4x3.png" style="min-width: 90px; min-height: 76px; padding: 8px;"/> + <image src="image4x3.png" style="min-width: 112px; min-height: 76px; padding: 8px;"/> +</hbox> + +<hbox align="start"> + <image style="width: auto; height: auto; list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/> + <image width="80" style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/> + <image height="30" style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/> + <image style="width: 10px; list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 21px, 5px);"/> + <image style="height: 8px; list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 21px, 5px);"/> +</hbox> + +<hbox align="end"> + <image maxwidth="20" + style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/> +</hbox> + +<hbox align="end"> + <image maxheight="15" + style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px);"/> +</hbox> + +<hbox align="end"> + <image maxwidth="24" + style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px); border: 8px solid green;"/> +</hbox> + +<hbox align="end"> + <image maxheight="22" + style="list-style-image: url(image4x3.png); -moz-image-region: rect(5px, 25px, 20px, 5px); border: 8px solid green;"/> +</hbox> + +</window> diff --git a/layout/xul/base/reftest/image4x3.png b/layout/xul/base/reftest/image4x3.png Binary files differnew file mode 100644 index 000000000..6719bf5ce --- /dev/null +++ b/layout/xul/base/reftest/image4x3.png diff --git a/layout/xul/base/reftest/popup-explicit-size-ref.xul b/layout/xul/base/reftest/popup-explicit-size-ref.xul new file mode 100644 index 000000000..4c864f103 --- /dev/null +++ b/layout/xul/base/reftest/popup-explicit-size-ref.xul @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <label value="One"/> + <label value="Two"/> +</window> diff --git a/layout/xul/base/reftest/popup-explicit-size.xul b/layout/xul/base/reftest/popup-explicit-size.xul new file mode 100644 index 000000000..6f1bf8582 --- /dev/null +++ b/layout/xul/base/reftest/popup-explicit-size.xul @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <label value="One"/> + <popup height="40"/> + <label value="Two"/> +</window> diff --git a/layout/xul/base/reftest/reftest.list b/layout/xul/base/reftest/reftest.list new file mode 100644 index 000000000..7452737be --- /dev/null +++ b/layout/xul/base/reftest/reftest.list @@ -0,0 +1,5 @@ +fails-if(Android||B2G) == textbox-multiline-noresize.xul textbox-multiline-ref.xul # reference is blank on Android (due to no native theme support?) +!= textbox-multiline-resize.xul textbox-multiline-ref.xul +== popup-explicit-size.xul popup-explicit-size-ref.xul +random-if(Android) == image-size.xul image-size-ref.xul +== image-scaling-min-height-1.xul image-scaling-min-height-1-ref.xul diff --git a/layout/xul/base/reftest/textbox-multiline-noresize.xul b/layout/xul/base/reftest/textbox-multiline-noresize.xul new file mode 100644 index 000000000..27945ae9a --- /dev/null +++ b/layout/xul/base/reftest/textbox-multiline-noresize.xul @@ -0,0 +1,5 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<textbox style="margin: 0;" multiline="true" width="100" height="100"/> +</window> diff --git a/layout/xul/base/reftest/textbox-multiline-ref.xul b/layout/xul/base/reftest/textbox-multiline-ref.xul new file mode 100644 index 000000000..23d279a75 --- /dev/null +++ b/layout/xul/base/reftest/textbox-multiline-ref.xul @@ -0,0 +1,5 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<vbox style="-moz-appearance: textfield;" width="100" height="100"/> +</window> diff --git a/layout/xul/base/reftest/textbox-multiline-resize.xul b/layout/xul/base/reftest/textbox-multiline-resize.xul new file mode 100644 index 000000000..d7a25a669 --- /dev/null +++ b/layout/xul/base/reftest/textbox-multiline-resize.xul @@ -0,0 +1,5 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<textbox style="margin: 0;" resizable="true" multiline="true" width="100" height="100"/> +</window> diff --git a/layout/xul/base/src/Makefile.in b/layout/xul/base/src/Makefile.in new file mode 100644 index 000000000..25bd6f144 --- /dev/null +++ b/layout/xul/base/src/Makefile.in @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +LIBRARY_NAME = gkxulbase_s +MSVC_ENABLE_PGO := 1 +LIBXUL_LIBRARY = 1 +FAIL_ON_WARNINGS = 1 + +include $(topsrcdir)/config/config.mk + +# we don't want the shared lib, but we want to force the creation of a static lib. +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/config/rules.mk + +LOCAL_INCLUDES = \ + -I$(srcdir) \ + -I$(srcdir)/../../../base \ + -I$(srcdir)/../../../../content/base/src \ + -I$(srcdir)/../../../../content/events/src \ + -I$(srcdir)/../../../generic \ + -I$(srcdir)/../../../style \ + $(NULL) + +DEFINES += -D_IMPL_NS_LAYOUT + diff --git a/layout/xul/base/src/crashtests/131008-1.xul b/layout/xul/base/src/crashtests/131008-1.xul new file mode 100644 index 000000000..d505f8696 --- /dev/null +++ b/layout/xul/base/src/crashtests/131008-1.xul @@ -0,0 +1,11 @@ +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window xmlns:html="http://www.w3.org/1999/xhtml" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="MainWindow" + title="IWindow Test"> +<div style="position:absolute">abc</div> + + +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/137216-1.xul b/layout/xul/base/src/crashtests/137216-1.xul new file mode 100644 index 000000000..a3fc043c8 --- /dev/null +++ b/layout/xul/base/src/crashtests/137216-1.xul @@ -0,0 +1,4 @@ +<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <iframe style="position:absolute;"/>
+</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/140218-1.xml b/layout/xul/base/src/crashtests/140218-1.xml new file mode 100644 index 000000000..311afc218 --- /dev/null +++ b/layout/xul/base/src/crashtests/140218-1.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <treechildren style = " display: block; " />
+</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/151826-1.xul b/layout/xul/base/src/crashtests/151826-1.xul new file mode 100644 index 000000000..1115dae72 --- /dev/null +++ b/layout/xul/base/src/crashtests/151826-1.xul @@ -0,0 +1,27 @@ +<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window
+ title = "Arrowscrollbox->Splitter Crash Testcase"
+ xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ width = "300"
+ height = "200"
+ orient = "vertical"
+>
+<vbox flex="1">
+
+<scrollbox flex="1">
+<vbox flex="1">
+<vbox id="box_1">
+<hbox><label value="Test"/></hbox>
+</vbox>
+<splitter collapse="none"/>
+<vbox id="box_2">
+<hbox><label value="Test"/></hbox>
+</vbox>
+</vbox>
+</scrollbox>
+
+</vbox>
+</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/168724-1.xul b/layout/xul/base/src/crashtests/168724-1.xul new file mode 100644 index 000000000..8456406c2 --- /dev/null +++ b/layout/xul/base/src/crashtests/168724-1.xul @@ -0,0 +1,18 @@ +<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css" ?>
+
+<window
+ id="nodeCreator" title="Node Creator"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+<description context="context">Right-click here, and expect a crash.</description>
+
+<popupset id="context-set">
+<popup id="context">
+<deck selectedItem="0">
+<menuitem label="You should never see this" />
+</deck>
+</popup>
+</popupset>
+</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/189814-1.xul b/layout/xul/base/src/crashtests/189814-1.xul new file mode 100644 index 000000000..79462348c --- /dev/null +++ b/layout/xul/base/src/crashtests/189814-1.xul @@ -0,0 +1,21 @@ +<?xml version="1.0"?> + +<window + id="sliderprint" title="Print These Sliders" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="background-color: white"> + + <label> + With the Classic theme, printing causes the browser to crash. adding style="-moz-appearance: none" to the + thumb prevents the crash. The crash doesn't happen at all with Modern. + </label> + <spacer height="10"/> + <hbox> + + <slider style="height: 174px; width: 24px" orient="vertical"> + <thumb/> + </slider> + + </hbox> + +</window> diff --git a/layout/xul/base/src/crashtests/237787-1.xul b/layout/xul/base/src/crashtests/237787-1.xul new file mode 100644 index 000000000..96cebca9f --- /dev/null +++ b/layout/xul/base/src/crashtests/237787-1.xul @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <toolbar> + <arrowscrollbox> + <menulist editable="true"> + </menulist> + </arrowscrollbox> + </toolbar> +</window> diff --git a/layout/xul/base/src/crashtests/265161-1.xul b/layout/xul/base/src/crashtests/265161-1.xul new file mode 100644 index 000000000..75a995790 --- /dev/null +++ b/layout/xul/base/src/crashtests/265161-1.xul @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<window + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <html:div> + <listitem> + </listitem> + </html:div> +</window> diff --git a/layout/xul/base/src/crashtests/289410-1.xul b/layout/xul/base/src/crashtests/289410-1.xul new file mode 100644 index 000000000..0de792f2c --- /dev/null +++ b/layout/xul/base/src/crashtests/289410-1.xul @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window id="crash-window" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <scrollbox> + <tree id="crash-tree"> + <treecols/> + <treechildren/> + </tree> + </scrollbox> + +</window> diff --git a/layout/xul/base/src/crashtests/291702-1.xul b/layout/xul/base/src/crashtests/291702-1.xul new file mode 100644 index 000000000..4c052630d --- /dev/null +++ b/layout/xul/base/src/crashtests/291702-1.xul @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window title="Negative flex bug #2" + orient="horizontal" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <button label="Button" flex="2"/> + <label value="This is a label" flex="1"/> + <label value="This is the second label" flex="-2"/> + <label value="This is another label" flex="-1"/> +</window> diff --git a/layout/xul/base/src/crashtests/291702-2.xul b/layout/xul/base/src/crashtests/291702-2.xul new file mode 100644 index 000000000..53d8a51f7 --- /dev/null +++ b/layout/xul/base/src/crashtests/291702-2.xul @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window title="Negative flex bug #2" + orient="horizontal" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <button label="Button" flex="1073741824"/> + <label value="This is a label" flex="1073741824"/> + <label value="This is the second label" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> +</window> diff --git a/layout/xul/base/src/crashtests/291702-3.xul b/layout/xul/base/src/crashtests/291702-3.xul new file mode 100644 index 000000000..b34404f36 --- /dev/null +++ b/layout/xul/base/src/crashtests/291702-3.xul @@ -0,0 +1,137 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window title="Negative flex bug #2" + orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <hbox> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + </hbox> + + <hbox> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + </hbox> + + + <hbox> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 2;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 2;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 2;"/> + </hbox> + + <hbox> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + <button label="Button" flex="1"/> + <label value="This is another label" style="-moz-box-flex: 1;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + <button label="Button" flex="1"/> + <label value="This is another label" style="-moz-box-flex: 1;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + <button label="Button" flex="1"/> + <label value="This is another label" style="-moz-box-flex: 1;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741823"/> + <label value="This is another label" style="-moz-box-flex: 1073741823;"/> + <button label="Button" flex="2"/> + <label value="This is another label" style="-moz-box-flex: 2;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741824"/> + <label value="This is another label" style="-moz-box-flex: 1073741824;"/> + <button label="Button" flex="2"/> + <label value="This is another label" style="-moz-box-flex: 2;"/> + </hbox> + <hbox> + <button label="Button" flex="1073741825"/> + <label value="This is another label" style="-moz-box-flex: 1073741825;"/> + <button label="Button" flex="2"/> + <label value="This is another label" style="-moz-box-flex: 2;"/> + </hbox> +</window> diff --git a/layout/xul/base/src/crashtests/294371-1.xul b/layout/xul/base/src/crashtests/294371-1.xul new file mode 100644 index 000000000..ca5b54914 --- /dev/null +++ b/layout/xul/base/src/crashtests/294371-1.xul @@ -0,0 +1,53 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> + +<window + id = "overflow crash" + title = "scrollbox crasher" + xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + persist="sizemode width height screenX screenY" + width="320" + height="240"> + + <scrollbox flex="1"> + <grid style="overflow: auto"> + <columns> + <column flex="0"/> + </columns> + <rows> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + <row><label value="Date"/></row> + </rows> + </grid> + </scrollbox> + +</window> diff --git a/layout/xul/base/src/crashtests/311457-1.html b/layout/xul/base/src/crashtests/311457-1.html new file mode 100644 index 000000000..e5b6ecdd6 --- /dev/null +++ b/layout/xul/base/src/crashtests/311457-1.html @@ -0,0 +1,12 @@ +<html><head> + +</head> + +<body> + +<div style="display: -moz-deck"><div style="display: -moz-popup"></div></div> + +<div style="position: relative">Y</div> + +</body> +</html>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/321056-1.xhtml b/layout/xul/base/src/crashtests/321056-1.xhtml new file mode 100644 index 000000000..a7ba11793 --- /dev/null +++ b/layout/xul/base/src/crashtests/321056-1.xhtml @@ -0,0 +1,10 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<xul:titlebar id="a"/>
+
+<script>
+var html = document.firstChild;
+var a = document.getElementById('a')
+document.removeChild(html)
+document.appendChild(a)
+</script>
+</html>
diff --git a/layout/xul/base/src/crashtests/322786-1.xul b/layout/xul/base/src/crashtests/322786-1.xul new file mode 100644 index 000000000..79bb092c4 --- /dev/null +++ b/layout/xul/base/src/crashtests/322786-1.xul @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <foo style="display: inline;"> + <scrollbox/> + </foo> +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/325377.xul b/layout/xul/base/src/crashtests/325377.xul new file mode 100644 index 000000000..8ea30473d --- /dev/null +++ b/layout/xul/base/src/crashtests/325377.xul @@ -0,0 +1,16 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" + title="Testcase bug 325377 - Crash on reload with evil xul textcase, using menulist and nested tooltips"> +<menulist style="display: table-cell;"> +<tooltip style="display: none;"> + <tooltip/> +</tooltip> +</menulist> + +<html:script> +function removestyles(){ +document.getElementsByTagName('tooltip')[0].removeAttribute('style'); +} +try { document.getElementsByTagName('tooltip')[0].offsetHeight; } catch(e) {} +setTimeout(removestyles,0); +</html:script> +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/326834-1-inner.xul b/layout/xul/base/src/crashtests/326834-1-inner.xul new file mode 100644 index 000000000..0fbdca7ab --- /dev/null +++ b/layout/xul/base/src/crashtests/326834-1-inner.xul @@ -0,0 +1,17 @@ +<window title="Testcase bug 326834 - Crash with evil xul testcase, using listbox/listitem and display: table-cell" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<listbox> + <listitem label="This page should not crash Mozilla"/> +</listbox> +<html:script> +function doe() { +var el=document.getElementsByTagName('*'); +document.getElementsByTagName('listbox')[0].style.display = 'table-cell'; +document.getElementsByTagName('listitem')[0].style.display = 'table-cell'; +window.getComputedStyle(document.getElementsByTagName('listitem')[0], '').getPropertyValue("height"); +document.getElementsByTagName('listitem')[0].style.display = ''; +} +setTimeout(doe,500); +</html:script> +</window> diff --git a/layout/xul/base/src/crashtests/326834-1.html b/layout/xul/base/src/crashtests/326834-1.html new file mode 100644 index 000000000..ca531caa6 --- /dev/null +++ b/layout/xul/base/src/crashtests/326834-1.html @@ -0,0 +1,9 @@ +<html class="reftest-wait"> +<head> +<script> +setTimeout('document.documentElement.className = ""', 1000); +</script> +<body> +<iframe src="326834-1-inner.xul"></iframe> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/326879-1.xul b/layout/xul/base/src/crashtests/326879-1.xul new file mode 100644 index 000000000..84d74c30c --- /dev/null +++ b/layout/xul/base/src/crashtests/326879-1.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + + +<script> + + +function init() { + + var menupopup = document.getElementsByTagName("menupopup")[0]; + menupopup.ordinal = null; +}; + + +window.addEventListener("load", init, false); + +</script> + + +<menulist> + <menupopup> + <menuitem label="Foo"/> + </menupopup> +</menulist> + + + +</window> diff --git a/layout/xul/base/src/crashtests/327776-1.xul b/layout/xul/base/src/crashtests/327776-1.xul new file mode 100644 index 000000000..af889493c --- /dev/null +++ b/layout/xul/base/src/crashtests/327776-1.xul @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script> +<![CDATA[ +function init() +{ + var span = document.getElementsByTagName("span")[0]; + var boxobj = document.getBoxObjectFor(span); + try { + boxobj.setPropertyAsSupports(undefined, undefined); + } catch(e) { + } +} +window.addEventListener("load", init, false); +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> + +<span></span> + +</body> +</window> diff --git a/layout/xul/base/src/crashtests/328135-1.xul b/layout/xul/base/src/crashtests/328135-1.xul new file mode 100644 index 000000000..77a467909 --- /dev/null +++ b/layout/xul/base/src/crashtests/328135-1.xul @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + + +<script> + +function init() { + var pop = document.getElementsByTagName("popup")[0]; + SpecialPowers.wrap(document).getAnonymousNodes(pop)[0]; + eval.eee = document.documentElement; +}; + + +window.addEventListener("load", init, false); + +</script> + +<popup/> + + +<tabbox/> + + +</window> diff --git a/layout/xul/base/src/crashtests/329327-1.xul b/layout/xul/base/src/crashtests/329327-1.xul new file mode 100644 index 000000000..fcfed07c4 --- /dev/null +++ b/layout/xul/base/src/crashtests/329327-1.xul @@ -0,0 +1,2 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><menulist equalsize="always"><y/> <z width="-444981589286"/> </menulist></window> diff --git a/layout/xul/base/src/crashtests/329407-1.xml b/layout/xul/base/src/crashtests/329407-1.xml new file mode 100644 index 000000000..0d41c0185 --- /dev/null +++ b/layout/xul/base/src/crashtests/329407-1.xml @@ -0,0 +1,14 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + +<body> + + <xul:hbox> + <select/> + <select/> + </xul:hbox> + +</body> + +</html> diff --git a/layout/xul/base/src/crashtests/329477-1.xhtml b/layout/xul/base/src/crashtests/329477-1.xhtml new file mode 100644 index 000000000..fcbd3da87 --- /dev/null +++ b/layout/xul/base/src/crashtests/329477-1.xhtml @@ -0,0 +1,31 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + +<script> + +<![CDATA[ + + +function init() +{ + var textbox = document.getElementsByTagName("textbox")[0]; + var hbox = SpecialPowers.wrap(document).getAnonymousNodes(textbox)[0]; + var menupopup = SpecialPowers.wrap(document).getAnonymousNodes(hbox)[1]; + + menupopup.click(); +} + +window.addEventListener("load", init, false); + +]]> +</script> + +</head> + +<body> + + + <textbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/> + +</body> +</html> diff --git a/layout/xul/base/src/crashtests/336962-1.xul b/layout/xul/base/src/crashtests/336962-1.xul new file mode 100644 index 000000000..5ad4ad22b --- /dev/null +++ b/layout/xul/base/src/crashtests/336962-1.xul @@ -0,0 +1,17 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script>
+ +function init() { + document.getElementById("foopy").style.position = "absolute"; +}
+ +window.addEventListener("load", init, 0);
+ +</script> + + +<box id="foopy" /> + + +</window> diff --git a/layout/xul/base/src/crashtests/344228-1.xul b/layout/xul/base/src/crashtests/344228-1.xul new file mode 100644 index 000000000..d6015707b --- /dev/null +++ b/layout/xul/base/src/crashtests/344228-1.xul @@ -0,0 +1,27 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(boom, 30);" class="reftest-wait"> + +<script> + +function remove(q1) { q1.parentNode.removeChild(q1); } + +function boom() +{ + var x = document.getElementById("x"); + var y = document.getElementById("y"); + remove(x); + remove(y); + + document.documentElement.removeAttribute("class"); +} + +</script> + +<tree> + <treechildren id="y"/> + <richlistbox> + <hbox id="x"/> + <menulist/> + </richlistbox> +</tree> + +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/346083-1.xul b/layout/xul/base/src/crashtests/346083-1.xul new file mode 100644 index 000000000..e04d610a4 --- /dev/null +++ b/layout/xul/base/src/crashtests/346083-1.xul @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<body xmlns="http://www.w3.org/1999/xhtml"> + +<script> +<![CDATA[ +document.getBoxObjectFor(document.getElementsByTagName("body")[0]).setProperty("foo", undefined); +]]> +</script> + +</body> +</window> diff --git a/layout/xul/base/src/crashtests/346281-1.xul b/layout/xul/base/src/crashtests/346281-1.xul new file mode 100644 index 000000000..4ef670155 --- /dev/null +++ b/layout/xul/base/src/crashtests/346281-1.xul @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<body xmlns="http://www.w3.org/1999/xhtml"> + +<script> +<![CDATA[ +var boxy = document.getBoxObjectFor(document.getElementsByTagName("body")[0]); +boxy.setPropertyAsSupports("zoink", undefined); +try { + boxy.removeProperty(undefined); +} catch(e) { } +]]> +</script> + +</body> +</window> diff --git a/layout/xul/base/src/crashtests/350460.xul b/layout/xul/base/src/crashtests/350460.xul new file mode 100644 index 000000000..b13de6c97 --- /dev/null +++ b/layout/xul/base/src/crashtests/350460.xul @@ -0,0 +1,8 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Crash [@ DoDeletingFrameSubtree] after reloading a xul page a few times with display: -moz-popup and menuitem">
+ <menuitem style="display: -moz-popup;">
+ <box style="display: -moz-popup;">
+ <box style="display: -moz-popup;"/>
+ </box>
+ </menuitem>
+</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/360642-1.xul b/layout/xul/base/src/crashtests/360642-1.xul new file mode 100644 index 000000000..5e37020a5 --- /dev/null +++ b/layout/xul/base/src/crashtests/360642-1.xul @@ -0,0 +1,9 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="reftest-wait" + onload="setTimeout(function() { var foo = document.getElementById('foo'); foo.parentNode.removeChild(foo); document.documentElement.removeAttribute('class'); }, 30);"> + + <listboxbody> + <hbox id="foo"/> + </listboxbody> + +</window> diff --git a/layout/xul/base/src/crashtests/365151.xul b/layout/xul/base/src/crashtests/365151.xul new file mode 100644 index 000000000..074c8d398 --- /dev/null +++ b/layout/xul/base/src/crashtests/365151.xul @@ -0,0 +1,39 @@ +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="boom()" class="reftest-wait"> + + +<script> +function boom() +{ + try { + var tree = document.getElementById("tree"); + var col = tree.treeBoxObject.columns.getFirstColumn(); + var treecols = document.getElementById("treecols"); + treecols.parentNode.removeChild(treecols); + var x = col.x; + } finally { + document.documentElement.removeAttribute("class"); + } +} +</script> + + +<tree rows="6" id="tree"> + + <treecols id="treecols"> + <treecol id="firstname" label="First Name"/> + </treecols> + + <treechildren id="treechildren"> + <treeitem> + <treerow> + <treecell label="Bob"/> + </treerow> + </treeitem> + </treechildren> + +</tree> + +</window> diff --git a/layout/xul/base/src/crashtests/366112-1.xul b/layout/xul/base/src/crashtests/366112-1.xul new file mode 100644 index 000000000..4a03ea2cf --- /dev/null +++ b/layout/xul/base/src/crashtests/366112-1.xul @@ -0,0 +1,9 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <nativescrollbar /> + +</window> diff --git a/layout/xul/base/src/crashtests/369942-1.xhtml b/layout/xul/base/src/crashtests/369942-1.xhtml new file mode 100644 index 000000000..a05705843 --- /dev/null +++ b/layout/xul/base/src/crashtests/369942-1.xhtml @@ -0,0 +1,36 @@ +<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+<head>
+
+<script>
+function boom()
+{
+ var span = document.getElementById("span");
+ var radio = document.getElementById("radio");
+
+ radio.appendChild(span);
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+
+<style>
+body {
+ text-align: center;
+ font-size: 9px;
+}
+</style>
+
+</head>
+
+
+<body onload="setTimeout(boom, 30);">
+
+<span id="span"><xul:wizard/><div>Industries</div></span>
+
+<xul:radio id="radio"/>
+
+</body>
+</html>
diff --git a/layout/xul/base/src/crashtests/374102-1.xul b/layout/xul/base/src/crashtests/374102-1.xul new file mode 100644 index 000000000..7e85f0d21 --- /dev/null +++ b/layout/xul/base/src/crashtests/374102-1.xul @@ -0,0 +1,5 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<tabpanels>
+<treechildren style="display: -moz-deck;"/>
+</tabpanels>
+</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/376137-1.html b/layout/xul/base/src/crashtests/376137-1.html new file mode 100644 index 000000000..23b39d900 --- /dev/null +++ b/layout/xul/base/src/crashtests/376137-1.html @@ -0,0 +1,18 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<style> +span { display:block; outline: 10px solid yellow; } +</style> +</head> + +<body> + +<div> + <div style="display: -moz-inline-grid"> + <span>M</span> + <span>N</span> + </div> +</div> + +</body> +</html> diff --git a/layout/xul/base/src/crashtests/376137-2.html b/layout/xul/base/src/crashtests/376137-2.html new file mode 100644 index 000000000..160c61ed3 --- /dev/null +++ b/layout/xul/base/src/crashtests/376137-2.html @@ -0,0 +1,11 @@ +<!DOCTYPE html>
+<title>Bug 376137</title>
+<style>
+p { width: 100%; border: solid 1px;}
+</style>
+
+<div style="display: -moz-inline-grid">
+ <div><p>M</p></div>
+ <div><p>N</p></div>
+</div>
+
diff --git a/layout/xul/base/src/crashtests/377592-1.svg b/layout/xul/base/src/crashtests/377592-1.svg new file mode 100644 index 000000000..7371708f2 --- /dev/null +++ b/layout/xul/base/src/crashtests/377592-1.svg @@ -0,0 +1,27 @@ +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="setTimeout(boom, 30);" + class="reftest-wait"> + + +<script> + +var emptyBinding = "url('data:text/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22foo%22%3E%3Ccontent%3E%0A%0A%20%20%20%20%0A%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E%0A')"; + +function boom() +{ + var foreignObject = document.getElementById("foreignObject") + foreignObject.style.MozBinding = emptyBinding; + + document.documentElement.removeAttribute("class"); +} + +</script> + + +<foreignObject width="500" height="500" transform="scale(.7,.7)" id="foreignObject" y="300"> + <xul:menuitem /> +</foreignObject> + + +</svg> diff --git a/layout/xul/base/src/crashtests/381862.html b/layout/xul/base/src/crashtests/381862.html new file mode 100644 index 000000000..e26fa357e --- /dev/null +++ b/layout/xul/base/src/crashtests/381862.html @@ -0,0 +1,23 @@ +<html><head>
+<title>Testcase bug - Crash [@ nsBoxFrame::BuildDisplayListForChildren] with tree stuff in iframe toggling display</title>
+</head>
+<body>
+<iframe src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3Cwindow%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E%0A%20%20%3Ctree%20style%3D%22display%3A%20block%3B%20position%3A%20absolute%3B%22%3E%0A%20%20%20%20%3Ctree%20style%3D%22display%3A%20table%3B%22%3E%0A%20%20%20%20%20%20%3Ctreeseparator%20style%3D%22display%3A%20block%3B%20position%3A%20absolute%3B%22%3E%0A%20%20%20%20%20%20%20%20%3Ctreechildren%20style%3D%22display%3A%20block%3B%22/%3E%0A%20%20%20%20%20%20%3C/treeseparator%3E%0A%20%20%20%20%20%20%3Ctreechildren%20style%3D%22display%3A%20none%3B%22/%3E%0A%20%20%20%20%3C/tree%3E%0A%20%20%3C/tree%3E%0A%3C/window%3E" id="content"></iframe>
+
+<script>
+function toggleIframe(){
+var x=document.getElementById('content');
+x.style.display = x.style.display == 'none' ? x.style.display = 'block' : x.style.display = 'none';
+setTimeout(toggleIframe,200);
+}
+setTimeout(toggleIframe,500);
+
+function removestyles(i){
+window.frames[0].document.getElementsByTagName('*')[1].removeAttribute('style');
+}
+
+setTimeout(removestyles,500,1);
+/*template*/
+</script>
+</body>
+</html>
diff --git a/layout/xul/base/src/crashtests/382746-1.xul b/layout/xul/base/src/crashtests/382746-1.xul new file mode 100644 index 000000000..9bb14f24f --- /dev/null +++ b/layout/xul/base/src/crashtests/382746-1.xul @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<grid> + <rows> + <column> + <hbox/> + <hbox/> + </column> + <hbox/> + </rows> +</grid> + +</window> diff --git a/layout/xul/base/src/crashtests/382899-1.xul b/layout/xul/base/src/crashtests/382899-1.xul new file mode 100644 index 000000000..7dab931f7 --- /dev/null +++ b/layout/xul/base/src/crashtests/382899-1.xul @@ -0,0 +1,9 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<hbox equalsize="always"><grid/>x</hbox> + +</window> diff --git a/layout/xul/base/src/crashtests/383236-1.xul b/layout/xul/base/src/crashtests/383236-1.xul new file mode 100644 index 000000000..244df65f1 --- /dev/null +++ b/layout/xul/base/src/crashtests/383236-1.xul @@ -0,0 +1,5 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<listbox><listhead>x</listhead></listbox> + +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/384037-1.xhtml b/layout/xul/base/src/crashtests/384037-1.xhtml new file mode 100644 index 000000000..04bac671c --- /dev/null +++ b/layout/xul/base/src/crashtests/384037-1.xhtml @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body>
+
+<xul:splitter id="s" collapse="both" state="collapsed" />
+
+</body>
+</html>
+
diff --git a/layout/xul/base/src/crashtests/384105-1-inner.xul b/layout/xul/base/src/crashtests/384105-1-inner.xul new file mode 100644 index 000000000..4ea6e0391 --- /dev/null +++ b/layout/xul/base/src/crashtests/384105-1-inner.xul @@ -0,0 +1,21 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script id="script" xmlns="http://www.w3.org/1999/xhtml"> +function doe(){ +document.getElementById('a').removeAttribute('style'); +} +setTimeout(doe,100); +</script> +<box id="a" style="position: absolute;"> + <menuitem sizetopopup="always"> + <menupopup style="position: absolute;"/> + </menuitem> + + <box style="position: fixed;"> + <tree> + <treecol> + <treecol/> + </treecol> + </tree> + </box> +</box> +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/384105-1.html b/layout/xul/base/src/crashtests/384105-1.html new file mode 100644 index 000000000..fe468a906 --- /dev/null +++ b/layout/xul/base/src/crashtests/384105-1.html @@ -0,0 +1,9 @@ +<html class="reftest-wait"> +<head> +<script> +setTimeout('document.documentElement.className = ""', 1000); +</script> +<body> +<iframe src="384105-1-inner.xul"></iframe> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/384491-1.xhtml b/layout/xul/base/src/crashtests/384491-1.xhtml new file mode 100644 index 000000000..2eb065f8d --- /dev/null +++ b/layout/xul/base/src/crashtests/384491-1.xhtml @@ -0,0 +1,8 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<body> + +<xul:listboxbody style="overflow: hidden" /> + +</body> +</html> diff --git a/layout/xul/base/src/crashtests/384871-1-inner.xul b/layout/xul/base/src/crashtests/384871-1-inner.xul new file mode 100644 index 000000000..62efdb260 --- /dev/null +++ b/layout/xul/base/src/crashtests/384871-1-inner.xul @@ -0,0 +1,9 @@ +<popup xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script xmlns="http://www.w3.org/1999/xhtml"> +function doe(){ +document.documentElement.autoPosition = 'on'; +window.location.reload(); +} +setTimeout(doe, 300); +</script> +</popup>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/384871-1.html b/layout/xul/base/src/crashtests/384871-1.html new file mode 100644 index 000000000..6bb2a9e07 --- /dev/null +++ b/layout/xul/base/src/crashtests/384871-1.html @@ -0,0 +1,9 @@ +<html class="reftest-wait"> +<head> +<script> +setTimeout('document.documentElement.className = ""', 500); +</script> +<body> +<iframe src="384871-1-inner.xul"></iframe> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/387033-1.xhtml b/layout/xul/base/src/crashtests/387033-1.xhtml new file mode 100644 index 000000000..58325b1a7 --- /dev/null +++ b/layout/xul/base/src/crashtests/387033-1.xhtml @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xbl="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <head> + <xbl:bindings> + <xbl:binding id="test" extends="chrome://global/content/bindings/text.xml#text-label"> + <xbl:implementation> + <xbl:property name="accessKey"> + <xbl:getter> + <![CDATA[ + this.parentNode.parentNode.removeChild(this.parentNode); + return ""; + ]]> + </xbl:getter> + <xbl:setter> + <![CDATA[ + return val; + ]]> + </xbl:setter> + </xbl:property> + </xbl:implementation> + </xbl:binding> + </xbl:bindings> + </head> + <body> + <xul:hbox> + <xul:label value="foobar" style="-moz-binding: url(#test)"/> + </xul:hbox> + </body> +</html> diff --git a/layout/xul/base/src/crashtests/387080-1.xul b/layout/xul/base/src/crashtests/387080-1.xul new file mode 100644 index 000000000..4eb9bd784 --- /dev/null +++ b/layout/xul/base/src/crashtests/387080-1.xul @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <description> + <foo height="1793689537164611773" width="20000238421986669650" /> + </description> +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/391974-1-inner.xul b/layout/xul/base/src/crashtests/391974-1-inner.xul new file mode 100644 index 000000000..f13aa2110 --- /dev/null +++ b/layout/xul/base/src/crashtests/391974-1-inner.xul @@ -0,0 +1,19 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<menuitem> +<tooltip/> +<box/> +</menuitem> + +<script xmlns="http://www.w3.org/1999/xhtml"> +function doe2() { +document.getElementsByTagName('menuitem')[0].setAttribute('description', 'tetx'); +} + +function doe3() { +document.getElementsByTagName('menuitem')[0].removeAttribute('description'); +document.getElementsByTagName('tooltip')[0].setAttribute('ordinal', '0'); +} +setTimeout(doe2,150); +setTimeout(doe3,200); +</script> +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/391974-1.html b/layout/xul/base/src/crashtests/391974-1.html new file mode 100644 index 000000000..c72a1a73c --- /dev/null +++ b/layout/xul/base/src/crashtests/391974-1.html @@ -0,0 +1,9 @@ +<html class="reftest-wait"> +<head> +<script> +setTimeout('document.documentElement.className = ""', 1000); +</script> +<body> +<iframe src="391974-1-inner.xul"></iframe> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/394120-1.xhtml b/layout/xul/base/src/crashtests/394120-1.xhtml new file mode 100644 index 000000000..9df447862 --- /dev/null +++ b/layout/xul/base/src/crashtests/394120-1.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> +<script> +function boom() +{ + var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var xultext = document.createElementNS(XUL_NS, "text"); + var hbox = document.getElementById("hbox") + hbox.appendChild(xultext); +} +</script> +</head> +<body onload="boom();"> + +<xul:listboxbody><xul:hbox id="hbox" /></xul:listboxbody> + +</body> +</html> diff --git a/layout/xul/base/src/crashtests/397293.xhtml b/layout/xul/base/src/crashtests/397293.xhtml new file mode 100644 index 000000000..cfd181921 --- /dev/null +++ b/layout/xul/base/src/crashtests/397293.xhtml @@ -0,0 +1,21 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="opacity: 0.2;"> +<head> +<script> + +function x() +{ + document.documentElement.style.counterReset = "chicken"; + + document.body.offsetHeight; +} + +</script> +</head> + +<body onload="setTimeout(x, 0);">Foo</body> + +<xul:listbox/> + +</html> diff --git a/layout/xul/base/src/crashtests/397304-1.html b/layout/xul/base/src/crashtests/397304-1.html new file mode 100644 index 000000000..3501f0581 --- /dev/null +++ b/layout/xul/base/src/crashtests/397304-1.html @@ -0,0 +1 @@ +<html><body><listboxbody style="display: -moz-grid-group;"></listboxbody></body></html>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/398326-1.xhtml b/layout/xul/base/src/crashtests/398326-1.xhtml new file mode 100644 index 000000000..a265ae4e0 --- /dev/null +++ b/layout/xul/base/src/crashtests/398326-1.xhtml @@ -0,0 +1,17 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<script> +var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + +function boom() +{ + var listbox = document.createElementNS(XUL_NS, 'listbox'); + document.body.appendChild(listbox); + var listitem = document.createElementNS(XUL_NS, 'listitem'); + listbox.appendChild(listitem); +} +</script> +</head> +<body onload="boom();"> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/399013.xul b/layout/xul/base/src/crashtests/399013.xul new file mode 100644 index 000000000..a2349aff8 --- /dev/null +++ b/layout/xul/base/src/crashtests/399013.xul @@ -0,0 +1,31 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<menulist id="b" style="display: -moz-groupbox;"> +<panel id="c" style=" position: absolute;"> +<popup onunderflow="document.getElementById('c').removeAttribute('style')"/> +</panel> +<menupopup id="a" style="display: -moz-stack;"> +<menulist/> +</menupopup> +<panel style="display: -moz-deck;" onoverflow="document.getElementById('b').removeAttribute('style')"> +<popup style="display: -moz-deck;"/> +</panel> +</menulist> +
+<script id="script" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function doe() {
+document.getElementById('c').removeAttribute('style');
+document.documentElement.boxObject.height;
+document.getElementById('b').removeAttribute('style');
+document.getElementById('a').setAttribute('selected', 'true');
+document.getElementById('a').setAttribute('style', 'position: fixed;');
+document.documentElement.boxObject.height;
+document.getElementById('a').removeAttribute('style');
+}
+
+function doe2() {
+window.location.reload();
+}
+setTimeout(doe2, 200);
+setTimeout(doe,100);
+]]></script>
+</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/400779-1.xhtml b/layout/xul/base/src/crashtests/400779-1.xhtml new file mode 100644 index 000000000..c0f5d493c --- /dev/null +++ b/layout/xul/base/src/crashtests/400779-1.xhtml @@ -0,0 +1,16 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> +<script> + +function boom() +{ + var menulist = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menulist"); + document.getElementById("h").appendChild(menulist); +} + +</script> +</head> +<body onload="boom();"> +<xul:listboxbody><xul:hbox id="h"/></xul:listboxbody> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/402912-1.xhtml b/layout/xul/base/src/crashtests/402912-1.xhtml new file mode 100644 index 000000000..b2cb98dc5 --- /dev/null +++ b/layout/xul/base/src/crashtests/402912-1.xhtml @@ -0,0 +1,5 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<body> +<xul:vbox equalsize="always"><xul:hbox flex="1"><span><xul:hbox width="10" height="10"/></span><xul:button /></xul:hbox><xul:hbox maxheight="0"/></xul:vbox> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/408904-1.xul b/layout/xul/base/src/crashtests/408904-1.xul new file mode 100644 index 000000000..59f215c73 --- /dev/null +++ b/layout/xul/base/src/crashtests/408904-1.xul @@ -0,0 +1 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><grid><rows><label/></rows><columns><column><label/></column></columns></grid></window> diff --git a/layout/xul/base/src/crashtests/412479-1.xhtml b/layout/xul/base/src/crashtests/412479-1.xhtml new file mode 100644 index 000000000..b1086a816 --- /dev/null +++ b/layout/xul/base/src/crashtests/412479-1.xhtml @@ -0,0 +1,4 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head></head> +<body><xul:menubar style="display: table-column; padding: 10px 3000px;"/></body> +</html> diff --git a/layout/xul/base/src/crashtests/415394-1.xhtml b/layout/xul/base/src/crashtests/415394-1.xhtml new file mode 100644 index 000000000..7dc24dc9f --- /dev/null +++ b/layout/xul/base/src/crashtests/415394-1.xhtml @@ -0,0 +1,28 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="reftest-wait"> +<head> +<script type="text/javascript"> + +function boom() +{ + document.execCommand("justifycenter", false, null); + + var listboxbody = document.getElementById("lbb"); + listboxbody.height = 9; + setTimeout(boom2, 0); + + function boom2() + { + var td = document.createElementNS("http://www.w3.org/1999/xhtml", "td"); + listboxbody.appendChild(td); + document.documentElement.removeAttribute("class"); + } +} + +</script> +</head> + +<body onload="setTimeout(boom, 0);" contenteditable="true"><xul:listboxbody id="lbb"><xul:hbox/><span><col style="width: 100px;" /></span></xul:listboxbody></body> + +</html> diff --git a/layout/xul/base/src/crashtests/420424-1.xul b/layout/xul/base/src/crashtests/420424-1.xul new file mode 100644 index 000000000..e60841706 --- /dev/null +++ b/layout/xul/base/src/crashtests/420424-1.xul @@ -0,0 +1,6 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="document.getElementById('a').ensureElementIsVisible(null);"> + +<listbox id="a"/> + +</window> diff --git a/layout/xul/base/src/crashtests/430356-1.xhtml b/layout/xul/base/src/crashtests/430356-1.xhtml new file mode 100644 index 000000000..6e5717ae9 --- /dev/null +++ b/layout/xul/base/src/crashtests/430356-1.xhtml @@ -0,0 +1,5 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<body style="visibility: collapse;"> +<tabpanels xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="width: -moz-max-content;"></tabpanels> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/431738.xhtml b/layout/xul/base/src/crashtests/431738.xhtml new file mode 100644 index 000000000..9ce917a3f --- /dev/null +++ b/layout/xul/base/src/crashtests/431738.xhtml @@ -0,0 +1,7 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<body> +<div> +<span style="font-size: 0pt;"><xul:listboxbody><span style="border: 1px solid red;"/></xul:listboxbody></span> +</div> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/432058-1.xul b/layout/xul/base/src/crashtests/432058-1.xul new file mode 100644 index 000000000..a7f63adf8 --- /dev/null +++ b/layout/xul/base/src/crashtests/432058-1.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();"> + +<script type="text/javascript"> +// <![CDATA[ + +var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + +function boom() +{ + var lb = document.getElementById("lb"); + var firstli = document.getElementById("firstli"); + lb.appendChild(document.createElementNS(XUL_NS, "hbox")); + lb.appendChild(document.createElementNS(XUL_NS, "listitem")); + firstli.style.display = "none"; + + // Flush layout. + document.getBoxObjectFor(document.documentElement).height; + + lb.removeChild(firstli); +} + +// ]]> +</script> + +<listbox id="lb"><listitem id="firstli"/></listbox> + +</window> diff --git a/layout/xul/base/src/crashtests/432068-1.xul b/layout/xul/base/src/crashtests/432068-1.xul new file mode 100644 index 000000000..02c3114e6 --- /dev/null +++ b/layout/xul/base/src/crashtests/432068-1.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();"> + +<hbox style="display: none;"> + <bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="x"> + <content><listitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/></content> + </binding> + </bindings> +</hbox> + +<script type="text/javascript"> + +function boom() +{ + document.getElementById("b").style.MozBinding = "url('#x')"; + + // Flush layout. + document.documentElement.boxObject.height; + + document.getElementById("listbox").removeChild(document.getElementById("c")); +} + +</script> + +<listbox id="listbox"><listitem/><listitem id="b"/><listitem id="c"/></listbox> + +</window> diff --git a/layout/xul/base/src/crashtests/432068-2.xul b/layout/xul/base/src/crashtests/432068-2.xul new file mode 100644 index 000000000..c75984bae --- /dev/null +++ b/layout/xul/base/src/crashtests/432068-2.xul @@ -0,0 +1,24 @@ +<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var l = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "listitem");
+ l.style.display = "none";
+
+ var c = document.getElementById("c");
+ c.parentNode.insertBefore(l, c);
+
+ document.getElementById("listbox").removeChild(document.getElementById("c"));
+}
+
+</script>
+
+<listbox id="listbox"><listitem/><listitem id="c"/></listbox>
+
+</window>
diff --git a/layout/xul/base/src/crashtests/433296-1.xul b/layout/xul/base/src/crashtests/433296-1.xul new file mode 100644 index 000000000..10fd0ec7b --- /dev/null +++ b/layout/xul/base/src/crashtests/433296-1.xul @@ -0,0 +1,5 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<hbox><listboxbody><listheader/><hbox><iframe/></hbox></listboxbody><tabpanels><tabpanels/></tabpanels></hbox> + +</window> diff --git a/layout/xul/base/src/crashtests/433429.xul b/layout/xul/base/src/crashtests/433429.xul new file mode 100644 index 000000000..a52bce68f --- /dev/null +++ b/layout/xul/base/src/crashtests/433429.xul @@ -0,0 +1,23 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();"> + +<script type="text/javascript"> + +function boom() +{ + var listbox = document.getElementById("listbox"); + + listbox.removeChild(listbox.childNodes[1]); + document.documentElement.style.MozBinding = "url('data:text/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22foo%22%3E%3Ccontent%3E%0A%3Chbox%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fkeymaster%2Fgatekeeper%2Fthere.is.only.xul%22%2F%3E%0A%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E%0A')"; + document.documentElement.boxObject.height; + listbox.removeChild(listbox.childNodes[0]); +} + +</script> + +<listbox id="listbox" style="-moz-binding: url(data:text/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22foo%22%3E%3Ccontent%3E%0A%3Clistbox%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fkeymaster%2Fgatekeeper%2Fthere.is.only.xul%22%3E%3Cchildren%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%2F%3E%3C%2Flistbox%3E%0A%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E%0A);"><listitem/><listitem/></listbox> + +</window> diff --git a/layout/xul/base/src/crashtests/434458-1.xul b/layout/xul/base/src/crashtests/434458-1.xul new file mode 100644 index 000000000..fbec2a413 --- /dev/null +++ b/layout/xul/base/src/crashtests/434458-1.xul @@ -0,0 +1,20 @@ +<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, 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"/> + +</window> diff --git a/layout/xul/base/src/crashtests/452185.html b/layout/xul/base/src/crashtests/452185.html new file mode 100644 index 000000000..d4981ffdf --- /dev/null +++ b/layout/xul/base/src/crashtests/452185.html @@ -0,0 +1,3 @@ +<html><head></head><body><div style="position: absolute;"> </div>
+<style>div, head {-moz-binding:url(452185.xml#a);</style>
+</body></html>
diff --git a/layout/xul/base/src/crashtests/452185.xml b/layout/xul/base/src/crashtests/452185.xml new file mode 100644 index 000000000..655c43a8d --- /dev/null +++ b/layout/xul/base/src/crashtests/452185.xml @@ -0,0 +1,5 @@ +<bindings xmlns="http://www.mozilla.org/xbl" xmlns:xlink="http://www.w3.org/1999/xlink"> +<binding id="a"> +<content><tbody xmlns="http://www.w3.org/1999/xhtml"><style>*::before { content:"b"; }</style></tbody></content> + +</binding></bindings>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/460900-1.xul b/layout/xul/base/src/crashtests/460900-1.xul new file mode 100644 index 000000000..bff7c5c36 --- /dev/null +++ b/layout/xul/base/src/crashtests/460900-1.xul @@ -0,0 +1,3 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="document.getElementById('label').control='c';"> +<label id="label"><hbox><listboxbody><hbox/><tooltip/></listboxbody><autorepeatbutton/></hbox></label> +</window> diff --git a/layout/xul/base/src/crashtests/464149-1.xul b/layout/xul/base/src/crashtests/464149-1.xul new file mode 100644 index 000000000..556656f02 --- /dev/null +++ b/layout/xul/base/src/crashtests/464149-1.xul @@ -0,0 +1,24 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window onload="boom();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="m"><content><xul:textbox type="text"><children/></xul:textbox></content></binding> +</bindings> + +<script type="text/javascript"> +<![CDATA[ + +function boom() +{ + document.getElementById("b").style.MozBinding = 'url("data:text/xml,' + encodeURIComponent("<bindings xmlns='http://www.mozilla.org/xbl'><binding id='foo'><content><hbox xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'><children xmlns='http://www.mozilla.org/xbl'/></hbox></content></binding></bindings>\n") + '")'; +} + +]]> +</script> + +<listbox style="float: right;"><listitem/><listitem id="b"/><listitem><hbox style="-moz-binding: url(#m);"/></listitem></listbox> + +</window> diff --git a/layout/xul/base/src/crashtests/464407-1.xhtml b/layout/xul/base/src/crashtests/464407-1.xhtml new file mode 100644 index 000000000..83666a6a4 --- /dev/null +++ b/layout/xul/base/src/crashtests/464407-1.xhtml @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> +</head> +<body> + +<xul:radio style="overflow: auto; height: 72057594037927940pt; display: table-cell;"/> + +</body> +</html> diff --git a/layout/xul/base/src/crashtests/467080.xul b/layout/xul/base/src/crashtests/467080.xul new file mode 100644 index 000000000..bc579b0ee --- /dev/null +++ b/layout/xul/base/src/crashtests/467080.xul @@ -0,0 +1,24 @@ +<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML" style="-moz-binding:url(#a);" class="reftest-wait"> + +<content xmlns="http://www.mozilla.org/xbl" ordinal="-1"> +<mathml:median style="display: block;"/> +</content> + +<script xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ +function finish() { + document.documentElement.removeAttribute("class"); +} +function doe() { +document.documentElement.removeAttribute('style'); +setTimeout(finish, 0); +} +setTimeout(doe, 100); +]]></script> + + +<bindings xmlns="http://www.mozilla.org/xbl"> +<binding id="a"> +<content><children/></content> +</binding></bindings> +</window> diff --git a/layout/xul/base/src/crashtests/467481-1.xul b/layout/xul/base/src/crashtests/467481-1.xul new file mode 100644 index 000000000..56fbd4441 --- /dev/null +++ b/layout/xul/base/src/crashtests/467481-1.xul @@ -0,0 +1,6 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="document.getElementById('a').setAttribute('ordinal', 30);"> + <listbox> + <listitem id="a"/> + <listitem><iframe/></listitem> + </listbox> +</window> diff --git a/layout/xul/base/src/crashtests/470063-1.html b/layout/xul/base/src/crashtests/470063-1.html new file mode 100644 index 000000000..11c01b30e --- /dev/null +++ b/layout/xul/base/src/crashtests/470063-1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> + +function boom() +{ + document.removeChild(document.documentElement) + document.appendChild(document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "hbox")); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/layout/xul/base/src/crashtests/470272.html b/layout/xul/base/src/crashtests/470272.html new file mode 100644 index 000000000..f23de269b --- /dev/null +++ b/layout/xul/base/src/crashtests/470272.html @@ -0,0 +1,21 @@ +<html> +<head> +<script> +function doe2(i) { +document.documentElement.offsetHeight; +document.getElementById('a').setAttribute('style', 'display: -moz-inline-box;'); +document.documentElement.offsetHeight; +} +</script> +</head> +<body style="float: right; -moz-column-count: 2; height: 20%;" onload="setTimeout(doe2,0);"> + <div style="display: none;"></div> + <ul style="display: -moz-inline-box;"></ul> + <span id="a"> + <ul style="display: -moz-grid; overflow: scroll;"></ul> + <span style="display: -moz-inline-box; height: 10px;"> + <span style="position: absolute;"></span> + </span> + </span> +</body> +</html> diff --git a/layout/xul/base/src/crashtests/472189.xul b/layout/xul/base/src/crashtests/472189.xul new file mode 100644 index 000000000..e276d8fc7 --- /dev/null +++ b/layout/xul/base/src/crashtests/472189.xul @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window id="yourwindow" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script type="text/javascript"> +<![CDATA[ +function onload() { + document.addEventListener("DOMAttrModified", function() {}, true); + document.getElementById("test").setAttribute("value", "50"); +} +]]> +</script> +<progressmeter id="test"/> +</window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/475133.html b/layout/xul/base/src/crashtests/475133.html new file mode 100644 index 000000000..ea4ee6325 --- /dev/null +++ b/layout/xul/base/src/crashtests/475133.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> +function x() +{ + document.removeEventListener("DOMAttrModified", x, false); + document.removeChild(document.documentElement); +} + +function boom() +{ + var p = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "progressmeter"); + document.addEventListener("DOMAttrModified", x, false); + document.documentElement.appendChild(p); +} +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/layout/xul/base/src/crashtests/488210-1.xhtml b/layout/xul/base/src/crashtests/488210-1.xhtml new file mode 100644 index 000000000..9c8e2640c --- /dev/null +++ b/layout/xul/base/src/crashtests/488210-1.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<script type="text/javascript"> + +function boom() +{ + var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var listbox = document.createElementNS(XUL_NS, 'listbox'); + document.body.appendChild(listbox); + listbox.appendChild(document.createElementNS(XUL_NS, 'listitem')); + listbox.appendChild(document.createElementNS(XUL_NS, 'listboxbody')); + listbox.appendChild(document.createElementNS(XUL_NS, 'hbox')); +} + +</script> +</head> + +<body onload="boom();"></body> +</html> diff --git a/layout/xul/base/src/crashtests/495728-1.xul b/layout/xul/base/src/crashtests/495728-1.xul new file mode 100644 index 000000000..ee8498d05 --- /dev/null +++ b/layout/xul/base/src/crashtests/495728-1.xul @@ -0,0 +1,239 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window id="list-testcase" title="Testcase" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="reftest-wait"> + +<script> +function scrollup() { + var list = document.getElementById('list'); + var firstindex = list.getIndexOfItem(document.getElementById('first')); + list.ensureIndexIsVisible(firstindex); + setTimeout("document.documentElement.removeAttribute('class')",1); +} + +function scrolldown() { + var list = document.getElementById('list'); + var lastindex = list.getIndexOfItem(document.getElementById('last')); + list.ensureIndexIsVisible(lastindex); + setTimeout("scrollup()",1); +} + +window.addEventListener("load", scrolldown, false); +</script> + +<listbox id="list"> +<listitem label="Item x" id="first"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x"/> +<listitem label="Item x" id="last"/> +</listbox> + +</window> diff --git a/layout/xul/base/src/crashtests/508927-1.xul b/layout/xul/base/src/crashtests/508927-1.xul new file mode 100644 index 000000000..98faff4a6 --- /dev/null +++ b/layout/xul/base/src/crashtests/508927-1.xul @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><xul:listrows xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><xul:listboxbody/><children xmlns="http://www.mozilla.org/xbl"/></xul:listrows></content></binding></bindings> +<hbox style="-moz-binding: url(#foo);"><listitem/><listitem/></hbox> +</window> diff --git a/layout/xul/base/src/crashtests/508927-2.xul b/layout/xul/base/src/crashtests/508927-2.xul new file mode 100644 index 000000000..5bf4f9a0c --- /dev/null +++ b/layout/xul/base/src/crashtests/508927-2.xul @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><xul:listrows xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><xul:listboxbody/><children xmlns="http://www.mozilla.org/xbl"/></xul:listrows></content></binding></bindings> +<hbox style="-moz-binding: url(#foo);"><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/><listitem/></hbox> +</window> diff --git a/layout/xul/base/src/crashtests/514300-1.xul b/layout/xul/base/src/crashtests/514300-1.xul new file mode 100644 index 000000000..d0d655011 --- /dev/null +++ b/layout/xul/base/src/crashtests/514300-1.xul @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="document.getElementById('listbox').removeChild(document.getElementById('span'));"> + +<bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="foo"> + <content><listitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><children xmlns="http://www.mozilla.org/xbl"/></listitem></content> + </binding> +</bindings> + +<listbox id="listbox" style="-moz-binding: url(#foo)"><span xmlns="http://www.w3.org/1999/xhtml" id="span"/></listbox> + +</window> diff --git a/layout/xul/base/src/crashtests/536931-1.xhtml b/layout/xul/base/src/crashtests/536931-1.xhtml new file mode 100644 index 000000000..6f3fc1396 --- /dev/null +++ b/layout/xul/base/src/crashtests/536931-1.xhtml @@ -0,0 +1,4 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<listbox id="listbox" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><tab/></listbox> +<script>window.addEventListener("load", function() { document.documentElement.appendChild(document.getElementById("listbox")); }, false);</script> +</html> diff --git a/layout/xul/base/src/crashtests/538308-1.xul b/layout/xul/base/src/crashtests/538308-1.xul new file mode 100644 index 000000000..a96f3fa4e --- /dev/null +++ b/layout/xul/base/src/crashtests/538308-1.xul @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="run()"> + + <tree id="tr" flex="1"> + <treecols> + <treecol/> + </treecols> + <treechildren> + <html:optgroup id="group"> + <html:option id="victim" label="never see this"/> + </html:optgroup> + </treechildren> + </tree> + + <script type="text/javascript"><![CDATA[ + function run() { + group = document.getElementById("group"); + tc = document.createElement("treechildren"); + group.appendChild(tc); + + v = document.getElementById("victim"); + v.parentNode.removeChild(v); + v = null; + + tree = document.getElementById("tr"); + col = tree.columns[0]; + alert(tree.view.getItemAtIndex(1, col)); + } + ]]></script> +</window> diff --git a/layout/xul/base/src/crashtests/557174-1.xml b/layout/xul/base/src/crashtests/557174-1.xml new file mode 100644 index 000000000..02850a2db --- /dev/null +++ b/layout/xul/base/src/crashtests/557174-1.xml @@ -0,0 +1 @@ +<ther:window xmlns:ther="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" a="" e=""><HTML><ther:statusbar l="" c=""><ther:menulist d=""><ther:menu t="" i="" l=""><mat:h xmlns:mat="http://www.w3.org/1998/Math/MathML" w=""/></ther:menu><ther:menupopup p=""/><ther:menu a="" t="" l=""><ther:menuseparator u="" x=""><xht:html xmlns:xht="http://www.w3.org/1999/xhtml" x=""><xht:body d=""><xht:abbr d=""><xht:abbr p=""><xht:small s=""><xht:a s=""><xht:var e=""><xht:samp e=""><xht:code p=""><xht:b e=""><xht:b d=""><xht:del t=""><xht:h4 r=""><xht:var l=""><xht:i r=""><xht:em r=""><xht:em n=""><xht:map g=""><xht:isindex d=""/></xht:map></xht:em></xht:em></xht:i></xht:var></xht:h4></xht:del></xht:b></xht:b></xht:code></xht:samp></xht:var></xht:a></xht:small></xht:abbr></xht:abbr></xht:body></xht:html></ther:menuseparator></ther:menu></ther:menulist></ther:statusbar></HTML></ther:window>
\ No newline at end of file diff --git a/layout/xul/base/src/crashtests/564705-1.xul b/layout/xul/base/src/crashtests/564705-1.xul new file mode 100644 index 000000000..b0f29bef7 --- /dev/null +++ b/layout/xul/base/src/crashtests/564705-1.xul @@ -0,0 +1,6 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><label value="…" accesskey="b"></label></window> + diff --git a/layout/xul/base/src/crashtests/583957-1.html b/layout/xul/base/src/crashtests/583957-1.html new file mode 100644 index 000000000..85b51bf0c --- /dev/null +++ b/layout/xul/base/src/crashtests/583957-1.html @@ -0,0 +1,20 @@ +<html> +<head> +<script> + +function boom() +{ + window.addEventListener("DOMSubtreeModified", function(){}, false); + + var m = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem"); + document.body.appendChild(m); + m.setAttribute("type", "checkbox"); + m.setAttribute("checked", "true"); + m.removeAttribute("type"); +} + +</script> +</head> + +<body onload="boom();"></body> +</html> diff --git a/layout/xul/base/src/crashtests/crashtests.list b/layout/xul/base/src/crashtests/crashtests.list new file mode 100644 index 000000000..f7651798b --- /dev/null +++ b/layout/xul/base/src/crashtests/crashtests.list @@ -0,0 +1,88 @@ +load 131008-1.xul +load 137216-1.xul +load 140218-1.xml +load 151826-1.xul +load 168724-1.xul +load 189814-1.xul +load 237787-1.xul +load 265161-1.xul +load 289410-1.xul +load 291702-1.xul +load 291702-2.xul +load 291702-3.xul +load 294371-1.xul +load 311457-1.html +load 321056-1.xhtml +load 326834-1.html +load 322786-1.xul +load 325377.xul +load 326879-1.xul +load 327776-1.xul +load 328135-1.xul +load 329327-1.xul +load 329407-1.xml +load 329477-1.xhtml +load 336962-1.xul +load 344228-1.xul +load 346083-1.xul +load 346281-1.xul +load 350460.xul +load 360642-1.xul +load 365151.xul +load 366112-1.xul +load 369942-1.xhtml +load 374102-1.xul +load 376137-1.html +load 376137-2.html +load 377592-1.svg +load 381862.html +load 382746-1.xul +load 382899-1.xul +load 383236-1.xul +load 384037-1.xhtml +load 384105-1.html +load 384491-1.xhtml +load 384871-1.html +load 387033-1.xhtml +load 387080-1.xul +load 391974-1.html +load 394120-1.xhtml +load 397293.xhtml +load 397304-1.html +load 398326-1.xhtml +load 399013.xul +load 400779-1.xhtml +load 402912-1.xhtml +load 408904-1.xul +load 412479-1.xhtml +asserts(4) load 415394-1.xhtml # Bug 163838 +load 420424-1.xul +load 430356-1.xhtml +load 431738.xhtml +load 432058-1.xul +load 432068-1.xul +load 432068-2.xul +load 433296-1.xul +load 433429.xul +load 434458-1.xul +load 452185.html +load 460900-1.xul +load 464149-1.xul +asserts-if(winWidget,1) load 464407-1.xhtml # Bug 450974 +load 467080.xul +load 467481-1.xul +load 470063-1.html +load 470272.html +load 472189.xul +load 475133.html +load 488210-1.xhtml +load 495728-1.xul +load 508927-1.xul +load 508927-2.xul +load 514300-1.xul +load 536931-1.xhtml +asserts(1) load 538308-1.xul +load 557174-1.xml +load menulist-focused.xhtml +load 564705-1.xul +load 583957-1.html diff --git a/layout/xul/base/src/crashtests/menulist-focused.xhtml b/layout/xul/base/src/crashtests/menulist-focused.xhtml new file mode 100644 index 000000000..7a09a838d --- /dev/null +++ b/layout/xul/base/src/crashtests/menulist-focused.xhtml @@ -0,0 +1,5 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<body> +<xul:menulist focused="true"/> +</body> +</html> diff --git a/layout/xul/base/src/moz.build b/layout/xul/base/src/moz.build new file mode 100644 index 000000000..5fb54ba5b --- /dev/null +++ b/layout/xul/base/src/moz.build @@ -0,0 +1,55 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MODULE = 'layout' + +CPP_SOURCES += [ + 'nsBox.cpp', + 'nsBoxFrame.cpp', + 'nsBoxLayout.cpp', + 'nsBoxLayoutState.cpp', + 'nsButtonBoxFrame.cpp', + 'nsRepeatService.cpp', + 'nsRootBoxFrame.cpp', + 'nsScrollBoxFrame.cpp', + 'nsScrollbarButtonFrame.cpp', + 'nsScrollbarFrame.cpp', + 'nsSliderFrame.cpp', + 'nsSprocketLayout.cpp', + 'nsStackFrame.cpp', + 'nsStackLayout.cpp', + 'nsXULTooltipListener.cpp', +] + +if CONFIG['MOZ_XUL']: + CPP_SOURCES += [ + 'nsScrollBoxObject.cpp', + 'nsContainerBoxObject.cpp', + 'nsMenuBoxObject.cpp', + 'nsPopupBoxObject.cpp', + 'nsListBoxObject.cpp', + 'nsBoxObject.cpp', + 'nsImageBoxFrame.cpp', + 'nsDocElementBoxFrame.cpp', + 'nsLeafBoxFrame.cpp', + 'nsTextBoxFrame.cpp', + 'nsGroupBoxFrame.cpp', + 'nsSplitterFrame.cpp', + 'nsDeckFrame.cpp', + 'nsProgressMeterFrame.cpp', + 'nsMenuPopupFrame.cpp', + 'nsMenuFrame.cpp', + 'nsMenuBarFrame.cpp', + 'nsMenuBarListener.cpp', + 'nsPopupSetFrame.cpp', + 'nsTitleBarFrame.cpp', + 'nsResizerFrame.cpp', + 'nsListBoxBodyFrame.cpp', + 'nsListItemFrame.cpp', + 'nsListBoxLayout.cpp', + 'nsXULLabelFrame.cpp', + 'nsXULPopupManager.cpp', + ] diff --git a/layout/xul/base/src/nsBox.cpp b/layout/xul/base/src/nsBox.cpp new file mode 100644 index 000000000..84e03f8f5 --- /dev/null +++ b/layout/xul/base/src/nsBox.cpp @@ -0,0 +1,972 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBoxLayoutState.h" +#include "nsBox.h" +#include "nsBoxFrame.h" +#include "nsPresContext.h" +#include "nsCOMPtr.h" +#include "nsIContent.h" +#include "nsContainerFrame.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsFrameManager.h" +#include "nsIDOMNode.h" +#include "nsIDOMMozNamedAttrMap.h" +#include "nsIDOMAttr.h" +#include "nsITheme.h" +#include "nsIServiceManager.h" +#include "nsBoxLayout.h" +#include "FrameLayerBuilder.h" +#include <algorithm> + +using namespace mozilla; + +#ifdef DEBUG_LAYOUT +int32_t gIndent = 0; +#endif + +#ifdef DEBUG_LAYOUT +void +nsBoxAddIndents() +{ + for(int32_t i=0; i < gIndent; i++) + { + printf(" "); + } +} +#endif + +#ifdef DEBUG_LAYOUT +void +nsBox::AppendAttribute(const nsAutoString& aAttribute, const nsAutoString& aValue, nsAutoString& aResult) +{ + aResult.Append(aAttribute); + aResult.AppendLiteral("='"); + aResult.Append(aValue); + aResult.AppendLiteral("' "); +} + +void +nsBox::ListBox(nsAutoString& aResult) +{ + nsAutoString name; + GetBoxName(name); + + char addr[100]; + sprintf(addr, "[@%p] ", static_cast<void*>(this)); + + aResult.AppendASCII(addr); + aResult.Append(name); + aResult.AppendLiteral(" "); + + nsIContent* content = GetContent(); + + // add on all the set attributes + if (content) { + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content)); + nsCOMPtr<nsIDOMMozNamedAttrMap> namedMap; + + node->GetAttributes(getter_AddRefs(namedMap)); + uint32_t length; + namedMap->GetLength(&length); + + nsCOMPtr<nsIDOMAttr> attribute; + for (uint32_t i = 0; i < length; ++i) + { + namedMap->Item(i, getter_AddRefs(attribute)); + attribute->GetName(name); + nsAutoString value; + attribute->GetValue(value); + AppendAttribute(name, value, aResult); + } + } +} + +NS_IMETHODIMP +nsBox::DumpBox(FILE* aFile) +{ + nsAutoString s; + ListBox(s); + fprintf(aFile, "%s", NS_LossyConvertUTF16toASCII(s).get()); + return NS_OK; +} + +void +nsBox::PropagateDebug(nsBoxLayoutState& aState) +{ + // propagate debug information + if (mState & NS_STATE_DEBUG_WAS_SET) { + if (mState & NS_STATE_SET_TO_DEBUG) + SetDebug(aState, true); + else + SetDebug(aState, false); + } else if (mState & NS_STATE_IS_ROOT) { + SetDebug(aState, gDebug); + } +} +#endif + +#ifdef DEBUG_LAYOUT +void +nsBox::GetBoxName(nsAutoString& aName) +{ + aName.AssignLiteral("Box"); +} +#endif + +nsresult +nsBox::BeginLayout(nsBoxLayoutState& aState) +{ +#ifdef DEBUG_LAYOUT + + nsBoxAddIndents(); + printf("Layout: "); + DumpBox(stdout); + printf("\n"); + gIndent++; +#endif + + // mark ourselves as dirty so no child under us + // can post an incremental layout. + // XXXldb Is this still needed? + mState |= NS_FRAME_HAS_DIRTY_CHILDREN; + + if (GetStateBits() & NS_FRAME_IS_DIRTY) + { + // If the parent is dirty, all the children are dirty (nsHTMLReflowState + // does this too). + nsIFrame* box; + for (box = GetChildBox(); box; box = box->GetNextBox()) + box->AddStateBits(NS_FRAME_IS_DIRTY); + } + + // Another copy-over from nsHTMLReflowState. + // Since we are in reflow, we don't need to store these properties anymore. + FrameProperties props = Properties(); + props.Delete(UsedBorderProperty()); + props.Delete(UsedPaddingProperty()); + props.Delete(UsedMarginProperty()); + +#ifdef DEBUG_LAYOUT + PropagateDebug(aState); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsBox::DoLayout(nsBoxLayoutState& aState) +{ + return NS_OK; +} + +nsresult +nsBox::EndLayout(nsBoxLayoutState& aState) +{ + + #ifdef DEBUG_LAYOUT + --gIndent; + #endif + + return SyncLayout(aState); +} + +bool nsBox::gGotTheme = false; +nsITheme* nsBox::gTheme = nullptr; + +nsBox::nsBox() +{ + MOZ_COUNT_CTOR(nsBox); + //mX = 0; + //mY = 0; + if (!gGotTheme) { + gGotTheme = true; + CallGetService("@mozilla.org/chrome/chrome-native-theme;1", &gTheme); + } +} + +nsBox::~nsBox() +{ + // NOTE: This currently doesn't get called for |nsBoxToBlockAdaptor| + // objects, so don't rely on putting anything here. + MOZ_COUNT_DTOR(nsBox); +} + +/* static */ void +nsBox::Shutdown() +{ + gGotTheme = false; + NS_IF_RELEASE(gTheme); +} + +NS_IMETHODIMP +nsBox::RelayoutChildAtOrdinal(nsBoxLayoutState& aState, nsIFrame* aChild) +{ + return NS_OK; +} + +nsresult +nsIFrame::GetClientRect(nsRect& aClientRect) +{ + aClientRect = mRect; + aClientRect.MoveTo(0,0); + + nsMargin borderPadding; + GetBorderAndPadding(borderPadding); + + aClientRect.Deflate(borderPadding); + + if (aClientRect.width < 0) + aClientRect.width = 0; + + if (aClientRect.height < 0) + aClientRect.height = 0; + + // NS_ASSERTION(aClientRect.width >=0 && aClientRect.height >= 0, "Content Size < 0"); + + return NS_OK; +} + +void +nsBox::SetBounds(nsBoxLayoutState& aState, const nsRect& aRect, bool aRemoveOverflowAreas) +{ + NS_BOX_ASSERTION(this, aRect.width >=0 && aRect.height >= 0, "SetBounds Size < 0"); + + nsRect rect(mRect); + + uint32_t flags = 0; + GetLayoutFlags(flags); + + uint32_t stateFlags = aState.LayoutFlags(); + + flags |= stateFlags; + + if ((flags & NS_FRAME_NO_MOVE_FRAME) == NS_FRAME_NO_MOVE_FRAME) + SetSize(nsSize(aRect.width, aRect.height)); + else + SetRect(aRect); + + // Nuke the overflow area. The caller is responsible for restoring + // it if necessary. + if (aRemoveOverflowAreas) { + // remove the previously stored overflow area + ClearOverflowRects(); + } + + if (!(flags & NS_FRAME_NO_MOVE_VIEW)) + { + nsContainerFrame::PositionFrameView(this); + if ((rect.x != aRect.x) || (rect.y != aRect.y)) + nsContainerFrame::PositionChildViews(this); + } + + + /* + // only if the origin changed + if ((rect.x != aRect.x) || (rect.y != aRect.y)) { + if (frame->HasView()) { + nsContainerFrame::PositionFrameView(presContext, frame, + frame->GetView()); + } else { + nsContainerFrame::PositionChildViews(presContext, frame); + } + } + */ +} + +void +nsBox::GetLayoutFlags(uint32_t& aFlags) +{ + aFlags = 0; +} + + +NS_IMETHODIMP +nsIFrame::GetBorderAndPadding(nsMargin& aBorderAndPadding) +{ + aBorderAndPadding.SizeTo(0, 0, 0, 0); + nsresult rv = GetBorder(aBorderAndPadding); + if (NS_FAILED(rv)) + return rv; + + nsMargin padding; + rv = GetPadding(padding); + if (NS_FAILED(rv)) + return rv; + + aBorderAndPadding += padding; + + return rv; +} + +NS_IMETHODIMP +nsBox::GetBorder(nsMargin& aMargin) +{ + aMargin.SizeTo(0,0,0,0); + + const nsStyleDisplay* disp = StyleDisplay(); + if (disp->mAppearance && gTheme) { + // Go to the theme for the border. + nsPresContext *context = PresContext(); + if (gTheme->ThemeSupportsWidget(context, this, disp->mAppearance)) { + nsIntMargin margin(0, 0, 0, 0); + gTheme->GetWidgetBorder(context->DeviceContext(), this, + disp->mAppearance, &margin); + aMargin.top = context->DevPixelsToAppUnits(margin.top); + aMargin.right = context->DevPixelsToAppUnits(margin.right); + aMargin.bottom = context->DevPixelsToAppUnits(margin.bottom); + aMargin.left = context->DevPixelsToAppUnits(margin.left); + return NS_OK; + } + } + + aMargin = StyleBorder()->GetComputedBorder(); + + return NS_OK; +} + +NS_IMETHODIMP +nsBox::GetPadding(nsMargin& aMargin) +{ + const nsStyleDisplay *disp = StyleDisplay(); + if (disp->mAppearance && gTheme) { + // Go to the theme for the padding. + nsPresContext *context = PresContext(); + if (gTheme->ThemeSupportsWidget(context, this, disp->mAppearance)) { + nsIntMargin margin(0, 0, 0, 0); + bool useThemePadding; + + useThemePadding = gTheme->GetWidgetPadding(context->DeviceContext(), + this, disp->mAppearance, + &margin); + if (useThemePadding) { + aMargin.top = context->DevPixelsToAppUnits(margin.top); + aMargin.right = context->DevPixelsToAppUnits(margin.right); + aMargin.bottom = context->DevPixelsToAppUnits(margin.bottom); + aMargin.left = context->DevPixelsToAppUnits(margin.left); + return NS_OK; + } + } + } + + aMargin.SizeTo(0,0,0,0); + StylePadding()->GetPadding(aMargin); + + return NS_OK; +} + +NS_IMETHODIMP +nsBox::GetMargin(nsMargin& aMargin) +{ + aMargin.SizeTo(0,0,0,0); + StyleMargin()->GetMargin(aMargin); + + return NS_OK; +} + +void +nsBox::SizeNeedsRecalc(nsSize& aSize) +{ + aSize.width = -1; + aSize.height = -1; +} + +void +nsBox::CoordNeedsRecalc(nscoord& aFlex) +{ + aFlex = -1; +} + +bool +nsBox::DoesNeedRecalc(const nsSize& aSize) +{ + return (aSize.width == -1 || aSize.height == -1); +} + +bool +nsBox::DoesNeedRecalc(nscoord aCoord) +{ + return (aCoord == -1); +} + +nsSize +nsBox::GetPrefSize(nsBoxLayoutState& aState) +{ + NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); + + nsSize pref(0,0); + DISPLAY_PREF_SIZE(this, pref); + + if (IsCollapsed()) + return pref; + + AddBorderAndPadding(pref); + bool widthSet, heightSet; + nsIFrame::AddCSSPrefSize(this, pref, widthSet, heightSet); + + nsSize minSize = GetMinSize(aState); + nsSize maxSize = GetMaxSize(aState); + return BoundsCheck(minSize, pref, maxSize); +} + +nsSize +nsBox::GetMinSize(nsBoxLayoutState& aState) +{ + NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); + + nsSize min(0,0); + DISPLAY_MIN_SIZE(this, min); + + if (IsCollapsed()) + return min; + + AddBorderAndPadding(min); + bool widthSet, heightSet; + nsIFrame::AddCSSMinSize(aState, this, min, widthSet, heightSet); + return min; +} + +nsSize +nsBox::GetMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) +{ + return nsSize(0, 0); +} + +nsSize +nsBox::GetMaxSize(nsBoxLayoutState& aState) +{ + NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); + + nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE); + DISPLAY_MAX_SIZE(this, maxSize); + + if (IsCollapsed()) + return maxSize; + + AddBorderAndPadding(maxSize); + bool widthSet, heightSet; + nsIFrame::AddCSSMaxSize(this, maxSize, widthSet, heightSet); + return maxSize; +} + +nscoord +nsBox::GetFlex(nsBoxLayoutState& aState) +{ + nscoord flex = 0; + + nsIFrame::AddCSSFlex(aState, this, flex); + + return flex; +} + +uint32_t +nsIFrame::GetOrdinal() +{ + uint32_t ordinal = StyleXUL()->mBoxOrdinal; + + // When present, attribute value overrides CSS. + nsIContent* content = GetContent(); + if (content && content->IsXUL()) { + nsresult error; + nsAutoString value; + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, value); + if (!value.IsEmpty()) { + ordinal = value.ToInteger(&error); + } + } + + return ordinal; +} + +nscoord +nsBox::GetBoxAscent(nsBoxLayoutState& aState) +{ + if (IsCollapsed()) + return 0; + + return GetPrefSize(aState).height; +} + +bool +nsBox::IsCollapsed() +{ + return StyleVisibility()->mVisible == NS_STYLE_VISIBILITY_COLLAPSE; +} + +nsresult +nsIFrame::Layout(nsBoxLayoutState& aState) +{ + NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); + + nsBox *box = static_cast<nsBox*>(this); + DISPLAY_LAYOUT(box); + + box->BeginLayout(aState); + + box->DoLayout(aState); + + box->EndLayout(aState); + + return NS_OK; +} + +bool +nsBox::DoesClipChildren() +{ + const nsStyleDisplay* display = StyleDisplay(); + NS_ASSERTION((display->mOverflowY == NS_STYLE_OVERFLOW_CLIP) == + (display->mOverflowX == NS_STYLE_OVERFLOW_CLIP), + "If one overflow is clip, the other should be too"); + return display->mOverflowX == NS_STYLE_OVERFLOW_CLIP; +} + +nsresult +nsBox::SyncLayout(nsBoxLayoutState& aState) +{ + /* + if (IsCollapsed()) { + CollapseChild(aState, this, true); + return NS_OK; + } + */ + + + if (GetStateBits() & NS_FRAME_IS_DIRTY) + Redraw(aState); + + RemoveStateBits(NS_FRAME_HAS_DIRTY_CHILDREN | NS_FRAME_IS_DIRTY + | NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW); + + nsPresContext* presContext = aState.PresContext(); + + uint32_t flags = 0; + GetLayoutFlags(flags); + + uint32_t stateFlags = aState.LayoutFlags(); + + flags |= stateFlags; + + nsRect visualOverflow; + + if (ComputesOwnOverflowArea()) { + visualOverflow = GetVisualOverflowRect(); + } + else { + nsRect rect(nsPoint(0, 0), GetSize()); + nsOverflowAreas overflowAreas(rect, rect); + if (!DoesClipChildren() && !IsCollapsed()) { + // See if our child frames caused us to overflow after being laid + // out. If so, store the overflow area. This normally can't happen + // in XUL, but it can happen with the CSS 'outline' property and + // possibly with other exotic stuff (e.g. relatively positioned + // frames in HTML inside XUL). + nsLayoutUtils::UnionChildOverflow(this, overflowAreas); + } + + FinishAndStoreOverflow(overflowAreas, GetSize()); + visualOverflow = overflowAreas.VisualOverflow(); + } + + nsView* view = GetView(); + if (view) { + // Make sure the frame's view is properly sized and positioned and has + // things like opacity correct + nsContainerFrame::SyncFrameViewAfterReflow(presContext, this, view, + visualOverflow, flags); + } + + return NS_OK; +} + +nsresult +nsIFrame::Redraw(nsBoxLayoutState& aState) +{ + if (aState.PaintingDisabled()) + return NS_OK; + + // nsStackLayout, at least, expects us to repaint descendants even + // if a damage rect is provided + InvalidateFrameSubtree(); + + return NS_OK; +} + +bool +nsIFrame::AddCSSPrefSize(nsIFrame* aBox, nsSize& aSize, bool &aWidthSet, bool &aHeightSet) +{ + aWidthSet = false; + aHeightSet = false; + + // add in the css min, max, pref + const nsStylePosition* position = aBox->StylePosition(); + + // see if the width or height was specifically set + // XXX Handle eStyleUnit_Enumerated? + // (Handling the eStyleUnit_Enumerated types requires + // GetPrefSize/GetMinSize methods that don't consider + // (min-/max-/)(width/height) properties.) + const nsStyleCoord &width = position->mWidth; + if (width.GetUnit() == eStyleUnit_Coord) { + aSize.width = width.GetCoordValue(); + aWidthSet = true; + } else if (width.IsCalcUnit()) { + if (!width.CalcHasPercent()) { + // pass 0 for percentage basis since we know there are no %s + aSize.width = nsRuleNode::ComputeComputedCalc(width, 0); + if (aSize.width < 0) + aSize.width = 0; + aWidthSet = true; + } + } + + const nsStyleCoord &height = position->mHeight; + if (height.GetUnit() == eStyleUnit_Coord) { + aSize.height = height.GetCoordValue(); + aHeightSet = true; + } else if (height.IsCalcUnit()) { + if (!height.CalcHasPercent()) { + // pass 0 for percentage basis since we know there are no %s + aSize.height = nsRuleNode::ComputeComputedCalc(height, 0); + if (aSize.height < 0) + aSize.height = 0; + aHeightSet = true; + } + } + + nsIContent* content = aBox->GetContent(); + // ignore 'height' and 'width' attributes if the actual element is not XUL + // For example, we might be magic XUL frames whose primary content is an HTML + // <select> + if (content && content->IsXUL()) { + nsAutoString value; + nsresult error; + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value); + if (!value.IsEmpty()) { + value.Trim("%"); + + aSize.width = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + aWidthSet = true; + } + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value); + if (!value.IsEmpty()) { + value.Trim("%"); + + aSize.height = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + aHeightSet = true; + } + } + + return (aWidthSet && aHeightSet); +} + + +bool +nsIFrame::AddCSSMinSize(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize& aSize, + bool &aWidthSet, bool &aHeightSet) +{ + aWidthSet = false; + aHeightSet = false; + + bool canOverride = true; + + // See if a native theme wants to supply a minimum size. + const nsStyleDisplay* display = aBox->StyleDisplay(); + if (display->mAppearance) { + nsITheme *theme = aState.PresContext()->GetTheme(); + if (theme && theme->ThemeSupportsWidget(aState.PresContext(), aBox, display->mAppearance)) { + nsIntSize size; + nsRenderingContext* rendContext = aState.GetRenderingContext(); + if (rendContext) { + theme->GetMinimumWidgetSize(rendContext, aBox, + display->mAppearance, &size, &canOverride); + if (size.width) { + aSize.width = aState.PresContext()->DevPixelsToAppUnits(size.width); + aWidthSet = true; + } + if (size.height) { + aSize.height = aState.PresContext()->DevPixelsToAppUnits(size.height); + aHeightSet = true; + } + } + } + } + + // add in the css min, max, pref + const nsStylePosition* position = aBox->StylePosition(); + + // same for min size. Unfortunately min size is always set to 0. So for now + // we will assume 0 (as a coord) means not set. + const nsStyleCoord &minWidth = position->mMinWidth; + if ((minWidth.GetUnit() == eStyleUnit_Coord && + minWidth.GetCoordValue() != 0) || + (minWidth.IsCalcUnit() && !minWidth.CalcHasPercent())) { + nscoord min = nsRuleNode::ComputeCoordPercentCalc(minWidth, 0); + if (!aWidthSet || (min > aSize.width && canOverride)) { + aSize.width = min; + aWidthSet = true; + } + } else if (minWidth.GetUnit() == eStyleUnit_Percent) { + NS_ASSERTION(minWidth.GetPercentValue() == 0.0f, + "Non-zero percentage values not currently supported"); + aSize.width = 0; + aWidthSet = true; // FIXME: should we really do this for + // nonzero values? + } + // XXX Handle eStyleUnit_Enumerated? + // (Handling the eStyleUnit_Enumerated types requires + // GetPrefSize/GetMinSize methods that don't consider + // (min-/max-/)(width/height) properties. + // calc() with percentage is treated like '0' (unset) + + const nsStyleCoord &minHeight = position->mMinHeight; + if ((minHeight.GetUnit() == eStyleUnit_Coord && + minHeight.GetCoordValue() != 0) || + (minHeight.IsCalcUnit() && !minHeight.CalcHasPercent())) { + nscoord min = nsRuleNode::ComputeCoordPercentCalc(minHeight, 0); + if (!aHeightSet || (min > aSize.height && canOverride)) { + aSize.height = min; + aHeightSet = true; + } + } else if (minHeight.GetUnit() == eStyleUnit_Percent) { + NS_ASSERTION(position->mMinHeight.GetPercentValue() == 0.0f, + "Non-zero percentage values not currently supported"); + aSize.height = 0; + aHeightSet = true; // FIXME: should we really do this for + // nonzero values? + } + // calc() with percentage is treated like '0' (unset) + + nsIContent* content = aBox->GetContent(); + if (content && content->IsXUL()) { + nsAutoString value; + nsresult error; + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::minwidth, value); + if (!value.IsEmpty()) + { + value.Trim("%"); + + nscoord val = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + if (val > aSize.width) + aSize.width = val; + aWidthSet = true; + } + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::minheight, value); + if (!value.IsEmpty()) + { + value.Trim("%"); + + nscoord val = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + if (val > aSize.height) + aSize.height = val; + + aHeightSet = true; + } + } + + return (aWidthSet && aHeightSet); +} + +bool +nsIFrame::AddCSSMaxSize(nsIFrame* aBox, nsSize& aSize, bool &aWidthSet, bool &aHeightSet) +{ + aWidthSet = false; + aHeightSet = false; + + // add in the css min, max, pref + const nsStylePosition* position = aBox->StylePosition(); + + // and max + // see if the width or height was specifically set + // XXX Handle eStyleUnit_Enumerated? + // (Handling the eStyleUnit_Enumerated types requires + // GetPrefSize/GetMinSize methods that don't consider + // (min-/max-/)(width/height) properties.) + const nsStyleCoord maxWidth = position->mMaxWidth; + if (maxWidth.ConvertsToLength()) { + aSize.width = nsRuleNode::ComputeCoordPercentCalc(maxWidth, 0); + aWidthSet = true; + } + // percentages and calc() with percentages are treated like 'none' + + const nsStyleCoord &maxHeight = position->mMaxHeight; + if (maxHeight.ConvertsToLength()) { + aSize.height = nsRuleNode::ComputeCoordPercentCalc(maxHeight, 0); + aHeightSet = true; + } + // percentages and calc() with percentages are treated like 'none' + + nsIContent* content = aBox->GetContent(); + if (content && content->IsXUL()) { + nsAutoString value; + nsresult error; + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::maxwidth, value); + if (!value.IsEmpty()) { + value.Trim("%"); + + nscoord val = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + aSize.width = val; + aWidthSet = true; + } + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::maxheight, value); + if (!value.IsEmpty()) { + value.Trim("%"); + + nscoord val = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + aSize.height = val; + + aHeightSet = true; + } + } + + return (aWidthSet || aHeightSet); +} + +bool +nsIFrame::AddCSSFlex(nsBoxLayoutState& aState, nsIFrame* aBox, nscoord& aFlex) +{ + bool flexSet = false; + + // get the flexibility + aFlex = aBox->StyleXUL()->mBoxFlex; + + // attribute value overrides CSS + nsIContent* content = aBox->GetContent(); + if (content && content->IsXUL()) { + nsresult error; + nsAutoString value; + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::flex, value); + if (!value.IsEmpty()) { + value.Trim("%"); + aFlex = value.ToInteger(&error); + flexSet = true; + } + } + + if (aFlex < 0) + aFlex = 0; + if (aFlex >= nscoord_MAX) + aFlex = nscoord_MAX - 1; + + return flexSet || aFlex > 0; +} + +void +nsBox::AddBorderAndPadding(nsSize& aSize) +{ + AddBorderAndPadding(this, aSize); +} + +void +nsBox::AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize) +{ + nsMargin borderPadding(0,0,0,0); + aBox->GetBorderAndPadding(borderPadding); + AddMargin(aSize, borderPadding); +} + +void +nsBox::AddMargin(nsIFrame* aChild, nsSize& aSize) +{ + nsMargin margin(0,0,0,0); + aChild->GetMargin(margin); + AddMargin(aSize, margin); +} + +void +nsBox::AddMargin(nsSize& aSize, const nsMargin& aMargin) +{ + if (aSize.width != NS_INTRINSICSIZE) + aSize.width += aMargin.left + aMargin.right; + + if (aSize.height != NS_INTRINSICSIZE) + aSize.height += aMargin.top + aMargin.bottom; +} + +nscoord +nsBox::BoundsCheck(nscoord aMin, nscoord aPref, nscoord aMax) +{ + if (aPref > aMax) + aPref = aMax; + + if (aPref < aMin) + aPref = aMin; + + return aPref; +} + +nsSize +nsBox::BoundsCheckMinMax(const nsSize& aMinSize, const nsSize& aMaxSize) +{ + return nsSize(std::max(aMaxSize.width, aMinSize.width), + std::max(aMaxSize.height, aMinSize.height)); +} + +nsSize +nsBox::BoundsCheck(const nsSize& aMinSize, const nsSize& aPrefSize, const nsSize& aMaxSize) +{ + return nsSize(BoundsCheck(aMinSize.width, aPrefSize.width, aMaxSize.width), + BoundsCheck(aMinSize.height, aPrefSize.height, aMaxSize.height)); +} + +#ifdef DEBUG_LAYOUT +nsresult +nsBox::SetDebug(nsBoxLayoutState& aState, bool aDebug) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsBox::GetDebugBoxAt( const nsPoint& aPoint, + nsIFrame** aBox) +{ + nsRect thisRect(nsPoint(0,0), GetSize()); + if (!thisRect.Contains(aPoint)) + return NS_ERROR_FAILURE; + + nsIFrame* child = GetChildBox(); + nsIFrame* hit = nullptr; + + *aBox = nullptr; + while (nullptr != child) { + nsresult rv = child->GetDebugBoxAt(aPoint - child->GetOffsetTo(this), &hit); + + if (NS_SUCCEEDED(rv) && hit) { + *aBox = hit; + } + child = child->GetNextBox(); + } + + // found a child + if (*aBox) { + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + + +NS_IMETHODIMP +nsBox::GetDebug(bool& aDebug) +{ + aDebug = false; + return NS_OK; +} + +#endif diff --git a/layout/xul/base/src/nsBox.h b/layout/xul/base/src/nsBox.h new file mode 100644 index 000000000..2329565c9 --- /dev/null +++ b/layout/xul/base/src/nsBox.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBox_h___ +#define nsBox_h___ + +#include "mozilla/Attributes.h" +#include "nsIFrame.h" + +class nsITheme; + +#define NS_STATE_IS_ROOT NS_FRAME_STATE_BIT(24) +#define NS_STATE_SET_TO_DEBUG NS_FRAME_STATE_BIT(26) +#define NS_STATE_DEBUG_WAS_SET NS_FRAME_STATE_BIT(27) + +class nsBox : public nsIFrame { + +public: + + friend class nsIFrame; + + static void Shutdown(); + + virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nscoord GetFlex(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nscoord GetBoxAscent(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + virtual nsSize GetMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + virtual bool IsCollapsed() MOZ_OVERRIDE; + + virtual void SetBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, + bool aRemoveOverflowAreas = false) MOZ_OVERRIDE; + + NS_IMETHOD GetBorder(nsMargin& aBorderAndPadding) MOZ_OVERRIDE; + NS_IMETHOD GetPadding(nsMargin& aBorderAndPadding) MOZ_OVERRIDE; + NS_IMETHOD GetMargin(nsMargin& aMargin) MOZ_OVERRIDE; + + virtual Valignment GetVAlign() const MOZ_OVERRIDE { return vAlign_Top; } + virtual Halignment GetHAlign() const MOZ_OVERRIDE { return hAlign_Left; } + + NS_IMETHOD RelayoutChildAtOrdinal(nsBoxLayoutState& aState, nsIFrame* aChild) MOZ_OVERRIDE; + +#ifdef DEBUG_LAYOUT + NS_IMETHOD GetDebugBoxAt(const nsPoint& aPoint, nsIFrame** aBox); + NS_IMETHOD GetDebug(bool& aDebug) MOZ_OVERRIDE; + NS_IMETHOD SetDebug(nsBoxLayoutState& aState, bool aDebug) MOZ_OVERRIDE; + + NS_IMETHOD DumpBox(FILE* out) MOZ_OVERRIDE; + NS_HIDDEN_(void) PropagateDebug(nsBoxLayoutState& aState); +#endif + + nsBox(); + virtual ~nsBox(); + + /** + * Returns true if this box clips its children, e.g., if this box is an sc +rollbox. + */ + virtual bool DoesClipChildren(); + virtual bool ComputesOwnOverflowArea() = 0; + + NS_HIDDEN_(nsresult) SyncLayout(nsBoxLayoutState& aBoxLayoutState); + + bool DoesNeedRecalc(const nsSize& aSize); + bool DoesNeedRecalc(nscoord aCoord); + void SizeNeedsRecalc(nsSize& aSize); + void CoordNeedsRecalc(nscoord& aCoord); + + void AddBorderAndPadding(nsSize& aSize); + + static void AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize); + static void AddMargin(nsIFrame* aChild, nsSize& aSize); + static void AddMargin(nsSize& aSize, const nsMargin& aMargin); + + static nsSize BoundsCheckMinMax(const nsSize& aMinSize, const nsSize& aMaxSize); + static nsSize BoundsCheck(const nsSize& aMinSize, const nsSize& aPrefSize, const nsSize& aMaxSize); + static nscoord BoundsCheck(nscoord aMinSize, nscoord aPrefSize, nscoord aMaxSize); + +protected: + +#ifdef DEBUG_LAYOUT + virtual void AppendAttribute(const nsAutoString& aAttribute, const nsAutoString& aValue, nsAutoString& aResult); + + virtual void ListBox(nsAutoString& aResult); +#endif + + virtual void GetLayoutFlags(uint32_t& aFlags); + + NS_HIDDEN_(nsresult) BeginLayout(nsBoxLayoutState& aState); + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState); + NS_HIDDEN_(nsresult) EndLayout(nsBoxLayoutState& aState); + +#ifdef DEBUG_LAYOUT + virtual void GetBoxName(nsAutoString& aName); + NS_HIDDEN_(void) PropagateDebug(nsBoxLayoutState& aState); +#endif + + static bool gGotTheme; + static nsITheme* gTheme; + + enum eMouseThrough { + unset, + never, + always + }; + +private: + + //nscoord mX; + //nscoord mY; +}; + +#ifdef DEBUG_LAYOUT +#define NS_BOX_ASSERTION(box,expr,str) \ + if (!(expr)) { \ + box->DumpBox(stdout); \ + NS_DebugBreak(NSDebugAssertion, str, #expr, __FILE__, __LINE__); \ + } +#else +#define NS_BOX_ASSERTION(box,expr,str) {} +#endif + +#endif + diff --git a/layout/xul/base/src/nsBoxFrame.cpp b/layout/xul/base/src/nsBoxFrame.cpp new file mode 100644 index 000000000..fbd75e6f0 --- /dev/null +++ b/layout/xul/base/src/nsBoxFrame.cpp @@ -0,0 +1,2090 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +// How boxes layout +// ---------------- +// Boxes layout a bit differently than html. html does a bottom up layout. Where boxes do a top down. +// 1) First thing a box does it goes out and askes each child for its min, max, and preferred sizes. +// 2) It then adds them up to determine its size. +// 3) If the box was asked to layout it self intrinically it will layout its children at their preferred size +// otherwise it will layout the child at the size it was told to. It will squeeze or stretch its children if +// Necessary. +// +// However there is a catch. Some html components like block frames can not determine their preferred size. +// this is their size if they were laid out intrinsically. So the box will flow the child to determine this can +// cache the value. + +// Boxes and Incremental Reflow +// ---------------------------- +// Boxes layout out top down by adding up their children's min, max, and preferred sizes. Only problem is if a incremental +// reflow occurs. The preferred size of a child deep in the hierarchy could change. And this could change +// any number of syblings around the box. Basically any children in the reflow chain must have their caches cleared +// so when asked for there current size they can relayout themselves. + +#include "nsBoxLayoutState.h" +#include "nsBoxFrame.h" +#include "mozilla/dom/Touch.h" +#include "nsStyleContext.h" +#include "nsPlaceholderFrame.h" +#include "nsPresContext.h" +#include "nsCOMPtr.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsHTMLParts.h" +#include "nsViewManager.h" +#include "nsView.h" +#include "nsIPresShell.h" +#include "nsCSSRendering.h" +#include "nsIServiceManager.h" +#include "nsBoxLayout.h" +#include "nsSprocketLayout.h" +#include "nsIScrollableFrame.h" +#include "nsWidgetsCID.h" +#include "nsCSSAnonBoxes.h" +#include "nsContainerFrame.h" +#include "nsIDOMElement.h" +#include "nsITheme.h" +#include "nsTransform2D.h" +#include "nsEventStateManager.h" +#include "nsEventDispatcher.h" +#include "nsIDOMEvent.h" +#include "nsDisplayList.h" +#include "mozilla/Preferences.h" +#include <algorithm> + +// Needed for Print Preview +#include "nsIURI.h" + +using namespace mozilla; +using namespace mozilla::dom; + +//define DEBUG_REDRAW + +#define DEBUG_SPRING_SIZE 8 +#define DEBUG_BORDER_SIZE 2 +#define COIL_SIZE 8 + +//#define TEST_SANITY + +#ifdef DEBUG_rods +//#define DO_NOISY_REFLOW +#endif + +#ifdef DEBUG_LAYOUT +bool nsBoxFrame::gDebug = false; +nsIFrame* nsBoxFrame::mDebugChild = nullptr; +#endif + +nsIFrame* +NS_NewBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot, nsBoxLayout* aLayoutManager) +{ + return new (aPresShell) nsBoxFrame(aPresShell, aContext, aIsRoot, aLayoutManager); +} + +nsIFrame* +NS_NewBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsBoxFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsBoxFrame) + +nsBoxFrame::nsBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + bool aIsRoot, + nsBoxLayout* aLayoutManager) : + nsContainerFrame(aContext) +{ + mState |= NS_STATE_IS_HORIZONTAL; + mState |= NS_STATE_AUTO_STRETCH; + + if (aIsRoot) + mState |= NS_STATE_IS_ROOT; + + mValign = vAlign_Top; + mHalign = hAlign_Left; + + // if no layout manager specified us the static sprocket layout + nsCOMPtr<nsBoxLayout> layout = aLayoutManager; + + if (layout == nullptr) { + NS_NewSprocketLayout(aPresShell, layout); + } + + SetLayoutManager(layout); +} + +nsBoxFrame::~nsBoxFrame() +{ +} + +NS_IMETHODIMP +nsBoxFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + nsresult r = nsContainerFrame::SetInitialChildList(aListID, aChildList); + if (r == NS_OK) { + // initialize our list of infos. + nsBoxLayoutState state(PresContext()); + CheckBoxOrder(); + if (mLayoutManager) + mLayoutManager->ChildrenSet(this, state, mFrames.FirstChild()); + } else { + NS_WARNING("Warning add child failed!!\n"); + } + + return r; +} + +/* virtual */ void +nsBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsContainerFrame::DidSetStyleContext(aOldStyleContext); + + // The values that CacheAttributes() computes depend on our style, + // so we need to recompute them here... + CacheAttributes(); +} + +/** + * Initialize us. This is a good time to get the alignment of the box + */ +void +nsBoxFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + + if (GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER) { + AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); + } + + MarkIntrinsicWidthsDirty(); + + CacheAttributes(); + +#ifdef DEBUG_LAYOUT + // if we are root and this + if (mState & NS_STATE_IS_ROOT) + GetDebugPref(GetPresContext()); +#endif + + UpdateMouseThrough(); + + // register access key + RegUnregAccessKey(true); +} + +void nsBoxFrame::UpdateMouseThrough() +{ + if (mContent) { + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::never, &nsGkAtoms::always, nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::mousethrough, strings, eCaseMatters)) { + case 0: AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); break; + case 1: AddStateBits(NS_FRAME_MOUSE_THROUGH_ALWAYS); break; + case 2: { + RemoveStateBits(NS_FRAME_MOUSE_THROUGH_ALWAYS); + RemoveStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); + break; + } + } + } +} + +void +nsBoxFrame::CacheAttributes() +{ + /* + printf("Caching: "); + DumpBox(stdout); + printf("\n"); + */ + + mValign = vAlign_Top; + mHalign = hAlign_Left; + + bool orient = false; + GetInitialOrientation(orient); + if (orient) + mState |= NS_STATE_IS_HORIZONTAL; + else + mState &= ~NS_STATE_IS_HORIZONTAL; + + bool normal = true; + GetInitialDirection(normal); + if (normal) + mState |= NS_STATE_IS_DIRECTION_NORMAL; + else + mState &= ~NS_STATE_IS_DIRECTION_NORMAL; + + GetInitialVAlignment(mValign); + GetInitialHAlignment(mHalign); + + bool equalSize = false; + GetInitialEqualSize(equalSize); + if (equalSize) + mState |= NS_STATE_EQUAL_SIZE; + else + mState &= ~NS_STATE_EQUAL_SIZE; + + bool autostretch = !!(mState & NS_STATE_AUTO_STRETCH); + GetInitialAutoStretch(autostretch); + if (autostretch) + mState |= NS_STATE_AUTO_STRETCH; + else + mState &= ~NS_STATE_AUTO_STRETCH; + + +#ifdef DEBUG_LAYOUT + bool debug = mState & NS_STATE_SET_TO_DEBUG; + bool debugSet = GetInitialDebug(debug); + if (debugSet) { + mState |= NS_STATE_DEBUG_WAS_SET; + if (debug) + mState |= NS_STATE_SET_TO_DEBUG; + else + mState &= ~NS_STATE_SET_TO_DEBUG; + } else { + mState &= ~NS_STATE_DEBUG_WAS_SET; + } +#endif +} + +#ifdef DEBUG_LAYOUT +bool +nsBoxFrame::GetInitialDebug(bool& aDebug) +{ + if (!GetContent()) + return false; + + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::_false, &nsGkAtoms::_true, nullptr}; + int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::debug, strings, eCaseMatters); + if (index >= 0) { + aDebug = index == 1; + return true; + } + + return false; +} +#endif + +bool +nsBoxFrame::GetInitialHAlignment(nsBoxFrame::Halignment& aHalign) +{ + if (!GetContent()) + return false; + + // XXXdwh Everything inside this if statement is deprecated code. + static nsIContent::AttrValuesArray alignStrings[] = + {&nsGkAtoms::left, &nsGkAtoms::right, nullptr}; + static const Halignment alignValues[] = {hAlign_Left, hAlign_Right}; + int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::align, + alignStrings, eCaseMatters); + if (index >= 0) { + aHalign = alignValues[index]; + return true; + } + + // Now that the deprecated stuff is out of the way, we move on to check the appropriate + // attribute. For horizontal boxes, we are checking the PACK attribute. For vertical boxes + // we are checking the ALIGN attribute. + nsIAtom* attrName = IsHorizontal() ? nsGkAtoms::pack : nsGkAtoms::align; + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::_empty, &nsGkAtoms::start, &nsGkAtoms::center, &nsGkAtoms::end, nullptr}; + static const Halignment values[] = + {hAlign_Left/*not used*/, hAlign_Left, hAlign_Center, hAlign_Right}; + index = GetContent()->FindAttrValueIn(kNameSpaceID_None, attrName, + strings, eCaseMatters); + + if (index == nsIContent::ATTR_VALUE_NO_MATCH) { + // The attr was present but had a nonsensical value. Revert to the default. + return false; + } + if (index > 0) { + aHalign = values[index]; + return true; + } + + // Now that we've checked for the attribute it's time to check CSS. For + // horizontal boxes we're checking PACK. For vertical boxes we are checking + // ALIGN. + const nsStyleXUL* boxInfo = StyleXUL(); + if (IsHorizontal()) { + switch (boxInfo->mBoxPack) { + case NS_STYLE_BOX_PACK_START: + aHalign = nsBoxFrame::hAlign_Left; + return true; + case NS_STYLE_BOX_PACK_CENTER: + aHalign = nsBoxFrame::hAlign_Center; + return true; + case NS_STYLE_BOX_PACK_END: + aHalign = nsBoxFrame::hAlign_Right; + return true; + default: // Nonsensical value. Just bail. + return false; + } + } + else { + switch (boxInfo->mBoxAlign) { + case NS_STYLE_BOX_ALIGN_START: + aHalign = nsBoxFrame::hAlign_Left; + return true; + case NS_STYLE_BOX_ALIGN_CENTER: + aHalign = nsBoxFrame::hAlign_Center; + return true; + case NS_STYLE_BOX_ALIGN_END: + aHalign = nsBoxFrame::hAlign_Right; + return true; + default: // Nonsensical value. Just bail. + return false; + } + } + + return false; +} + +bool +nsBoxFrame::GetInitialVAlignment(nsBoxFrame::Valignment& aValign) +{ + if (!GetContent()) + return false; + + static nsIContent::AttrValuesArray valignStrings[] = + {&nsGkAtoms::top, &nsGkAtoms::baseline, &nsGkAtoms::middle, &nsGkAtoms::bottom, nullptr}; + static const Valignment valignValues[] = + {vAlign_Top, vAlign_BaseLine, vAlign_Middle, vAlign_Bottom}; + int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::valign, + valignStrings, eCaseMatters); + if (index >= 0) { + aValign = valignValues[index]; + return true; + } + + // Now that the deprecated stuff is out of the way, we move on to check the appropriate + // attribute. For horizontal boxes, we are checking the ALIGN attribute. For vertical boxes + // we are checking the PACK attribute. + nsIAtom* attrName = IsHorizontal() ? nsGkAtoms::align : nsGkAtoms::pack; + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::_empty, &nsGkAtoms::start, &nsGkAtoms::center, + &nsGkAtoms::baseline, &nsGkAtoms::end, nullptr}; + static const Valignment values[] = + {vAlign_Top/*not used*/, vAlign_Top, vAlign_Middle, vAlign_BaseLine, vAlign_Bottom}; + index = GetContent()->FindAttrValueIn(kNameSpaceID_None, attrName, + strings, eCaseMatters); + if (index == nsIContent::ATTR_VALUE_NO_MATCH) { + // The attr was present but had a nonsensical value. Revert to the default. + return false; + } + if (index > 0) { + aValign = values[index]; + return true; + } + + // Now that we've checked for the attribute it's time to check CSS. For + // horizontal boxes we're checking ALIGN. For vertical boxes we are checking + // PACK. + const nsStyleXUL* boxInfo = StyleXUL(); + if (IsHorizontal()) { + switch (boxInfo->mBoxAlign) { + case NS_STYLE_BOX_ALIGN_START: + aValign = nsBoxFrame::vAlign_Top; + return true; + case NS_STYLE_BOX_ALIGN_CENTER: + aValign = nsBoxFrame::vAlign_Middle; + return true; + case NS_STYLE_BOX_ALIGN_BASELINE: + aValign = nsBoxFrame::vAlign_BaseLine; + return true; + case NS_STYLE_BOX_ALIGN_END: + aValign = nsBoxFrame::vAlign_Bottom; + return true; + default: // Nonsensical value. Just bail. + return false; + } + } + else { + switch (boxInfo->mBoxPack) { + case NS_STYLE_BOX_PACK_START: + aValign = nsBoxFrame::vAlign_Top; + return true; + case NS_STYLE_BOX_PACK_CENTER: + aValign = nsBoxFrame::vAlign_Middle; + return true; + case NS_STYLE_BOX_PACK_END: + aValign = nsBoxFrame::vAlign_Bottom; + return true; + default: // Nonsensical value. Just bail. + return false; + } + } + + return false; +} + +void +nsBoxFrame::GetInitialOrientation(bool& aIsHorizontal) +{ + // see if we are a vertical or horizontal box. + if (!GetContent()) + return; + + // Check the style system first. + const nsStyleXUL* boxInfo = StyleXUL(); + if (boxInfo->mBoxOrient == NS_STYLE_BOX_ORIENT_HORIZONTAL) + aIsHorizontal = true; + else + aIsHorizontal = false; + + // Now see if we have an attribute. The attribute overrides + // the style system value. + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::vertical, &nsGkAtoms::horizontal, nullptr}; + int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::orient, + strings, eCaseMatters); + if (index >= 0) { + aIsHorizontal = index == 1; + } +} + +void +nsBoxFrame::GetInitialDirection(bool& aIsNormal) +{ + if (!GetContent()) + return; + + if (IsHorizontal()) { + // For horizontal boxes only, we initialize our value based off the CSS 'direction' property. + // This means that BiDI users will end up with horizontally inverted chrome. + aIsNormal = (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR); // If text runs RTL then so do we. + } + else + aIsNormal = true; // Assume a normal direction in the vertical case. + + // Now check the style system to see if we should invert aIsNormal. + const nsStyleXUL* boxInfo = StyleXUL(); + if (boxInfo->mBoxDirection == NS_STYLE_BOX_DIRECTION_REVERSE) + aIsNormal = !aIsNormal; // Invert our direction. + + // Now see if we have an attribute. The attribute overrides + // the style system value. + if (IsHorizontal()) { + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::reverse, &nsGkAtoms::ltr, &nsGkAtoms::rtl, nullptr}; + int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::dir, + strings, eCaseMatters); + if (index >= 0) { + bool values[] = {!aIsNormal, true, false}; + aIsNormal = values[index]; + } + } else if (GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, + nsGkAtoms::reverse, eCaseMatters)) { + aIsNormal = !aIsNormal; + } +} + +/* Returns true if it was set. + */ +bool +nsBoxFrame::GetInitialEqualSize(bool& aEqualSize) +{ + // see if we are a vertical or horizontal box. + if (!GetContent()) + return false; + + if (GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::equalsize, + nsGkAtoms::always, eCaseMatters)) { + aEqualSize = true; + return true; + } + + return false; +} + +/* Returns true if it was set. + */ +bool +nsBoxFrame::GetInitialAutoStretch(bool& aStretch) +{ + if (!GetContent()) + return false; + + // Check the align attribute. + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::_empty, &nsGkAtoms::stretch, nullptr}; + int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::align, + strings, eCaseMatters); + if (index != nsIContent::ATTR_MISSING && index != 0) { + aStretch = index == 1; + return true; + } + + // Check the CSS box-align property. + const nsStyleXUL* boxInfo = StyleXUL(); + aStretch = (boxInfo->mBoxAlign == NS_STYLE_BOX_ALIGN_STRETCH); + + return true; +} + +NS_IMETHODIMP +nsBoxFrame::DidReflow(nsPresContext* aPresContext, + const nsHTMLReflowState* aReflowState, + nsDidReflowStatus aStatus) +{ + nsFrameState preserveBits = + mState & (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); + nsresult rv = nsFrame::DidReflow(aPresContext, aReflowState, aStatus); + mState |= preserveBits; + return rv; +} + +bool +nsBoxFrame::HonorPrintBackgroundSettings() +{ + return (!mContent || !mContent->IsInNativeAnonymousSubtree()) && + nsContainerFrame::HonorPrintBackgroundSettings(); +} + +#ifdef DO_NOISY_REFLOW +static int myCounter = 0; +static void printSize(char * aDesc, nscoord aSize) +{ + printf(" %s: ", aDesc); + if (aSize == NS_UNCONSTRAINEDSIZE) { + printf("UC"); + } else { + printf("%d", aSize); + } +} +#endif + +/* virtual */ nscoord +nsBoxFrame::GetMinWidth(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + + nsBoxLayoutState state(PresContext(), aRenderingContext); + nsSize minSize = GetMinSize(state); + + // GetMinSize returns border-box width, and we want to return content + // width. Since Reflow uses the reflow state's border and padding, we + // actually just want to subtract what GetMinSize added, which is the + // result of GetBorderAndPadding. + nsMargin bp; + GetBorderAndPadding(bp); + + result = minSize.width - bp.LeftRight(); + result = std::max(result, 0); + + return result; +} + +/* virtual */ nscoord +nsBoxFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + + nsBoxLayoutState state(PresContext(), aRenderingContext); + nsSize prefSize = GetPrefSize(state); + + // GetPrefSize returns border-box width, and we want to return content + // width. Since Reflow uses the reflow state's border and padding, we + // actually just want to subtract what GetPrefSize added, which is the + // result of GetBorderAndPadding. + nsMargin bp; + GetBorderAndPadding(bp); + + result = prefSize.width - bp.LeftRight(); + result = std::max(result, 0); + + return result; +} + +NS_IMETHODIMP +nsBoxFrame::Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + // If you make changes to this method, please keep nsLeafBoxFrame::Reflow + // in sync, if the changes are applicable there. + + DO_GLOBAL_REFLOW_COUNT("nsBoxFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); + + NS_ASSERTION(aReflowState.ComputedWidth() >=0 && + aReflowState.ComputedHeight() >= 0, "Computed Size < 0"); + +#ifdef DO_NOISY_REFLOW + printf("\n-------------Starting BoxFrame Reflow ----------------------------\n"); + printf("%p ** nsBF::Reflow %d ", this, myCounter++); + + printSize("AW", aReflowState.availableWidth); + printSize("AH", aReflowState.availableHeight); + printSize("CW", aReflowState.ComputedWidth()); + printSize("CH", aReflowState.ComputedHeight()); + + printf(" *\n"); + +#endif + + aStatus = NS_FRAME_COMPLETE; + + // create the layout state + nsBoxLayoutState state(aPresContext, aReflowState.rendContext, + &aReflowState, aReflowState.mReflowDepth); + + nsSize computedSize(aReflowState.ComputedWidth(),aReflowState.ComputedHeight()); + + nsMargin m; + m = aReflowState.mComputedBorderPadding; + // GetBorderAndPadding(m); + + nsSize prefSize(0,0); + + // if we are told to layout intrinsic then get our preferred size. + NS_ASSERTION(computedSize.width != NS_INTRINSICSIZE, + "computed width should always be computed"); + if (computedSize.height == NS_INTRINSICSIZE) { + prefSize = GetPrefSize(state); + nsSize minSize = GetMinSize(state); + nsSize maxSize = GetMaxSize(state); + // XXXbz isn't GetPrefSize supposed to bounds-check for us? + prefSize = BoundsCheck(minSize, prefSize, maxSize); + } + + // get our desiredSize + computedSize.width += m.left + m.right; + + if (aReflowState.ComputedHeight() == NS_INTRINSICSIZE) { + computedSize.height = prefSize.height; + // prefSize is border-box but min/max constraints are content-box. + nscoord verticalBorderPadding = + aReflowState.mComputedBorderPadding.TopBottom(); + nscoord contentHeight = computedSize.height - verticalBorderPadding; + // Note: contentHeight might be negative, but that's OK because min-height + // is never negative. + computedSize.height = aReflowState.ApplyMinMaxHeight(contentHeight) + + verticalBorderPadding; + } else { + computedSize.height += m.top + m.bottom; + } + + nsRect r(mRect.x, mRect.y, computedSize.width, computedSize.height); + + SetBounds(state, r); + + // layout our children + Layout(state); + + // ok our child could have gotten bigger. So lets get its bounds + + // get the ascent + nscoord ascent = mRect.height; + + // getting the ascent could be a lot of work. Don't get it if + // we are the root. The viewport doesn't care about it. + if (!(mState & NS_STATE_IS_ROOT)) { + ascent = GetBoxAscent(state); + } + + aDesiredSize.width = mRect.width; + aDesiredSize.height = mRect.height; + aDesiredSize.ascent = ascent; + + aDesiredSize.mOverflowAreas = GetOverflowAreas(); + +#ifdef DO_NOISY_REFLOW + { + printf("%p ** nsBF(done) W:%d H:%d ", this, aDesiredSize.width, aDesiredSize.height); + + if (maxElementSize) { + printf("MW:%d\n", *maxElementWidth); + } else { + printf("MW:?\n"); + } + + } +#endif + + ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); + return NS_OK; +} + +nsSize +nsBoxFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState) +{ + NS_ASSERTION(aBoxLayoutState.GetRenderingContext(), + "must have rendering context"); + + nsSize size(0,0); + DISPLAY_PREF_SIZE(this, size); + if (!DoesNeedRecalc(mPrefSize)) { + return mPrefSize; + } + +#ifdef DEBUG_LAYOUT + PropagateDebug(aBoxLayoutState); +#endif + + if (IsCollapsed()) + return size; + + // if the size was not completely redefined in CSS then ask our children + bool widthSet, heightSet; + if (!nsIFrame::AddCSSPrefSize(this, size, widthSet, heightSet)) + { + if (mLayoutManager) { + nsSize layoutSize = mLayoutManager->GetPrefSize(this, aBoxLayoutState); + if (!widthSet) + size.width = layoutSize.width; + if (!heightSet) + size.height = layoutSize.height; + } + else { + size = nsBox::GetPrefSize(aBoxLayoutState); + } + } + + nsSize minSize = GetMinSize(aBoxLayoutState); + nsSize maxSize = GetMaxSize(aBoxLayoutState); + mPrefSize = BoundsCheck(minSize, size, maxSize); + + return mPrefSize; +} + +nscoord +nsBoxFrame::GetBoxAscent(nsBoxLayoutState& aBoxLayoutState) +{ + if (!DoesNeedRecalc(mAscent)) + return mAscent; + +#ifdef DEBUG_LAYOUT + PropagateDebug(aBoxLayoutState); +#endif + + if (IsCollapsed()) + return 0; + + if (mLayoutManager) + mAscent = mLayoutManager->GetAscent(this, aBoxLayoutState); + else + mAscent = nsBox::GetBoxAscent(aBoxLayoutState); + + return mAscent; +} + +nsSize +nsBoxFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState) +{ + NS_ASSERTION(aBoxLayoutState.GetRenderingContext(), + "must have rendering context"); + + nsSize size(0,0); + DISPLAY_MIN_SIZE(this, size); + if (!DoesNeedRecalc(mMinSize)) { + return mMinSize; + } + +#ifdef DEBUG_LAYOUT + PropagateDebug(aBoxLayoutState); +#endif + + if (IsCollapsed()) + return size; + + // if the size was not completely redefined in CSS then ask our children + bool widthSet, heightSet; + if (!nsIFrame::AddCSSMinSize(aBoxLayoutState, this, size, widthSet, heightSet)) + { + if (mLayoutManager) { + nsSize layoutSize = mLayoutManager->GetMinSize(this, aBoxLayoutState); + if (!widthSet) + size.width = layoutSize.width; + if (!heightSet) + size.height = layoutSize.height; + } + else { + size = nsBox::GetMinSize(aBoxLayoutState); + } + } + + mMinSize = size; + + return size; +} + +nsSize +nsBoxFrame::GetMaxSize(nsBoxLayoutState& aBoxLayoutState) +{ + NS_ASSERTION(aBoxLayoutState.GetRenderingContext(), + "must have rendering context"); + + nsSize size(NS_INTRINSICSIZE, NS_INTRINSICSIZE); + DISPLAY_MAX_SIZE(this, size); + if (!DoesNeedRecalc(mMaxSize)) { + return mMaxSize; + } + +#ifdef DEBUG_LAYOUT + PropagateDebug(aBoxLayoutState); +#endif + + if (IsCollapsed()) + return size; + + // if the size was not completely redefined in CSS then ask our children + bool widthSet, heightSet; + if (!nsIFrame::AddCSSMaxSize(this, size, widthSet, heightSet)) + { + if (mLayoutManager) { + nsSize layoutSize = mLayoutManager->GetMaxSize(this, aBoxLayoutState); + if (!widthSet) + size.width = layoutSize.width; + if (!heightSet) + size.height = layoutSize.height; + } + else { + size = nsBox::GetMaxSize(aBoxLayoutState); + } + } + + mMaxSize = size; + + return size; +} + +nscoord +nsBoxFrame::GetFlex(nsBoxLayoutState& aBoxLayoutState) +{ + if (!DoesNeedRecalc(mFlex)) + return mFlex; + + mFlex = nsBox::GetFlex(aBoxLayoutState); + + return mFlex; +} + +/** + * If subclassing please subclass this method not layout. + * layout will call this method. + */ +NS_IMETHODIMP +nsBoxFrame::DoLayout(nsBoxLayoutState& aState) +{ + uint32_t oldFlags = aState.LayoutFlags(); + aState.SetLayoutFlags(0); + + nsresult rv = NS_OK; + if (mLayoutManager) { + CoordNeedsRecalc(mAscent); + rv = mLayoutManager->Layout(this, aState); + } + + aState.SetLayoutFlags(oldFlags); + + if (HasAbsolutelyPositionedChildren()) { + // Set up a |reflowState| to pass into ReflowAbsoluteFrames + nsHTMLReflowState reflowState(aState.PresContext(), this, + aState.GetRenderingContext(), + nsSize(mRect.width, NS_UNCONSTRAINEDSIZE)); + + // Set up a |desiredSize| to pass into ReflowAbsoluteFrames + nsHTMLReflowMetrics desiredSize; + desiredSize.width = mRect.width; + desiredSize.height = mRect.height; + + // get the ascent (cribbed from ::Reflow) + nscoord ascent = mRect.height; + + // getting the ascent could be a lot of work. Don't get it if + // we are the root. The viewport doesn't care about it. + if (!(mState & NS_STATE_IS_ROOT)) { + ascent = GetBoxAscent(aState); + } + desiredSize.ascent = ascent; + desiredSize.mOverflowAreas = GetOverflowAreas(); + + // Set up a |reflowStatus| to pass into ReflowAbsoluteFrames + // (just a dummy value; hopefully that's OK) + nsReflowStatus reflowStatus = NS_FRAME_COMPLETE; + ReflowAbsoluteFrames(aState.PresContext(), desiredSize, + reflowState, reflowStatus); + } + + return rv; +} + +void +nsBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // unregister access key + RegUnregAccessKey(false); + + // clean up the container box's layout manager and child boxes + SetLayoutManager(nullptr); + + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +#ifdef DEBUG_LAYOUT +NS_IMETHODIMP +nsBoxFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug) +{ + // see if our state matches the given debug state + bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG; + bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet); + + // if it doesn't then tell each child below us the new debug state + if (debugChanged) + { + if (aDebug) { + mState |= NS_STATE_CURRENTLY_IN_DEBUG; + } else { + mState &= ~NS_STATE_CURRENTLY_IN_DEBUG; + } + + SetDebugOnChildList(aState, mFirstChild, aDebug); + + MarkIntrinsicWidthsDirty(); + } + + return NS_OK; +} +#endif + +/* virtual */ void +nsBoxFrame::MarkIntrinsicWidthsDirty() +{ + SizeNeedsRecalc(mPrefSize); + SizeNeedsRecalc(mMinSize); + SizeNeedsRecalc(mMaxSize); + CoordNeedsRecalc(mFlex); + CoordNeedsRecalc(mAscent); + + if (mLayoutManager) { + nsBoxLayoutState state(PresContext()); + mLayoutManager->IntrinsicWidthsDirty(this, state); + } + + // Don't call base class method, since everything it does is within an + // IsBoxWrapped check. +} + +NS_IMETHODIMP +nsBoxFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_PRECONDITION(aListID == kPrincipalList, "We don't support out-of-flow kids"); + nsPresContext* presContext = PresContext(); + nsBoxLayoutState state(presContext); + + // remove the child frame + mFrames.RemoveFrame(aOldFrame); + + // notify the layout manager + if (mLayoutManager) + mLayoutManager->ChildrenRemoved(this, state, aOldFrame); + + // destroy the child frame + aOldFrame->Destroy(); + + // mark us dirty and generate a reflow command + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + return NS_OK; +} + +NS_IMETHODIMP +nsBoxFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + NS_ASSERTION(!aPrevFrame || mFrames.ContainsFrame(aPrevFrame), + "inserting after sibling frame not in our child list"); + NS_PRECONDITION(aListID == kPrincipalList, "We don't support out-of-flow kids"); + nsBoxLayoutState state(PresContext()); + + // insert the child frames + const nsFrameList::Slice& newFrames = + mFrames.InsertFrames(this, aPrevFrame, aFrameList); + + // notify the layout manager + if (mLayoutManager) + mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames); + + // Make sure to check box order _after_ notifying the layout + // manager; otherwise the slice we give the layout manager will + // just be bogus. If the layout manager cares about the order, we + // just lose. + CheckBoxOrder(); + +#ifdef DEBUG_LAYOUT + // if we are in debug make sure our children are in debug as well. + if (mState & NS_STATE_CURRENTLY_IN_DEBUG) + SetDebugOnChildList(state, mFrames.FirstChild(), true); +#endif + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + return NS_OK; +} + + +NS_IMETHODIMP +nsBoxFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + NS_PRECONDITION(aListID == kPrincipalList, "We don't support out-of-flow kids"); + nsBoxLayoutState state(PresContext()); + + // append the new frames + const nsFrameList::Slice& newFrames = mFrames.AppendFrames(this, aFrameList); + + // notify the layout manager + if (mLayoutManager) + mLayoutManager->ChildrenAppended(this, state, newFrames); + + // Make sure to check box order _after_ notifying the layout + // manager; otherwise the slice we give the layout manager will + // just be bogus. If the layout manager cares about the order, we + // just lose. + CheckBoxOrder(); + +#ifdef DEBUG_LAYOUT + // if we are in debug make sure our children are in debug as well. + if (mState & NS_STATE_CURRENTLY_IN_DEBUG) + SetDebugOnChildList(state, mFrames.FirstChild(), true); +#endif + + // XXXbz why is this NS_FRAME_FIRST_REFLOW check here? + if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + return NS_OK; +} + +/* virtual */ nsIFrame* +nsBoxFrame::GetContentInsertionFrame() +{ + if (GetStateBits() & NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK) + return GetFirstPrincipalChild()->GetContentInsertionFrame(); + return nsContainerFrame::GetContentInsertionFrame(); +} + +NS_IMETHODIMP +nsBoxFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + + // Ignore 'width', 'height', 'screenX', 'screenY' and 'sizemode' on a + // <window>. + nsIAtom *tag = mContent->Tag(); + if ((tag == nsGkAtoms::window || + tag == nsGkAtoms::page || + tag == nsGkAtoms::dialog || + tag == nsGkAtoms::wizard) && + (nsGkAtoms::width == aAttribute || + nsGkAtoms::height == aAttribute || + nsGkAtoms::screenX == aAttribute || + nsGkAtoms::screenY == aAttribute || + nsGkAtoms::sizemode == aAttribute)) { + return rv; + } + + if (aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height || + aAttribute == nsGkAtoms::align || + aAttribute == nsGkAtoms::valign || + aAttribute == nsGkAtoms::left || + aAttribute == nsGkAtoms::top || + aAttribute == nsGkAtoms::right || + aAttribute == nsGkAtoms::bottom || + aAttribute == nsGkAtoms::start || + aAttribute == nsGkAtoms::end || + aAttribute == nsGkAtoms::minwidth || + aAttribute == nsGkAtoms::maxwidth || + aAttribute == nsGkAtoms::minheight || + aAttribute == nsGkAtoms::maxheight || + aAttribute == nsGkAtoms::flex || + aAttribute == nsGkAtoms::orient || + aAttribute == nsGkAtoms::pack || + aAttribute == nsGkAtoms::dir || + aAttribute == nsGkAtoms::mousethrough || + aAttribute == nsGkAtoms::equalsize) { + + if (aAttribute == nsGkAtoms::align || + aAttribute == nsGkAtoms::valign || + aAttribute == nsGkAtoms::orient || + aAttribute == nsGkAtoms::pack || +#ifdef DEBUG_LAYOUT + aAttribute == nsGkAtoms::debug || +#endif + aAttribute == nsGkAtoms::dir) { + + mValign = nsBoxFrame::vAlign_Top; + mHalign = nsBoxFrame::hAlign_Left; + + bool orient = true; + GetInitialOrientation(orient); + if (orient) + mState |= NS_STATE_IS_HORIZONTAL; + else + mState &= ~NS_STATE_IS_HORIZONTAL; + + bool normal = true; + GetInitialDirection(normal); + if (normal) + mState |= NS_STATE_IS_DIRECTION_NORMAL; + else + mState &= ~NS_STATE_IS_DIRECTION_NORMAL; + + GetInitialVAlignment(mValign); + GetInitialHAlignment(mHalign); + + bool equalSize = false; + GetInitialEqualSize(equalSize); + if (equalSize) + mState |= NS_STATE_EQUAL_SIZE; + else + mState &= ~NS_STATE_EQUAL_SIZE; + +#ifdef DEBUG_LAYOUT + bool debug = mState & NS_STATE_SET_TO_DEBUG; + bool debugSet = GetInitialDebug(debug); + if (debugSet) { + mState |= NS_STATE_DEBUG_WAS_SET; + + if (debug) + mState |= NS_STATE_SET_TO_DEBUG; + else + mState &= ~NS_STATE_SET_TO_DEBUG; + } else { + mState &= ~NS_STATE_DEBUG_WAS_SET; + } +#endif + + bool autostretch = !!(mState & NS_STATE_AUTO_STRETCH); + GetInitialAutoStretch(autostretch); + if (autostretch) + mState |= NS_STATE_AUTO_STRETCH; + else + mState &= ~NS_STATE_AUTO_STRETCH; + } + else if (aAttribute == nsGkAtoms::left || + aAttribute == nsGkAtoms::top || + aAttribute == nsGkAtoms::right || + aAttribute == nsGkAtoms::bottom || + aAttribute == nsGkAtoms::start || + aAttribute == nsGkAtoms::end) { + mState &= ~NS_STATE_STACK_NOT_POSITIONED; + } + else if (aAttribute == nsGkAtoms::mousethrough) { + UpdateMouseThrough(); + } + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + else if (aAttribute == nsGkAtoms::ordinal) { + nsBoxLayoutState state(PresContext()); + nsIFrame* parent = GetParentBox(); + // If our parent is not a box, there's not much we can do... but in that + // case our ordinal doesn't matter anyway, so that's ok. + // Also don't bother with popup frames since they are kept on the + // kPopupList and RelayoutChildAtOrdinal() only handles + // principal children. + if (parent && !(GetStateBits() & NS_FRAME_OUT_OF_FLOW) && + StyleDisplay()->mDisplay != NS_STYLE_DISPLAY_POPUP) { + parent->RelayoutChildAtOrdinal(state, this); + // XXXldb Should this instead be a tree change on the child or parent? + PresContext()->PresShell()-> + FrameNeedsReflow(parent, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + } + } + // If the accesskey changed, register for the new value + // The old value has been unregistered in nsXULElement::SetAttr + else if (aAttribute == nsGkAtoms::accesskey) { + RegUnregAccessKey(true); + } + + return rv; +} + +#ifdef DEBUG_LAYOUT +void +nsBoxFrame::GetDebugPref(nsPresContext* aPresContext) +{ + gDebug = Preferences::GetBool("xul.debug.box"); +} + +class nsDisplayXULDebug : public nsDisplayItem { +public: + nsDisplayXULDebug(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) : + nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayXULDebug); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayXULDebug() { + MOZ_COUNT_DTOR(nsDisplayXULDebug); + } +#endif + + virtual void HitTest(nsDisplayListBuilder* aBuilder, nsRect aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) { + nsPoint rectCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2); + static_cast<nsBoxFrame*>(mFrame)-> + DisplayDebugInfoFor(this, rectCenter - ToReferenceFrame()); + aOutFrames->AppendElement(this); + } + virtual void Paint(nsDisplayListBuilder* aBuilder + nsRenderingContext* aCtx); + NS_DISPLAY_DECL_NAME("XULDebug", TYPE_XUL_DEBUG) +}; + +void +nsDisplayXULDebug::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + static_cast<nsBoxFrame*>(mFrame)-> + PaintXULDebugOverlay(*aCtx, ToReferenceFrame()); +} + +static void +PaintXULDebugBackground(nsIFrame* aFrame, nsRenderingContext* aCtx, + const nsRect& aDirtyRect, nsPoint aPt) +{ + static_cast<nsBoxFrame*>(aFrame)->PaintXULDebugBackground(*aCtx, aPt); +} +#endif + +void +nsBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // forcelayer is only supported on XUL elements with box layout + bool forceLayer = + GetContent()->HasAttr(kNameSpaceID_None, nsGkAtoms::layer) && + GetContent()->IsXUL(); + + // Check for frames that are marked as a part of the region used + // in calculating glass margins on Windows. + if (GetContent()->IsXUL()) { + const nsStyleDisplay* styles = StyleDisplay(); + if (styles && styles->mAppearance == NS_THEME_WIN_EXCLUDE_GLASS) { + nsRect rect = nsRect(aBuilder->ToReferenceFrame(this), GetSize()); + aBuilder->AddExcludedGlassRegion(rect); + } + } + + nsDisplayListCollection tempLists; + const nsDisplayListSet& destination = forceLayer ? tempLists : aLists; + + DisplayBorderBackgroundOutline(aBuilder, destination); + +#ifdef DEBUG_LAYOUT + if (mState & NS_STATE_CURRENTLY_IN_DEBUG) { + destination.BorderBackground()->AppendNewToTop(new (aBuilder) + nsDisplayGeneric(aBuilder, this, PaintXULDebugBackground, + "XULDebugBackground")); + destination.Outlines()->AppendNewToTop(new (aBuilder) + nsDisplayXULDebug(aBuilder, this)); + } +#endif + + BuildDisplayListForChildren(aBuilder, aDirtyRect, destination); + + // see if we have to draw a selection frame around this container + DisplaySelectionOverlay(aBuilder, destination.Content()); + + if (forceLayer) { + // This is a bit of a hack. Collect up all descendant display items + // and merge them into a single Content() list. This can cause us + // to violate CSS stacking order, but forceLayer is a magic + // XUL-only extension anyway. + nsDisplayList masterList; + masterList.AppendToTop(tempLists.BorderBackground()); + masterList.AppendToTop(tempLists.BlockBorderBackgrounds()); + masterList.AppendToTop(tempLists.Floats()); + masterList.AppendToTop(tempLists.Content()); + masterList.AppendToTop(tempLists.PositionedDescendants()); + masterList.AppendToTop(tempLists.Outlines()); + // Wrap the list to make it its own layer + aLists.Content()->AppendNewToTop(new (aBuilder) + nsDisplayOwnLayer(aBuilder, this, &masterList)); + } +} + +void +nsBoxFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsIFrame* kid = mFrames.FirstChild(); + // Put each child's background onto the BlockBorderBackgrounds list + // to emulate the existing two-layer XUL painting scheme. + nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds()); + // The children should be in the right order + while (kid) { + BuildDisplayListForChild(aBuilder, kid, aDirtyRect, set); + kid = kid->GetNextSibling(); + } +} + +// REVIEW: PaintChildren did a few things none of which are a big deal +// anymore: +// * Paint some debugging rects for this frame. +// This is done by nsDisplayXULDebugBackground, which goes in the +// BorderBackground() layer so it isn't clipped by OVERFLOW_CLIP. +// * Apply OVERFLOW_CLIP to the children. +// This is now in nsFrame::BuildDisplayListForStackingContext/Child. +// * Actually paint the children. +// Moved to BuildDisplayList. +// * Paint per-kid debug information. +// This is done by nsDisplayXULDebug, which is in the Outlines() +// layer so it goes on top. This means it is not clipped by OVERFLOW_CLIP, +// whereas it did used to respect OVERFLOW_CLIP, but too bad. +#ifdef DEBUG_LAYOUT +void +nsBoxFrame::PaintXULDebugBackground(nsRenderingContext& aRenderingContext, + nsPoint aPt) +{ + nsMargin border; + GetBorder(border); + + nsMargin debugBorder; + nsMargin debugMargin; + nsMargin debugPadding; + + bool isHorizontal = IsHorizontal(); + + GetDebugBorder(debugBorder); + PixelMarginToTwips(GetPresContext(), debugBorder); + + GetDebugMargin(debugMargin); + PixelMarginToTwips(GetPresContext(), debugMargin); + + GetDebugPadding(debugPadding); + PixelMarginToTwips(GetPresContext(), debugPadding); + + nsRect inner(mRect); + inner.MoveTo(aPt); + inner.Deflate(debugMargin); + inner.Deflate(border); + //nsRect borderRect(inner); + + nscolor color; + if (isHorizontal) { + color = NS_RGB(0,0,255); + } else { + color = NS_RGB(255,0,0); + } + + aRenderingContext.SetColor(color); + + //left + nsRect r(inner); + r.width = debugBorder.left; + aRenderingContext.FillRect(r); + + // top + r = inner; + r.height = debugBorder.top; + aRenderingContext.FillRect(r); + + //right + r = inner; + r.x = r.x + r.width - debugBorder.right; + r.width = debugBorder.right; + aRenderingContext.FillRect(r); + + //bottom + r = inner; + r.y = r.y + r.height - debugBorder.bottom; + r.height = debugBorder.bottom; + aRenderingContext.FillRect(r); + + + // if we have dirty children or we are dirty + // place a green border around us. + if (NS_SUBTREE_DIRTY(this)) { + nsRect dirtyr(inner); + aRenderingContext.SetColor(NS_RGB(0,255,0)); + aRenderingContext.DrawRect(dirtyr); + aRenderingContext.SetColor(color); + } +} + +void +nsBoxFrame::PaintXULDebugOverlay(nsRenderingContext& aRenderingContext, + nsPoint aPt) + nsMargin border; + GetBorder(border); + + nsMargin debugMargin; + GetDebugMargin(debugMargin); + PixelMarginToTwips(GetPresContext(), debugMargin); + + nsRect inner(mRect); + inner.MoveTo(aPt); + inner.Deflate(debugMargin); + inner.Deflate(border); + + nscoord onePixel = GetPresContext()->IntScaledPixelsToTwips(1); + + kid = GetChildBox(); + while (nullptr != kid) { + bool isHorizontal = IsHorizontal(); + + nscoord x, y, borderSize, spacerSize; + + nsRect cr(kid->mRect); + nsMargin margin; + kid->GetMargin(margin); + cr.Inflate(margin); + + if (isHorizontal) + { + cr.y = inner.y; + x = cr.x; + y = cr.y + onePixel; + spacerSize = debugBorder.top - onePixel*4; + } else { + cr.x = inner.x; + x = cr.y; + y = cr.x + onePixel; + spacerSize = debugBorder.left - onePixel*4; + } + + nsBoxLayoutState state(GetPresContext()); + nscoord flex = kid->GetFlex(state); + + if (!kid->IsCollapsed()) { + aRenderingContext.SetColor(NS_RGB(255,255,255)); + + if (isHorizontal) + borderSize = cr.width; + else + borderSize = cr.height; + + DrawSpacer(GetPresContext(), aRenderingContext, isHorizontal, flex, x, y, borderSize, spacerSize); + } + + kid = kid->GetNextBox(); + } +} +#endif + +#ifdef DEBUG_LAYOUT +void +nsBoxFrame::GetBoxName(nsAutoString& aName) +{ + GetFrameName(aName); +} +#endif + +#ifdef DEBUG +NS_IMETHODIMP +nsBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("Box"), aResult); +} +#endif + +nsIAtom* +nsBoxFrame::GetType() const +{ + return nsGkAtoms::boxFrame; +} + +#ifdef DEBUG_LAYOUT +NS_IMETHODIMP +nsBoxFrame::GetDebug(bool& aDebug) +{ + aDebug = (mState & NS_STATE_CURRENTLY_IN_DEBUG); + return NS_OK; +} +#endif + +// REVIEW: nsBoxFrame::GetFrameForPoint is a problem because of 'mousethrough' +// attribute support. Here's how it works: +// * For each child frame F, we determine the target frame T(F) by recursively +// invoking GetFrameForPoint on the child +// * Let F' be the last child frame such that T(F') doesn't have mousethrough. +// If F' exists, return T(F') +// * Otherwise let F'' be the first child frame such that T(F'') is non-null. +// If F'' exists, return T(F'') +// * Otherwise return this frame, if this frame contains the point +// * Otherwise return null +// It's not clear how this should work for more complex z-ordering situations. +// The basic principle seems to be that if a frame F has a descendant +// 'mousethrough' frame that includes the target position, then F +// will not receive events (unless it overrides GetFrameForPoint). +// A 'mousethrough' frame will only receive an event if, after applying that rule, +// all eligible frames are 'mousethrough'; the bottom-most inner-most 'mousethrough' +// frame is then chosen (the first eligible frame reached in a +// traversal of the frame tree --- pre/post is irrelevant since ancestors +// of the mousethrough frames can't be eligible). +// IMHO this is very bogus and adds a great deal of complexity for something +// that is very rarely used. So I'm redefining 'mousethrough' to the following: +// a frame with mousethrough is transparent to mouse events. This is compatible +// with the way 'mousethrough' is used in Seamonkey's navigator.xul and +// Firefox's browser.xul. The only other place it's used is in the 'expander' +// XBL binding, which in our tree is only used by Thunderbird SMIME Advanced +// Preferences, and I can't figure out what that does, so I'll have to test it. +// If it's broken I'll probably just change the binding to use it more sensibly. +// This new behaviour is implemented in nsDisplayList::HitTest. +// REVIEW: This debug-box stuff is annoying. I'm just going to put debug boxes +// in the outline layer and avoid GetDebugBoxAt. + +// REVIEW: GetCursor had debug-only event dumping code. I have replaced it +// with instrumentation in nsDisplayXULDebug. + +#ifdef DEBUG_LAYOUT +void +nsBoxFrame::DrawLine(nsRenderingContext& aRenderingContext, bool aHorizontal, nscoord x1, nscoord y1, nscoord x2, nscoord y2) +{ + if (aHorizontal) + aRenderingContext.DrawLine(x1,y1,x2,y2); + else + aRenderingContext.DrawLine(y1,x1,y2,x2); +} + +void +nsBoxFrame::FillRect(nsRenderingContext& aRenderingContext, bool aHorizontal, nscoord x, nscoord y, nscoord width, nscoord height) +{ + if (aHorizontal) + aRenderingContext.FillRect(x,y,width,height); + else + aRenderingContext.FillRect(y,x,height,width); +} + +void +nsBoxFrame::DrawSpacer(nsPresContext* aPresContext, nsRenderingContext& aRenderingContext, bool aHorizontal, int32_t flex, nscoord x, nscoord y, nscoord size, nscoord spacerSize) +{ + nscoord onePixel = aPresContext->IntScaledPixelsToTwips(1); + + // if we do draw the coils + int distance = 0; + int center = 0; + int offset = 0; + int coilSize = COIL_SIZE*onePixel; + int halfSpacer = spacerSize/2; + + distance = size; + center = y + halfSpacer; + offset = x; + + int coils = distance/coilSize; + + int halfCoilSize = coilSize/2; + + if (flex == 0) { + DrawLine(aRenderingContext, aHorizontal, x,y + spacerSize/2, x + size, y + spacerSize/2); + } else { + for (int i=0; i < coils; i++) + { + DrawLine(aRenderingContext, aHorizontal, offset, center+halfSpacer, offset+halfCoilSize, center-halfSpacer); + DrawLine(aRenderingContext, aHorizontal, offset+halfCoilSize, center-halfSpacer, offset+coilSize, center+halfSpacer); + + offset += coilSize; + } + } + + FillRect(aRenderingContext, aHorizontal, x + size - spacerSize/2, y, spacerSize/2, spacerSize); + FillRect(aRenderingContext, aHorizontal, x, y, spacerSize/2, spacerSize); + + //DrawKnob(aPresContext, aRenderingContext, x + size - spacerSize, y, spacerSize); +} + +void +nsBoxFrame::GetDebugBorder(nsMargin& aInset) +{ + aInset.SizeTo(2,2,2,2); + + if (IsHorizontal()) + aInset.top = 10; + else + aInset.left = 10; +} + +void +nsBoxFrame::GetDebugMargin(nsMargin& aInset) +{ + aInset.SizeTo(2,2,2,2); +} + +void +nsBoxFrame::GetDebugPadding(nsMargin& aPadding) +{ + aPadding.SizeTo(2,2,2,2); +} + +void +nsBoxFrame::PixelMarginToTwips(nsPresContext* aPresContext, nsMargin& aMarginPixels) +{ + nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); + aMarginPixels.left *= onePixel; + aMarginPixels.right *= onePixel; + aMarginPixels.top *= onePixel; + aMarginPixels.bottom *= onePixel; +} + +void +nsBoxFrame::GetValue(nsPresContext* aPresContext, const nsSize& a, const nsSize& b, char* ch) +{ + float p2t = aPresContext->ScaledPixelsToTwips(); + + char width[100]; + char height[100]; + + if (a.width == NS_INTRINSICSIZE) + sprintf(width,"%s","INF"); + else + sprintf(width,"%d", nscoord(a.width/*/p2t*/)); + + if (a.height == NS_INTRINSICSIZE) + sprintf(height,"%s","INF"); + else + sprintf(height,"%d", nscoord(a.height/*/p2t*/)); + + + sprintf(ch, "(%s%s, %s%s)", width, (b.width != NS_INTRINSICSIZE ? "[SET]" : ""), + height, (b.height != NS_INTRINSICSIZE ? "[SET]" : "")); + +} + +void +nsBoxFrame::GetValue(nsPresContext* aPresContext, int32_t a, int32_t b, char* ch) +{ + if (a == NS_INTRINSICSIZE) + sprintf(ch, "%d[SET]", b); + else + sprintf(ch, "%d", a); +} + +nsresult +nsBoxFrame::DisplayDebugInfoFor(nsIFrame* aBox, + nsPoint& aPoint) +{ + nsBoxLayoutState state(GetPresContext()); + + nscoord x = aPoint.x; + nscoord y = aPoint.y; + + // get the area inside our border but not our debug margins. + nsRect insideBorder(aBox->mRect); + insideBorder.MoveTo(0,0): + nsMargin border(0,0,0,0); + aBox->GetBorderAndPadding(border); + insideBorder.Deflate(border); + + bool isHorizontal = IsHorizontal(); + + if (!insideBorder.Contains(nsPoint(x,y))) + return NS_ERROR_FAILURE; + + //printf("%%%%%% inside box %%%%%%%\n"); + + int count = 0; + nsIFrame* child = aBox->GetChildBox(); + + nsMargin m; + nsMargin m2; + GetDebugBorder(m); + PixelMarginToTwips(aPresContext, m); + + GetDebugMargin(m2); + PixelMarginToTwips(aPresContext, m2); + + m += m2; + + if ((isHorizontal && y < insideBorder.y + m.top) || + (!isHorizontal && x < insideBorder.x + m.left)) { + //printf("**** inside debug border *******\n"); + while (child) + { + const nsRect& r = child->mRect; + + // if we are not in the child. But in the spacer above the child. + if ((isHorizontal && x >= r.x && x < r.x + r.width) || + (!isHorizontal && y >= r.y && y < r.y + r.height)) { + aCursor = NS_STYLE_CURSOR_POINTER; + // found it but we already showed it. + if (mDebugChild == child) + return NS_OK; + + if (aBox->GetContent()) { + printf("---------------\n"); + DumpBox(stdout); + printf("\n"); + } + + if (child->GetContent()) { + printf("child #%d: ", count); + child->DumpBox(stdout); + printf("\n"); + } + + mDebugChild = child; + + nsSize prefSizeCSS(NS_INTRINSICSIZE, NS_INTRINSICSIZE); + nsSize minSizeCSS (NS_INTRINSICSIZE, NS_INTRINSICSIZE); + nsSize maxSizeCSS (NS_INTRINSICSIZE, NS_INTRINSICSIZE); + nscoord flexCSS = NS_INTRINSICSIZE; + + bool widthSet, heightSet; + nsIFrame::AddCSSPrefSize(child, prefSizeCSS, widthSet, heightSet); + nsIFrame::AddCSSMinSize (state, child, minSizeCSS, widthSet, heightSet); + nsIFrame::AddCSSMaxSize (child, maxSizeCSS, widthSet, heightSet); + nsIFrame::AddCSSFlex (state, child, flexCSS); + + nsSize prefSize = child->GetPrefSize(state); + nsSize minSize = child->GetMinSize(state); + nsSize maxSize = child->GetMaxSize(state); + nscoord flexSize = child->GetFlex(state); + nscoord ascentSize = child->GetBoxAscent(state); + + char min[100]; + char pref[100]; + char max[100]; + char calc[100]; + char flex[100]; + char ascent[100]; + + nsSize actualSize; + GetFrameSizeWithMargin(child, actualSize); + nsSize actualSizeCSS (NS_INTRINSICSIZE, NS_INTRINSICSIZE); + + GetValue(aPresContext, minSize, minSizeCSS, min); + GetValue(aPresContext, prefSize, prefSizeCSS, pref); + GetValue(aPresContext, maxSize, maxSizeCSS, max); + GetValue(aPresContext, actualSize, actualSizeCSS, calc); + GetValue(aPresContext, flexSize, flexCSS, flex); + GetValue(aPresContext, ascentSize, NS_INTRINSICSIZE, ascent); + + + printf("min%s, pref%s, max%s, actual%s, flex=%s, ascent=%s\n\n", + min, + pref, + max, + calc, + flex, + ascent + ); + + return NS_OK; + } + + child = child->GetNextBox(); + count++; + } + } else { + } + + mDebugChild = nullptr; + + return NS_OK; +} + +void +nsBoxFrame::SetDebugOnChildList(nsBoxLayoutState& aState, nsIFrame* aChild, bool aDebug) +{ + nsIFrame* child = GetChildBox(); + while (child) + { + child->SetDebug(aState, aDebug); + child = child->GetNextBox(); + } +} + +nsresult +nsBoxFrame::GetFrameSizeWithMargin(nsIFrame* aBox, nsSize& aSize) +{ + nsRect rect(aBox->GetRect()); + nsMargin margin(0,0,0,0); + aBox->GetMargin(margin); + rect.Inflate(margin); + aSize.width = rect.width; + aSize.height = rect.height; + return NS_OK; +} +#endif + +// If you make changes to this function, check its counterparts +// in nsTextBoxFrame and nsXULLabelFrame +void +nsBoxFrame::RegUnregAccessKey(bool aDoReg) +{ + MOZ_ASSERT(mContent); + + // find out what type of element this is + nsIAtom *atom = mContent->Tag(); + + // only support accesskeys for the following elements + if (atom != nsGkAtoms::button && + atom != nsGkAtoms::toolbarbutton && + atom != nsGkAtoms::checkbox && + atom != nsGkAtoms::textbox && + atom != nsGkAtoms::tab && + atom != nsGkAtoms::radio) { + return; + } + + nsAutoString accessKey; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey); + + if (accessKey.IsEmpty()) + return; + + // With a valid PresContext we can get the ESM + // and register the access key + nsEventStateManager *esm = PresContext()->EventStateManager(); + + uint32_t key = accessKey.First(); + if (aDoReg) + esm->RegisterAccessKey(mContent, key); + else + esm->UnregisterAccessKey(mContent, key); +} + +bool +nsBoxFrame::SupportsOrdinalsInChildren() +{ + return true; +} + +// Helper less-than-or-equal function, used in CheckBoxOrder() as a +// template-parameter for the sorting functions. +bool +IsBoxOrdinalLEQ(nsIFrame* aFrame1, + nsIFrame* aFrame2) +{ + // If we've got a placeholder frame, use its out-of-flow frame's ordinal val. + nsIFrame* aRealFrame1 = nsPlaceholderFrame::GetRealFrameFor(aFrame1); + nsIFrame* aRealFrame2 = nsPlaceholderFrame::GetRealFrameFor(aFrame2); + return aRealFrame1->GetOrdinal() <= aRealFrame2->GetOrdinal(); +} + +void +nsBoxFrame::CheckBoxOrder() +{ + if (SupportsOrdinalsInChildren() && + !nsLayoutUtils::IsFrameListSorted<IsBoxOrdinalLEQ>(mFrames)) { + nsLayoutUtils::SortFrameList<IsBoxOrdinalLEQ>(mFrames); + } +} + +nsresult +nsBoxFrame::LayoutChildAt(nsBoxLayoutState& aState, nsIFrame* aBox, const nsRect& aRect) +{ + // get the current rect + nsRect oldRect(aBox->GetRect()); + aBox->SetBounds(aState, aRect); + + bool layout = NS_SUBTREE_DIRTY(aBox); + + if (layout || (oldRect.width != aRect.width || oldRect.height != aRect.height)) { + return aBox->Layout(aState); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBoxFrame::RelayoutChildAtOrdinal(nsBoxLayoutState& aState, nsIFrame* aChild) +{ + if (!SupportsOrdinalsInChildren()) + return NS_OK; + + uint32_t ord = aChild->GetOrdinal(); + + nsIFrame* child = mFrames.FirstChild(); + nsIFrame* newPrevSib = nullptr; + + while (child) { + if (ord < child->GetOrdinal()) { + break; + } + + if (child != aChild) { + newPrevSib = child; + } + + child = child->GetNextBox(); + } + + if (aChild->GetPrevSibling() == newPrevSib) { + // This box is not moving. + return NS_OK; + } + + // Take |aChild| out of its old position in the child list. + mFrames.RemoveFrame(aChild); + + // Insert it after |newPrevSib| or at the start if it's null. + mFrames.InsertFrame(nullptr, newPrevSib, aChild); + + return NS_OK; +} + +/** + * This wrapper class lets us redirect mouse hits from descendant frames + * of a menu to the menu itself, if they didn't specify 'allowevents'. + * + * The wrapper simply turns a hit on a descendant element + * into a hit on the menu itself, unless there is an element between the target + * and the menu with the "allowevents" attribute. + * + * This is used by nsMenuFrame and nsTreeColFrame. + * + * Note that turning a hit on a descendant element into nullptr, so events + * could fall through to the menu background, might be an appealing simplification + * but it would mean slightly strange behaviour in some cases, because grabber + * wrappers can be created for many individual lists and items, so the exact + * fallthrough behaviour would be complex. E.g. an element with "allowevents" + * on top of the Content() list could receive the event even if it was covered + * by a PositionedDescenants() element without "allowevents". It is best to + * never convert a non-null hit into null. + */ +// REVIEW: This is roughly of what nsMenuFrame::GetFrameForPoint used to do. +// I've made 'allowevents' affect child elements because that seems the only +// reasonable thing to do. +class nsDisplayXULEventRedirector : public nsDisplayWrapList { +public: + nsDisplayXULEventRedirector(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayItem* aItem, + nsIFrame* aTargetFrame) + : nsDisplayWrapList(aBuilder, aFrame, aItem), mTargetFrame(aTargetFrame) {} + nsDisplayXULEventRedirector(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + nsIFrame* aTargetFrame) + : nsDisplayWrapList(aBuilder, aFrame, aList), mTargetFrame(aTargetFrame) {} + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames); + NS_DISPLAY_DECL_NAME("XULEventRedirector", TYPE_XUL_EVENT_REDIRECTOR) +private: + nsIFrame* mTargetFrame; +}; + +void nsDisplayXULEventRedirector::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) +{ + nsTArray<nsIFrame*> outFrames; + mList.HitTest(aBuilder, aRect, aState, &outFrames); + + bool topMostAdded = false; + uint32_t localLength = outFrames.Length(); + + for (uint32_t i = 0; i < localLength; i++) { + + for (nsIContent* content = outFrames.ElementAt(i)->GetContent(); + content && content != mTargetFrame->GetContent(); + content = content->GetParent()) { + if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::allowevents, + nsGkAtoms::_true, eCaseMatters)) { + // Events are allowed on 'frame', so let it go. + aOutFrames->AppendElement(outFrames.ElementAt(i)); + topMostAdded = true; + } + } + + // If there was no hit on the topmost frame or its ancestors, + // add the target frame itself as the first candidate (see bug 562554). + if (!topMostAdded) { + topMostAdded = true; + aOutFrames->AppendElement(mTargetFrame); + } + } +} + +class nsXULEventRedirectorWrapper : public nsDisplayWrapper +{ +public: + nsXULEventRedirectorWrapper(nsIFrame* aTargetFrame) + : mTargetFrame(aTargetFrame) {} + virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) { + return new (aBuilder) + nsDisplayXULEventRedirector(aBuilder, aFrame, aList, mTargetFrame); + } + virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) { + return new (aBuilder) + nsDisplayXULEventRedirector(aBuilder, aItem->Frame(), aItem, + mTargetFrame); + } +private: + nsIFrame* mTargetFrame; +}; + +void +nsBoxFrame::WrapListsInRedirector(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aIn, + const nsDisplayListSet& aOut) +{ + nsXULEventRedirectorWrapper wrapper(this); + wrapper.WrapLists(aBuilder, this, aIn, aOut); +} + +bool +nsBoxFrame::GetEventPoint(nsGUIEvent* aEvent, nsPoint &aPoint) { + nsIntPoint refPoint; + bool res = GetEventPoint(aEvent, refPoint); + aPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, refPoint, this); + return res; +} + +bool +nsBoxFrame::GetEventPoint(nsGUIEvent* aEvent, nsIntPoint &aPoint) { + NS_ENSURE_TRUE(aEvent, false); + + if (aEvent->eventStructType == NS_TOUCH_EVENT) { + nsTouchEvent* touchEvent = static_cast<nsTouchEvent*>(aEvent); + // return false if there is more than one touch on the page, or if + // we can't find a touch point + if (touchEvent->touches.Length() != 1) { + return false; + } + + nsIDOMTouch *touch = touchEvent->touches.SafeElementAt(0); + if (!touch) { + return false; + } + Touch* domtouch = static_cast<Touch*>(touch); + aPoint = domtouch->mRefPoint; + } else { + aPoint = aEvent->refPoint; + } + return true; +} diff --git a/layout/xul/base/src/nsBoxFrame.h b/layout/xul/base/src/nsBoxFrame.h new file mode 100644 index 000000000..a22358565 --- /dev/null +++ b/layout/xul/base/src/nsBoxFrame.h @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Eric D Vaughan + nsBoxFrame is a frame that can lay its children out either vertically or horizontally. + It lays them out according to a min max or preferred size. + +**/ + +#ifndef nsBoxFrame_h___ +#define nsBoxFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsContainerFrame.h" +#include "nsBoxLayout.h" + +class nsBoxLayoutState; + +// flags from box +#define NS_STATE_BOX_CHILD_RESERVED NS_FRAME_STATE_BIT(20) +#define NS_STATE_STACK_NOT_POSITIONED NS_FRAME_STATE_BIT(21) +//#define NS_STATE_IS_HORIZONTAL NS_FRAME_STATE_BIT(22) moved to nsIFrame.h +#define NS_STATE_AUTO_STRETCH NS_FRAME_STATE_BIT(23) +//#define NS_STATE_IS_ROOT NS_FRAME_STATE_BIT(24) moved to nsBox.h +#define NS_STATE_CURRENTLY_IN_DEBUG NS_FRAME_STATE_BIT(25) +//#define NS_STATE_SET_TO_DEBUG NS_FRAME_STATE_BIT(26) moved to nsBox.h +//#define NS_STATE_DEBUG_WAS_SET NS_FRAME_STATE_BIT(27) moved to nsBox.h +#define NS_STATE_MENU_HAS_POPUP_LIST NS_FRAME_STATE_BIT(28) /* used on nsMenuFrame */ +#define NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK NS_FRAME_STATE_BIT(29) +#define NS_STATE_EQUAL_SIZE NS_FRAME_STATE_BIT(30) +//#define NS_STATE_IS_DIRECTION_NORMAL NS_FRAME_STATE_BIT(31) moved to nsIFrame.h +#define NS_FRAME_MOUSE_THROUGH_ALWAYS NS_FRAME_STATE_BIT(60) +#define NS_FRAME_MOUSE_THROUGH_NEVER NS_FRAME_STATE_BIT(61) + +nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + bool aIsRoot, + nsBoxLayout* aLayoutManager); +nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + +class nsBoxFrame : public nsContainerFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + bool aIsRoot, + nsBoxLayout* aLayoutManager); + friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + // gets the rect inside our border and debug border. If you wish to paint inside a box + // call this method to get the rect so you don't draw on the debug border or outer border. + + virtual void SetLayoutManager(nsBoxLayout* aLayout) MOZ_OVERRIDE { mLayoutManager = aLayout; } + virtual nsBoxLayout* GetLayoutManager() MOZ_OVERRIDE { return mLayoutManager; } + + NS_IMETHOD RelayoutChildAtOrdinal(nsBoxLayoutState& aState, nsIFrame* aChild) MOZ_OVERRIDE; + + virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nscoord GetFlex(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nscoord GetBoxAscent(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; +#ifdef DEBUG_LAYOUT + NS_IMETHOD SetDebug(nsBoxLayoutState& aBoxLayoutState, bool aDebug) MOZ_OVERRIDE; + NS_IMETHOD GetDebug(bool& aDebug) MOZ_OVERRIDE; +#endif + virtual Valignment GetVAlign() const MOZ_OVERRIDE { return mValign; } + virtual Halignment GetHAlign() const MOZ_OVERRIDE { return mHalign; } + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + virtual bool ComputesOwnOverflowArea() MOZ_OVERRIDE { return false; } + + // ----- child and sibling operations --- + + // ----- public methods ------- + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* asPrevInFlow) MOZ_OVERRIDE; + + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + virtual void MarkIntrinsicWidthsDirty() MOZ_OVERRIDE; + virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE; + virtual nscoord GetPrefWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE; + + NS_IMETHOD Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) MOZ_OVERRIDE; + + NS_IMETHOD AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) MOZ_OVERRIDE; + + NS_IMETHOD InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) MOZ_OVERRIDE; + + NS_IMETHOD RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) MOZ_OVERRIDE; + + virtual nsIFrame* GetContentInsertionFrame() MOZ_OVERRIDE; + + NS_IMETHOD SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) MOZ_OVERRIDE; + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) MOZ_OVERRIDE; + + virtual nsIAtom* GetType() const MOZ_OVERRIDE; + + virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE + { + // record that children that are ignorable whitespace should be excluded + // (When content was loaded via the XUL content sink, it's already + // been excluded, but we need this for when the XUL namespace is used + // in other MIME types or when the XUL CSS display types are used with + // non-XUL elements.) + + // This is bogus, but it's what we've always done. + // (Given that we're replaced, we need to say we're a replaced element + // that contains a block so nsHTMLReflowState doesn't tell us to be + // NS_INTRINSICSIZE wide.) + return nsContainerFrame::IsFrameOfType(aFlags & + ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock | eXULBox | + nsIFrame::eExcludesIgnorableWhitespace)); + } + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE; +#endif + + NS_IMETHOD DidReflow(nsPresContext* aPresContext, + const nsHTMLReflowState* aReflowState, + nsDidReflowStatus aStatus) MOZ_OVERRIDE; + + virtual bool HonorPrintBackgroundSettings() MOZ_OVERRIDE; + + virtual ~nsBoxFrame(); + + nsBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot = false, nsBoxLayout* aLayoutManager = nullptr); + + // virtual so nsStackFrame, nsButtonBoxFrame, nsSliderFrame and nsMenuFrame + // can override it + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists); + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + +#ifdef DEBUG_LAYOUT + virtual void SetDebugOnChildList(nsBoxLayoutState& aState, nsIFrame* aChild, bool aDebug); + nsresult DisplayDebugInfoFor(nsIFrame* aBox, + nsPoint& aPoint); +#endif + + static nsresult LayoutChildAt(nsBoxLayoutState& aState, nsIFrame* aBox, const nsRect& aRect); + + /** + * Utility method to redirect events on descendants to this frame. + * Supports 'allowevents' attribute on descendant elements to allow those + * elements and their descendants to receive events. + */ + void WrapListsInRedirector(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aIn, + const nsDisplayListSet& aOut); + + /** + * This defaults to true, but some box frames (nsListBoxBodyFrame for + * example) don't support ordinals in their children. + */ + virtual bool SupportsOrdinalsInChildren(); + +protected: +#ifdef DEBUG_LAYOUT + virtual void GetBoxName(nsAutoString& aName) MOZ_OVERRIDE; + void PaintXULDebugBackground(nsRenderingContext& aRenderingContext, + nsPoint aPt); + void PaintXULDebugOverlay(nsRenderingContext& aRenderingContext, + nsPoint aPt); +#endif + + virtual bool GetInitialEqualSize(bool& aEqualSize); + virtual void GetInitialOrientation(bool& aIsHorizontal); + virtual void GetInitialDirection(bool& aIsNormal); + virtual bool GetInitialHAlignment(Halignment& aHalign); + virtual bool GetInitialVAlignment(Valignment& aValign); + virtual bool GetInitialAutoStretch(bool& aStretch); + + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + nsSize mPrefSize; + nsSize mMinSize; + nsSize mMaxSize; + nscoord mFlex; + nscoord mAscent; + + nsCOMPtr<nsBoxLayout> mLayoutManager; + + // Get the point associated with this event. Returns true if a single valid + // point was found. Otherwise false. + bool GetEventPoint(nsGUIEvent *aEvent, nsPoint &aPoint); + // Gets the event coordinates relative to the widget offset associated with + // this frame. Return true if a single valid point was found. + bool GetEventPoint(nsGUIEvent *aEvent, nsIntPoint &aPoint); + +protected: + void RegUnregAccessKey(bool aDoReg); + + NS_HIDDEN_(void) CheckBoxOrder(); + +private: + +#ifdef DEBUG_LAYOUT + nsresult SetDebug(nsPresContext* aPresContext, bool aDebug); + bool GetInitialDebug(bool& aDebug); + void GetDebugPref(nsPresContext* aPresContext); + + void GetDebugBorder(nsMargin& aInset); + void GetDebugPadding(nsMargin& aInset); + void GetDebugMargin(nsMargin& aInset); + + nsresult GetFrameSizeWithMargin(nsIFrame* aBox, nsSize& aSize); + + void PixelMarginToTwips(nsPresContext* aPresContext, nsMargin& aMarginPixels); + + void GetValue(nsPresContext* aPresContext, const nsSize& a, const nsSize& b, char* value); + void GetValue(nsPresContext* aPresContext, int32_t a, int32_t b, char* value); + void DrawSpacer(nsPresContext* aPresContext, nsRenderingContext& aRenderingContext, bool aHorizontal, int32_t flex, nscoord x, nscoord y, nscoord size, nscoord spacerSize); + void DrawLine(nsRenderingContext& aRenderingContext, bool aHorizontal, nscoord x1, nscoord y1, nscoord x2, nscoord y2); + void FillRect(nsRenderingContext& aRenderingContext, bool aHorizontal, nscoord x, nscoord y, nscoord width, nscoord height); +#endif + virtual void UpdateMouseThrough(); + + void CacheAttributes(); + + // instance variables. + Halignment mHalign; + Valignment mValign; + +#ifdef DEBUG_LAYOUT + static bool gDebug; + static nsIFrame* mDebugChild; +#endif + +}; // class nsBoxFrame + +#endif + diff --git a/layout/xul/base/src/nsBoxLayout.cpp b/layout/xul/base/src/nsBoxLayout.cpp new file mode 100644 index 000000000..223cef7f5 --- /dev/null +++ b/layout/xul/base/src/nsBoxLayout.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsBox.h" +#include "nsCOMPtr.h" +#include "nsContainerFrame.h" +#include "nsBoxLayout.h" + +void +nsBoxLayout::AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize) +{ + nsBox::AddBorderAndPadding(aBox, aSize); +} + +void +nsBoxLayout::AddMargin(nsIFrame* aBox, nsSize& aSize) +{ + nsBox::AddMargin(aBox, aSize); +} + +void +nsBoxLayout::AddMargin(nsSize& aSize, const nsMargin& aMargin) +{ + nsBox::AddMargin(aSize, aMargin); +} + +nsSize +nsBoxLayout::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize pref (0, 0); + AddBorderAndPadding(aBox, pref); + + return pref; +} + +nsSize +nsBoxLayout::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize minSize (0,0); + AddBorderAndPadding(aBox, minSize); + return minSize; +} + +nsSize +nsBoxLayout::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + //AddBorderAndPadding () never changes maxSize (NS_INTRINSICSIZE) + //AddBorderAndPadding(aBox, maxSize); + return nsSize (NS_INTRINSICSIZE,NS_INTRINSICSIZE); +} + + +nscoord +nsBoxLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + return 0; +} + +NS_IMETHODIMP +nsBoxLayout::Layout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + return NS_OK; +} + +void +nsBoxLayout::AddLargestSize(nsSize& aSize, const nsSize& aSize2) +{ + if (aSize2.width > aSize.width) + aSize.width = aSize2.width; + + if (aSize2.height > aSize.height) + aSize.height = aSize2.height; +} + +void +nsBoxLayout::AddSmallestSize(nsSize& aSize, const nsSize& aSize2) +{ + if (aSize2.width < aSize.width) + aSize.width = aSize2.width; + + if (aSize2.height < aSize.height) + aSize.height = aSize2.height; +} + +NS_IMPL_ISUPPORTS1(nsBoxLayout, nsBoxLayout) diff --git a/layout/xul/base/src/nsBoxLayout.h b/layout/xul/base/src/nsBoxLayout.h new file mode 100644 index 000000000..201ee61fe --- /dev/null +++ b/layout/xul/base/src/nsBoxLayout.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBoxLayout_h___ +#define nsBoxLayout_h___ + +#include "nsISupports.h" +#include "nsCoord.h" +#include "nsFrameList.h" + +class nsIFrame; +class nsBoxLayoutState; +struct nsSize; +struct nsMargin; + +#define NS_BOX_LAYOUT_IID \ +{ 0x09d522a7, 0x304c, 0x4137, \ + { 0xaf, 0xc9, 0xe0, 0x80, 0x2e, 0x89, 0xb7, 0xe8 } } + +class nsIGridPart; + +class nsBoxLayout : public nsISupports { + +public: + + nsBoxLayout() {} + virtual ~nsBoxLayout() {} + + NS_DECL_ISUPPORTS + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_BOX_LAYOUT_IID) + + NS_IMETHOD Layout(nsIFrame* aBox, nsBoxLayoutState& aState); + + virtual nsSize GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState); + virtual nsSize GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState); + virtual nsSize GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState); + virtual nscoord GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState); + virtual void ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aPrevBox, + const nsFrameList::Slice& aNewChildren) {} + virtual void ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState, + const nsFrameList::Slice& aNewChildren) {} + virtual void ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) {} + virtual void ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) {} + virtual void IntrinsicWidthsDirty(nsIFrame* aBox, nsBoxLayoutState& aState) {} + + virtual void AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize); + virtual void AddMargin(nsIFrame* aChild, nsSize& aSize); + virtual void AddMargin(nsSize& aSize, const nsMargin& aMargin); + + virtual nsIGridPart* AsGridPart() { return nullptr; } + + static void AddLargestSize(nsSize& aSize, const nsSize& aToAdd); + static void AddSmallestSize(nsSize& aSize, const nsSize& aToAdd); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsBoxLayout, NS_BOX_LAYOUT_IID) + +#endif + diff --git a/layout/xul/base/src/nsBoxLayoutState.cpp b/layout/xul/base/src/nsBoxLayoutState.cpp new file mode 100644 index 000000000..603020e0b --- /dev/null +++ b/layout/xul/base/src/nsBoxLayoutState.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsBoxLayoutState.h" + +nsBoxLayoutState::nsBoxLayoutState(nsPresContext* aPresContext, + nsRenderingContext* aRenderingContext, + const nsHTMLReflowState* aOuterReflowState, + uint16_t aReflowDepth) + : mPresContext(aPresContext) + , mRenderingContext(aRenderingContext) + , mOuterReflowState(aOuterReflowState) + , mLayoutFlags(0) + , mReflowDepth(aReflowDepth) + , mPaintingDisabled(false) +{ + NS_ASSERTION(mPresContext, "PresContext must be non-null"); +} + +nsBoxLayoutState::nsBoxLayoutState(const nsBoxLayoutState& aState) + : mPresContext(aState.mPresContext) + , mRenderingContext(aState.mRenderingContext) + , mOuterReflowState(aState.mOuterReflowState) + , mLayoutFlags(aState.mLayoutFlags) + , mReflowDepth(aState.mReflowDepth + 1) + , mPaintingDisabled(aState.mPaintingDisabled) +{ + NS_ASSERTION(mPresContext, "PresContext must be non-null"); +} diff --git a/layout/xul/base/src/nsBoxLayoutState.h b/layout/xul/base/src/nsBoxLayoutState.h new file mode 100644 index 000000000..0cd9da5ab --- /dev/null +++ b/layout/xul/base/src/nsBoxLayoutState.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Author: + Eric D Vaughan + +**/ + +#ifndef nsBoxLayoutState_h___ +#define nsBoxLayoutState_h___ + +#include "nsCOMPtr.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" + +class nsRenderingContext; +class nsCalculatedBoxInfo; +struct nsHTMLReflowMetrics; +struct nsHTMLReflowState; +class nsString; +class nsHTMLReflowCommand; + +class MOZ_STACK_CLASS nsBoxLayoutState +{ +public: + nsBoxLayoutState(nsPresContext* aPresContext, + nsRenderingContext* aRenderingContext = nullptr, + // see OuterReflowState() below + const nsHTMLReflowState* aOuterReflowState = nullptr, + uint16_t aReflowDepth = 0) NS_HIDDEN; + nsBoxLayoutState(const nsBoxLayoutState& aState) NS_HIDDEN; + + nsPresContext* PresContext() const { return mPresContext; } + nsIPresShell* PresShell() const { return mPresContext->PresShell(); } + + uint32_t LayoutFlags() const { return mLayoutFlags; } + void SetLayoutFlags(uint32_t aFlags) { mLayoutFlags = aFlags; } + + // if true no one under us will paint during reflow. + void SetPaintingDisabled(bool aDisable) { mPaintingDisabled = aDisable; } + bool PaintingDisabled() const { return mPaintingDisabled; } + + // The rendering context may be null for specialized uses of + // nsBoxLayoutState and should be null-checked before it is used. + // However, passing a null rendering context to the constructor when + // doing box layout or intrinsic size calculation will cause bugs. + nsRenderingContext* GetRenderingContext() const { return mRenderingContext; } + + struct AutoReflowDepth { + AutoReflowDepth(nsBoxLayoutState& aState) + : mState(aState) { ++mState.mReflowDepth; } + ~AutoReflowDepth() { --mState.mReflowDepth; } + nsBoxLayoutState& mState; + }; + + // The HTML reflow state that lives outside the box-block boundary. + // May not be set reliably yet. + const nsHTMLReflowState* OuterReflowState() { return mOuterReflowState; } + + uint16_t GetReflowDepth() { return mReflowDepth; } + +private: + nsRefPtr<nsPresContext> mPresContext; + nsRenderingContext *mRenderingContext; + const nsHTMLReflowState *mOuterReflowState; + uint32_t mLayoutFlags; + uint16_t mReflowDepth; + bool mPaintingDisabled; +}; + +#endif + diff --git a/layout/xul/base/src/nsBoxObject.cpp b/layout/xul/base/src/nsBoxObject.cpp new file mode 100644 index 000000000..86dc7ebbe --- /dev/null +++ b/layout/xul/base/src/nsBoxObject.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBoxObject.h" +#include "nsCOMPtr.h" +#include "nsIDocument.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsIDocShell.h" +#include "nsReadableUtils.h" +#include "nsDOMClassInfoID.h" +#include "nsView.h" +#ifdef MOZ_XUL +#include "nsIDOMXULElement.h" +#else +#include "nsIDOMElement.h" +#endif +#include "nsLayoutUtils.h" +#include "nsISupportsPrimitives.h" +#include "nsSupportsPrimitives.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla::dom; + +// Implementation ///////////////////////////////////////////////////////////////// + +// Static member variable initialization + +// Implement our nsISupports methods + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBoxObject) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBoxObject) + +DOMCI_DATA(BoxObject, nsBoxObject) + +// QueryInterface implementation for nsBoxObject +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsBoxObject) + NS_INTERFACE_MAP_ENTRY(nsIBoxObject) + NS_INTERFACE_MAP_ENTRY(nsPIBoxObject) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(BoxObject) +NS_INTERFACE_MAP_END + +static PLDHashOperator +PropertyTraverser(const nsAString& aKey, nsISupports* aProperty, void* userArg) +{ + nsCycleCollectionTraversalCallback *cb = + static_cast<nsCycleCollectionTraversalCallback*>(userArg); + + cb->NoteXPCOMChild(aProperty); + + return PL_DHASH_NEXT; +} + +NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsBoxObject) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBoxObject) + if (tmp->mPropertyTable) { + tmp->mPropertyTable->EnumerateRead(PropertyTraverser, &cb); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +// Constructors/Destructors +nsBoxObject::nsBoxObject(void) + :mContent(nullptr) +{ +} + +nsBoxObject::~nsBoxObject(void) +{ +} + +NS_IMETHODIMP +nsBoxObject::GetElement(nsIDOMElement** aResult) +{ + if (mContent) { + return CallQueryInterface(mContent, aResult); + } + + *aResult = nullptr; + return NS_OK; +} + +// nsPIBoxObject ////////////////////////////////////////////////////////////////////////// + +nsresult +nsBoxObject::Init(nsIContent* aContent) +{ + mContent = aContent; + return NS_OK; +} + +void +nsBoxObject::Clear() +{ + mPropertyTable = nullptr; + mContent = nullptr; +} + +void +nsBoxObject::ClearCachedValues() +{ +} + +nsIFrame* +nsBoxObject::GetFrame(bool aFlushLayout) +{ + nsIPresShell* shell = GetPresShell(aFlushLayout); + if (!shell) + return nullptr; + + if (!aFlushLayout) { + // If we didn't flush layout when getting the presshell, we should at least + // flush to make sure our frame model is up to date. + // XXXbz should flush on document, no? Except people call this from + // frame code, maybe? + shell->FlushPendingNotifications(Flush_Frames); + } + + // The flush might have killed mContent. + if (!mContent) { + return nullptr; + } + + return mContent->GetPrimaryFrame(); +} + +nsIPresShell* +nsBoxObject::GetPresShell(bool aFlushLayout) +{ + if (!mContent) { + return nullptr; + } + + nsCOMPtr<nsIDocument> doc = mContent->GetCurrentDoc(); + if (!doc) { + return nullptr; + } + + if (aFlushLayout) { + doc->FlushPendingNotifications(Flush_Layout); + } + + return doc->GetShell(); +} + +nsresult +nsBoxObject::GetOffsetRect(nsIntRect& aRect) +{ + aRect.SetRect(0, 0, 0, 0); + + if (!mContent) + return NS_ERROR_NOT_INITIALIZED; + + // Get the Frame for our content + nsIFrame* frame = GetFrame(true); + if (frame) { + // Get its origin + nsPoint origin = frame->GetPositionIgnoringScrolling(); + + // Find the frame parent whose content is the document element. + Element *docElement = mContent->GetCurrentDoc()->GetRootElement(); + nsIFrame* parent = frame->GetParent(); + for (;;) { + // If we've hit the document element, break here + if (parent->GetContent() == docElement) { + break; + } + + nsIFrame* next = parent->GetParent(); + if (!next) { + NS_WARNING("We should have hit the document element..."); + origin += parent->GetPosition(); + break; + } + + // Add the parent's origin to our own to get to the + // right coordinate system + origin += next->GetPositionOfChildIgnoringScrolling(parent); + parent = next; + } + + // For the origin, add in the border for the frame + const nsStyleBorder* border = frame->StyleBorder(); + origin.x += border->GetComputedBorderWidth(NS_SIDE_LEFT); + origin.y += border->GetComputedBorderWidth(NS_SIDE_TOP); + + // And subtract out the border for the parent + const nsStyleBorder* parentBorder = parent->StyleBorder(); + origin.x -= parentBorder->GetComputedBorderWidth(NS_SIDE_LEFT); + origin.y -= parentBorder->GetComputedBorderWidth(NS_SIDE_TOP); + + aRect.x = nsPresContext::AppUnitsToIntCSSPixels(origin.x); + aRect.y = nsPresContext::AppUnitsToIntCSSPixels(origin.y); + + // Get the union of all rectangles in this and continuation frames. + // It doesn't really matter what we use as aRelativeTo here, since + // we only care about the size. Using 'parent' might make things + // a bit faster by speeding up the internal GetOffsetTo operations. + nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, parent); + aRect.width = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.width); + aRect.height = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.height); + } + + return NS_OK; +} + +nsresult +nsBoxObject::GetScreenPosition(nsIntPoint& aPoint) +{ + aPoint.x = aPoint.y = 0; + + if (!mContent) + return NS_ERROR_NOT_INITIALIZED; + + nsIFrame* frame = GetFrame(true); + if (frame) { + nsIntRect rect = frame->GetScreenRect(); + aPoint.x = rect.x; + aPoint.y = rect.y; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetX(int32_t* aResult) +{ + nsIntRect rect; + GetOffsetRect(rect); + *aResult = rect.x; + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetY(int32_t* aResult) +{ + nsIntRect rect; + GetOffsetRect(rect); + *aResult = rect.y; + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetWidth(int32_t* aResult) +{ + nsIntRect rect; + GetOffsetRect(rect); + *aResult = rect.width; + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetHeight(int32_t* aResult) +{ + nsIntRect rect; + GetOffsetRect(rect); + *aResult = rect.height; + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetScreenX(int32_t *_retval) +{ + nsIntPoint position; + nsresult rv = GetScreenPosition(position); + if (NS_FAILED(rv)) return rv; + + *_retval = position.x; + + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetScreenY(int32_t *_retval) +{ + nsIntPoint position; + nsresult rv = GetScreenPosition(position); + if (NS_FAILED(rv)) return rv; + + *_retval = position.y; + + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetPropertyAsSupports(const PRUnichar* aPropertyName, nsISupports** aResult) +{ + NS_ENSURE_ARG(aPropertyName && *aPropertyName); + if (!mPropertyTable) { + *aResult = nullptr; + return NS_OK; + } + nsDependentString propertyName(aPropertyName); + mPropertyTable->Get(propertyName, aResult); // Addref here. + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::SetPropertyAsSupports(const PRUnichar* aPropertyName, nsISupports* aValue) +{ + NS_ENSURE_ARG(aPropertyName && *aPropertyName); + + if (!mPropertyTable) { + mPropertyTable = new nsInterfaceHashtable<nsStringHashKey,nsISupports>; + if (!mPropertyTable) return NS_ERROR_OUT_OF_MEMORY; + mPropertyTable->Init(8); + } + + nsDependentString propertyName(aPropertyName); + mPropertyTable->Put(propertyName, aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetProperty(const PRUnichar* aPropertyName, PRUnichar** aResult) +{ + nsCOMPtr<nsISupports> data; + nsresult rv = GetPropertyAsSupports(aPropertyName,getter_AddRefs(data)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!data) { + *aResult = nullptr; + return NS_OK; + } + + nsCOMPtr<nsISupportsString> supportsStr = do_QueryInterface(data); + if (!supportsStr) + return NS_ERROR_FAILURE; + + return supportsStr->ToString(aResult); +} + +NS_IMETHODIMP +nsBoxObject::SetProperty(const PRUnichar* aPropertyName, const PRUnichar* aPropertyValue) +{ + NS_ENSURE_ARG(aPropertyName && *aPropertyName); + + nsDependentString propertyName(aPropertyName); + nsDependentString propertyValue; + if (aPropertyValue) { + propertyValue.Rebind(aPropertyValue); + } else { + propertyValue.SetIsVoid(true); + } + + nsCOMPtr<nsISupportsString> supportsStr(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + NS_ENSURE_TRUE(supportsStr, NS_ERROR_OUT_OF_MEMORY); + supportsStr->SetData(propertyValue); + + return SetPropertyAsSupports(aPropertyName,supportsStr); +} + +NS_IMETHODIMP +nsBoxObject::RemoveProperty(const PRUnichar* aPropertyName) +{ + NS_ENSURE_ARG(aPropertyName && *aPropertyName); + + if (!mPropertyTable) return NS_OK; + + nsDependentString propertyName(aPropertyName); + mPropertyTable->Remove(propertyName); + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetParentBox(nsIDOMElement * *aParentBox) +{ + *aParentBox = nullptr; + nsIFrame* frame = GetFrame(false); + if (!frame) return NS_OK; + nsIFrame* parent = frame->GetParent(); + if (!parent) return NS_OK; + + nsCOMPtr<nsIDOMElement> el = do_QueryInterface(parent->GetContent()); + *aParentBox = el; + NS_IF_ADDREF(*aParentBox); + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetFirstChild(nsIDOMElement * *aFirstVisibleChild) +{ + *aFirstVisibleChild = nullptr; + nsIFrame* frame = GetFrame(false); + if (!frame) return NS_OK; + nsIFrame* firstFrame = frame->GetFirstPrincipalChild(); + if (!firstFrame) return NS_OK; + // get the content for the box and query to a dom element + nsCOMPtr<nsIDOMElement> el = do_QueryInterface(firstFrame->GetContent()); + el.swap(*aFirstVisibleChild); + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetLastChild(nsIDOMElement * *aLastVisibleChild) +{ + *aLastVisibleChild = nullptr; + nsIFrame* frame = GetFrame(false); + if (!frame) return NS_OK; + return GetPreviousSibling(frame, nullptr, aLastVisibleChild); +} + +NS_IMETHODIMP +nsBoxObject::GetNextSibling(nsIDOMElement **aNextOrdinalSibling) +{ + *aNextOrdinalSibling = nullptr; + nsIFrame* frame = GetFrame(false); + if (!frame) return NS_OK; + nsIFrame* nextFrame = frame->GetNextSibling(); + if (!nextFrame) return NS_OK; + // get the content for the box and query to a dom element + nsCOMPtr<nsIDOMElement> el = do_QueryInterface(nextFrame->GetContent()); + el.swap(*aNextOrdinalSibling); + return NS_OK; +} + +NS_IMETHODIMP +nsBoxObject::GetPreviousSibling(nsIDOMElement **aPreviousOrdinalSibling) +{ + *aPreviousOrdinalSibling = nullptr; + nsIFrame* frame = GetFrame(false); + if (!frame) return NS_OK; + nsIFrame* parentFrame = frame->GetParent(); + if (!parentFrame) return NS_OK; + return GetPreviousSibling(parentFrame, frame, aPreviousOrdinalSibling); +} + +nsresult +nsBoxObject::GetPreviousSibling(nsIFrame* aParentFrame, nsIFrame* aFrame, + nsIDOMElement** aResult) +{ + *aResult = nullptr; + nsIFrame* nextFrame = aParentFrame->GetFirstPrincipalChild(); + nsIFrame* prevFrame = nullptr; + while (nextFrame) { + if (nextFrame == aFrame) + break; + prevFrame = nextFrame; + nextFrame = nextFrame->GetNextSibling(); + } + + if (!prevFrame) return NS_OK; + // get the content for the box and query to a dom element + nsCOMPtr<nsIDOMElement> el = do_QueryInterface(prevFrame->GetContent()); + el.swap(*aResult); + return NS_OK; +} + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewBoxObject(nsIBoxObject** aResult) +{ + *aResult = new nsBoxObject; + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} + diff --git a/layout/xul/base/src/nsBoxObject.h b/layout/xul/base/src/nsBoxObject.h new file mode 100644 index 000000000..c93222104 --- /dev/null +++ b/layout/xul/base/src/nsBoxObject.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsBoxObject_h_ +#define nsBoxObject_h_ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIBoxObject.h" +#include "nsPIBoxObject.h" +#include "nsPoint.h" +#include "nsAutoPtr.h" +#include "nsHashKeys.h" +#include "nsInterfaceHashtable.h" +#include "nsCycleCollectionParticipant.h" + +class nsIFrame; +class nsIDocShell; +struct nsIntRect; +class nsIPresShell; + +class nsBoxObject : public nsPIBoxObject +{ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsBoxObject) + NS_DECL_NSIBOXOBJECT + +public: + nsBoxObject(); + virtual ~nsBoxObject(); + + // nsPIBoxObject + virtual nsresult Init(nsIContent* aContent) MOZ_OVERRIDE; + virtual void Clear() MOZ_OVERRIDE; + virtual void ClearCachedValues() MOZ_OVERRIDE; + + nsIFrame* GetFrame(bool aFlushLayout); + nsIPresShell* GetPresShell(bool aFlushLayout); + nsresult GetOffsetRect(nsIntRect& aRect); + nsresult GetScreenPosition(nsIntPoint& aPoint); + + // Given a parent frame and a child frame, find the frame whose + // next sibling is the given child frame and return its element + static nsresult GetPreviousSibling(nsIFrame* aParentFrame, nsIFrame* aFrame, + nsIDOMElement** aResult); + +protected: + + nsAutoPtr<nsInterfaceHashtable<nsStringHashKey,nsISupports> > mPropertyTable; //[OWNER] + + nsIContent* mContent; // [WEAK] +}; + +#endif diff --git a/layout/xul/base/src/nsButtonBoxFrame.cpp b/layout/xul/base/src/nsButtonBoxFrame.cpp new file mode 100644 index 000000000..7c0572f4f --- /dev/null +++ b/layout/xul/base/src/nsButtonBoxFrame.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsButtonBoxFrame.h" +#include "nsIContent.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMXULButtonElement.h" +#include "nsGkAtoms.h" +#include "nsINameSpaceManager.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsGUIEvent.h" +#include "nsEventStateManager.h" +#include "nsIDOMElement.h" +#include "nsDisplayList.h" +#include "nsContentUtils.h" +#include "mozilla/dom/Element.h" + + +// +// NS_NewXULButtonFrame +// +// Creates a new Button frame and returns it +// +nsIFrame* +NS_NewButtonBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsButtonBoxFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsButtonBoxFrame) + +void +nsButtonBoxFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // override, since we don't want children to get events + if (aBuilder->IsForEventDelivery()) + return; + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); +} + +NS_IMETHODIMP +nsButtonBoxFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + switch (aEvent->message) { + case NS_KEY_DOWN: + if (NS_KEY_EVENT == aEvent->eventStructType) { + nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent; + if (NS_VK_SPACE == keyEvent->keyCode) { + nsEventStateManager *esm = aPresContext->EventStateManager(); + // :hover:active state + esm->SetContentState(mContent, NS_EVENT_STATE_HOVER); + esm->SetContentState(mContent, NS_EVENT_STATE_ACTIVE); + } + } + break; + +// On mac, Return fires the defualt button, not the focused one. +#ifndef XP_MACOSX + case NS_KEY_PRESS: + if (NS_KEY_EVENT == aEvent->eventStructType) { + nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent; + if (NS_VK_RETURN == keyEvent->keyCode) { + nsCOMPtr<nsIDOMXULButtonElement> buttonEl(do_QueryInterface(mContent)); + if (buttonEl) { + MouseClicked(aPresContext, aEvent); + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } + } + } + break; +#endif + + case NS_KEY_UP: + if (NS_KEY_EVENT == aEvent->eventStructType) { + nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent; + if (NS_VK_SPACE == keyEvent->keyCode) { + // only activate on keyup if we're already in the :hover:active state + NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?"); + nsEventStates buttonState = mContent->AsElement()->State(); + if (buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE | + NS_EVENT_STATE_HOVER)) { + // return to normal state + nsEventStateManager *esm = aPresContext->EventStateManager(); + esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE); + esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER); + MouseClicked(aPresContext, aEvent); + } + } + } + break; + + case NS_MOUSE_CLICK: + if (NS_IS_MOUSE_LEFT_CLICK(aEvent)) { + MouseClicked(aPresContext, aEvent); + } + break; + } + + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +void +nsButtonBoxFrame::DoMouseClick(nsGUIEvent* aEvent, bool aTrustEvent) +{ + // Don't execute if we're disabled. + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters)) + return; + + // Execute the oncommand event handler. + bool isShift = false; + bool isControl = false; + bool isAlt = false; + bool isMeta = false; + if(aEvent) { + isShift = ((nsInputEvent*)(aEvent))->IsShift(); + isControl = ((nsInputEvent*)(aEvent))->IsControl(); + isAlt = ((nsInputEvent*)(aEvent))->IsAlt(); + isMeta = ((nsInputEvent*)(aEvent))->IsMeta(); + } + + // Have the content handle the event, propagating it according to normal DOM rules. + nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell(); + if (shell) { + nsContentUtils::DispatchXULCommand(mContent, + aEvent ? + aEvent->mFlags.mIsTrusted : aTrustEvent, + nullptr, shell, + isControl, isAlt, isShift, isMeta); + } +} diff --git a/layout/xul/base/src/nsButtonBoxFrame.h b/layout/xul/base/src/nsButtonBoxFrame.h new file mode 100644 index 000000000..5b66473a6 --- /dev/null +++ b/layout/xul/base/src/nsButtonBoxFrame.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsButtonBoxFrame_h___ +#define nsButtonBoxFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +class nsButtonBoxFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewButtonBoxFrame(nsIPresShell* aPresShell); + + nsButtonBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) + :nsBoxFrame(aPresShell, aContext, false) { + UpdateMouseThrough(); + } + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + virtual void MouseClicked (nsPresContext* aPresContext, nsGUIEvent* aEvent) + { DoMouseClick(aEvent, false); } + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE { + return MakeFrameName(NS_LITERAL_STRING("ButtonBoxFrame"), aResult); + } +#endif + + /** + * Our implementation of MouseClicked. + * @param aTrustEvent if true and aEvent as null, then assume the event was trusted + */ + void DoMouseClick(nsGUIEvent* aEvent, bool aTrustEvent); + void UpdateMouseThrough() MOZ_OVERRIDE { AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); } +}; // class nsButtonBoxFrame + +#endif /* nsButtonBoxFrame_h___ */ diff --git a/layout/xul/base/src/nsContainerBoxObject.cpp b/layout/xul/base/src/nsContainerBoxObject.cpp new file mode 100644 index 000000000..2e5e1aa43 --- /dev/null +++ b/layout/xul/base/src/nsContainerBoxObject.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsIContainerBoxObject.h" +#include "nsIBrowserBoxObject.h" +#include "nsIEditorBoxObject.h" +#include "nsIIFrameBoxObject.h" +#include "nsBoxObject.h" +#include "nsIDocShell.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIFrame.h" +#include "nsSubDocumentFrame.h" + +/** + * nsContainerBoxObject implements deprecated nsIBrowserBoxObject, + * nsIEditorBoxObject and nsIIFrameBoxObject interfaces only because of the + * backward compatibility. + */ + +class nsContainerBoxObject : public nsBoxObject, + public nsIBrowserBoxObject, + public nsIEditorBoxObject, + public nsIIFrameBoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICONTAINERBOXOBJECT + NS_DECL_NSIBROWSERBOXOBJECT + NS_DECL_NSIEDITORBOXOBJECT + NS_DECL_NSIIFRAMEBOXOBJECT +}; + +NS_INTERFACE_MAP_BEGIN(nsContainerBoxObject) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIContainerBoxObject, nsIBrowserBoxObject) + NS_INTERFACE_MAP_ENTRY(nsIBrowserBoxObject) + NS_INTERFACE_MAP_ENTRY(nsIEditorBoxObject) + NS_INTERFACE_MAP_ENTRY(nsIIFrameBoxObject) +NS_INTERFACE_MAP_END_INHERITING(nsBoxObject) + +NS_IMPL_ADDREF_INHERITED(nsContainerBoxObject, nsBoxObject) +NS_IMPL_RELEASE_INHERITED(nsContainerBoxObject, nsBoxObject) + +NS_IMETHODIMP nsContainerBoxObject::GetDocShell(nsIDocShell** aResult) +{ + *aResult = nullptr; + + nsIFrame *frame = GetFrame(false); + + if (frame) { + nsSubDocumentFrame *subDocFrame = do_QueryFrame(frame); + if (subDocFrame) { + // Ok, the frame for mContent is an nsSubDocumentFrame, it knows how + // to reach the docshell, so ask it... + + return subDocFrame->GetDocShell(aResult); + } + } + + if (!mContent) { + return NS_OK; + } + + // No nsSubDocumentFrame available for mContent, try if there's a mapping + // between mContent's document to mContent's subdocument. + + // XXXbz sXBL/XBL2 issue -- ownerDocument or currentDocument? + nsIDocument *doc = mContent->GetDocument(); + + if (!doc) { + return NS_OK; + } + + nsIDocument *sub_doc = doc->GetSubDocumentFor(mContent); + + if (!sub_doc) { + return NS_OK; + } + + nsCOMPtr<nsISupports> container = sub_doc->GetContainer(); + + if (!container) { + return NS_OK; + } + + return CallQueryInterface(container, aResult); +} + +nsresult +NS_NewContainerBoxObject(nsIBoxObject** aResult) +{ + *aResult = new nsContainerBoxObject(); + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} + diff --git a/layout/xul/base/src/nsDeckFrame.cpp b/layout/xul/base/src/nsDeckFrame.cpp new file mode 100644 index 000000000..ecf0d1256 --- /dev/null +++ b/layout/xul/base/src/nsDeckFrame.cpp @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsDeckFrame.h" +#include "nsStyleContext.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsCOMPtr.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsHTMLParts.h" +#include "nsIPresShell.h" +#include "nsCSSRendering.h" +#include "nsViewManager.h" +#include "nsBoxLayoutState.h" +#include "nsStackLayout.h" +#include "nsDisplayList.h" +#include "nsContainerFrame.h" + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +nsIFrame* +NS_NewDeckFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsDeckFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsDeckFrame) + +NS_QUERYFRAME_HEAD(nsDeckFrame) + NS_QUERYFRAME_ENTRY(nsDeckFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + + +nsDeckFrame::nsDeckFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) + : nsBoxFrame(aPresShell, aContext), mIndex(0) +{ + nsCOMPtr<nsBoxLayout> layout; + NS_NewStackLayout(aPresShell, layout); + SetLayoutManager(layout); +} + +nsIAtom* +nsDeckFrame::GetType() const +{ + return nsGkAtoms::deckFrame; +} + +NS_IMETHODIMP +nsDeckFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + + + // if the index changed hide the old element and make the new element visible + if (aAttribute == nsGkAtoms::selectedIndex) { + IndexChanged(); + } + + return rv; +} + +void +nsDeckFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + mIndex = GetSelectedIndex(); +} + +void +nsDeckFrame::HideBox(nsIFrame* aBox) +{ + nsIPresShell::ClearMouseCapture(aBox); +} + +void +nsDeckFrame::IndexChanged() +{ + //did the index change? + int32_t index = GetSelectedIndex(); + if (index == mIndex) + return; + + // redraw + InvalidateFrame(); + + // hide the currently showing box + nsIFrame* currentBox = GetSelectedBox(); + if (currentBox) // only hide if it exists + HideBox(currentBox); + + mIndex = index; + +#ifdef ACCESSIBILITY + nsAccessibilityService* accService = GetAccService(); + if (accService) { + accService->DeckPanelSwitched(PresContext()->GetPresShell(), mContent, + currentBox, GetSelectedBox()); + } +#endif +} + +int32_t +nsDeckFrame::GetSelectedIndex() +{ + // default index is 0 + int32_t index = 0; + + // get the index attribute + nsAutoString value; + if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::selectedIndex, value)) + { + nsresult error; + + // convert it to an integer + index = value.ToInteger(&error); + } + + return index; +} + +nsIFrame* +nsDeckFrame::GetSelectedBox() +{ + return (mIndex >= 0) ? mFrames.FrameAt(mIndex) : nullptr; +} + +void +nsDeckFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // if a tab is hidden all its children are too. + if (!StyleVisibility()->mVisible) + return; + + nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); +} + +void +nsDeckFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // only paint the selected box + nsIFrame* box = GetSelectedBox(); + if (!box) + return; + + // Putting the child in the background list. This is a little weird but + // it matches what we were doing before. + nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds()); + BuildDisplayListForChild(aBuilder, box, aDirtyRect, set); +} + +NS_IMETHODIMP +nsDeckFrame::DoLayout(nsBoxLayoutState& aState) +{ + // Make sure we tweak the state so it does not resize our children. + // We will do that. + uint32_t oldFlags = aState.LayoutFlags(); + aState.SetLayoutFlags(NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_VISIBILITY); + + // do a normal layout + nsresult rv = nsBoxFrame::DoLayout(aState); + + // run though each child. Hide all but the selected one + nsIFrame* box = GetChildBox(); + + nscoord count = 0; + while (box) + { + // make collapsed children not show up + if (count != mIndex) + HideBox(box); + + box = box->GetNextBox(); + count++; + } + + aState.SetLayoutFlags(oldFlags); + + return rv; +} + diff --git a/layout/xul/base/src/nsDeckFrame.h b/layout/xul/base/src/nsDeckFrame.h new file mode 100644 index 000000000..f0c6f7d54 --- /dev/null +++ b/layout/xul/base/src/nsDeckFrame.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Eric D Vaughan + A frame that can have multiple children. Only one child may be displayed at one time. So the + can be flipped though like a deck of cards. + +**/ + +#ifndef nsDeckFrame_h___ +#define nsDeckFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +class nsDeckFrame : public nsBoxFrame +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsDeckFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewDeckFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + NS_IMETHOD DoLayout(nsBoxLayoutState& aState) MOZ_OVERRIDE; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + + virtual nsIAtom* GetType() const MOZ_OVERRIDE; + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE + { + return MakeFrameName(NS_LITERAL_STRING("Deck"), aResult); + } +#endif + + nsDeckFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + nsIFrame* GetSelectedBox(); + +protected: + + void IndexChanged(); + int32_t GetSelectedIndex(); + void HideBox(nsIFrame* aBox); + +private: + + int32_t mIndex; + +}; // class nsDeckFrame + +#endif + diff --git a/layout/xul/base/src/nsDocElementBoxFrame.cpp b/layout/xul/base/src/nsDocElementBoxFrame.cpp new file mode 100644 index 000000000..88bd4f834 --- /dev/null +++ b/layout/xul/base/src/nsDocElementBoxFrame.cpp @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsHTMLParts.h" +#include "nsContainerFrame.h" +#include "nsCSSRendering.h" +#include "nsIDocument.h" +#include "nsPageFrame.h" +#include "nsGUIEvent.h" +#include "nsIDOMEvent.h" +#include "nsStyleConsts.h" +#include "nsGkAtoms.h" +#include "nsIPresShell.h" +#include "nsBoxFrame.h" +#include "nsStackLayout.h" +#include "nsIAnonymousContentCreator.h" +#include "nsINodeInfo.h" +#include "nsIServiceManager.h" +#include "nsNodeInfoManager.h" +#include "nsContentCreatorFunctions.h" +#include "nsContentUtils.h" +#include "nsContentList.h" + +//#define DEBUG_REFLOW + +class nsDocElementBoxFrame : public nsBoxFrame, + public nsIAnonymousContentCreator +{ +public: + virtual void DestroyFrom(nsIFrame* aDestructRoot); + + friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + nsDocElementBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext) + :nsBoxFrame(aShell, aContext, true) {} + + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // nsIAnonymousContentCreator + virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements); + virtual void AppendAnonymousContentTo(nsBaseContentList& aElements, + uint32_t aFilter); + + virtual bool IsFrameOfType(uint32_t aFlags) const + { + // Override nsBoxFrame. + if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced)) + return false; + return nsBoxFrame::IsFrameOfType(aFlags); + } + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const; +#endif +private: + nsCOMPtr<nsIContent> mPopupgroupContent; + nsCOMPtr<nsIContent> mTooltipContent; +}; + +//---------------------------------------------------------------------- + +nsIFrame* +NS_NewDocElementBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsDocElementBoxFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsDocElementBoxFrame) + +void +nsDocElementBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + nsContentUtils::DestroyAnonymousContent(&mPopupgroupContent); + nsContentUtils::DestroyAnonymousContent(&mTooltipContent); + nsBoxFrame::DestroyFrom(aDestructRoot); +} + +nsresult +nsDocElementBoxFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) +{ + nsIDocument* doc = mContent->GetDocument(); + if (!doc) { + // The page is currently being torn down. Why bother. + return NS_ERROR_FAILURE; + } + nsNodeInfoManager *nodeInfoManager = doc->NodeInfoManager(); + + // create the top-secret popupgroup node. shhhhh! + nsCOMPtr<nsINodeInfo> nodeInfo; + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::popupgroup, + nullptr, kNameSpaceID_XUL, + nsIDOMNode::ELEMENT_NODE); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = NS_NewXULElement(getter_AddRefs(mPopupgroupContent), + nodeInfo.forget()); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aElements.AppendElement(mPopupgroupContent)) + return NS_ERROR_OUT_OF_MEMORY; + + // create the top-secret default tooltip node. shhhhh! + nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::tooltip, nullptr, + kNameSpaceID_XUL, + nsIDOMNode::ELEMENT_NODE); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); + + rv = NS_NewXULElement(getter_AddRefs(mTooltipContent), nodeInfo.forget()); + NS_ENSURE_SUCCESS(rv, rv); + + mTooltipContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_default, + NS_LITERAL_STRING("true"), false); + + if (!aElements.AppendElement(mTooltipContent)) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +void +nsDocElementBoxFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, + uint32_t aFilter) +{ + aElements.MaybeAppendElement(mPopupgroupContent); + aElements.MaybeAppendElement(mTooltipContent); +} + +NS_QUERYFRAME_HEAD(nsDocElementBoxFrame) + NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +#ifdef DEBUG +NS_IMETHODIMP +nsDocElementBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("DocElementBox"), aResult); +} +#endif diff --git a/layout/xul/base/src/nsGroupBoxFrame.cpp b/layout/xul/base/src/nsGroupBoxFrame.cpp new file mode 100644 index 000000000..159ca121c --- /dev/null +++ b/layout/xul/base/src/nsGroupBoxFrame.cpp @@ -0,0 +1,244 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// YY need to pass isMultiple before create called + +#include "nsBoxFrame.h" +#include "nsCSSRendering.h" +#include "nsRenderingContext.h" +#include "nsStyleContext.h" +#include "nsDisplayList.h" + +class nsGroupBoxFrame : public nsBoxFrame { +public: + NS_DECL_FRAMEARENA_HELPERS + + nsGroupBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsBoxFrame(aShell, aContext) {} + + NS_IMETHOD GetBorderAndPadding(nsMargin& aBorderAndPadding); + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const { + return MakeFrameName(NS_LITERAL_STRING("GroupBoxFrame"), aResult); + } +#endif + + virtual bool HonorPrintBackgroundSettings() { return false; } + + void PaintBorderBackground(nsRenderingContext& aRenderingContext, + nsPoint aPt, const nsRect& aDirtyRect); + + // make sure we our kids get our orient and align instead of us. + // our child box has no content node so it will search for a parent with one. + // that will be us. + virtual void GetInitialOrientation(bool& aHorizontal) { aHorizontal = false; } + virtual bool GetInitialHAlignment(Halignment& aHalign) { aHalign = hAlign_Left; return true; } + virtual bool GetInitialVAlignment(Valignment& aValign) { aValign = vAlign_Top; return true; } + virtual bool GetInitialAutoStretch(bool& aStretch) { aStretch = true; return true; } + + nsIFrame* GetCaptionBox(nsPresContext* aPresContext, nsRect& aCaptionRect); +}; + +/* +class nsGroupBoxInnerFrame : public nsBoxFrame { +public: + + nsGroupBoxInnerFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsBoxFrame(aShell, aContext) {} + + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsString& aResult) const { + return MakeFrameName("GroupBoxFrameInner", aResult); + } +#endif + + // we are always flexible + virtual bool GetDefaultFlex(int32_t& aFlex) { aFlex = 1; return true; } + +}; +*/ + +nsIFrame* +NS_NewGroupBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsGroupBoxFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsGroupBoxFrame) + +class nsDisplayXULGroupBackground : public nsDisplayItem { +public: + nsDisplayXULGroupBackground(nsDisplayListBuilder* aBuilder, + nsGroupBoxFrame* aFrame) : + nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayXULGroupBackground); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayXULGroupBackground() { + MOZ_COUNT_DTOR(nsDisplayXULGroupBackground); + } +#endif + + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) { + aOutFrames->AppendElement(mFrame); + } + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx); + NS_DISPLAY_DECL_NAME("XULGroupBackground", TYPE_XUL_GROUP_BACKGROUND) +}; + +void +nsDisplayXULGroupBackground::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + static_cast<nsGroupBoxFrame*>(mFrame)-> + PaintBorderBackground(*aCtx, ToReferenceFrame(), mVisibleRect); +} + +void +nsGroupBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // Paint our background and border + if (IsVisibleForPainting(aBuilder)) { + aLists.BorderBackground()->AppendNewToTop(new (aBuilder) + nsDisplayXULGroupBackground(aBuilder, this)); + + DisplayOutline(aBuilder, aLists); + } + + BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); +} + +void +nsGroupBoxFrame::PaintBorderBackground(nsRenderingContext& aRenderingContext, + nsPoint aPt, const nsRect& aDirtyRect) { + int skipSides = 0; + const nsStyleBorder* borderStyleData = StyleBorder(); + const nsMargin& border = borderStyleData->GetComputedBorder(); + nscoord yoff = 0; + nsPresContext* presContext = PresContext(); + + nsRect groupRect; + nsIFrame* groupBox = GetCaptionBox(presContext, groupRect); + + if (groupBox) { + // if the border is smaller than the legend. Move the border down + // to be centered on the legend. + nsMargin groupMargin; + groupBox->StyleMargin()->GetMargin(groupMargin); + groupRect.Inflate(groupMargin); + + if (border.top < groupRect.height) + yoff = (groupRect.height - border.top)/2 + groupRect.y; + } + + nsRect rect(aPt.x, aPt.y + yoff, mRect.width, mRect.height - yoff); + + groupRect += aPt; + + nsCSSRendering::PaintBackground(presContext, aRenderingContext, this, + aDirtyRect, rect, + nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES); + + if (groupBox) { + + // we should probably use PaintBorderEdges to do this but for now just use clipping + // to achieve the same effect. + + // draw left side + nsRect clipRect(rect); + clipRect.width = groupRect.x - rect.x; + clipRect.height = border.top; + + aRenderingContext.PushState(); + aRenderingContext.IntersectClip(clipRect); + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, rect, mStyleContext, skipSides); + + aRenderingContext.PopState(); + + + // draw right side + clipRect = rect; + clipRect.x = groupRect.XMost(); + clipRect.width = rect.XMost() - groupRect.XMost(); + clipRect.height = border.top; + + aRenderingContext.PushState(); + aRenderingContext.IntersectClip(clipRect); + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, rect, mStyleContext, skipSides); + + aRenderingContext.PopState(); + + + + // draw bottom + + clipRect = rect; + clipRect.y += border.top; + clipRect.height = mRect.height - (yoff + border.top); + + aRenderingContext.PushState(); + aRenderingContext.IntersectClip(clipRect); + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, rect, mStyleContext, skipSides); + + aRenderingContext.PopState(); + + } else { + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, nsRect(aPt, GetSize()), + mStyleContext, skipSides); + } +} + +nsIFrame* +nsGroupBoxFrame::GetCaptionBox(nsPresContext* aPresContext, nsRect& aCaptionRect) +{ + // first child is our grouped area + nsIFrame* box = GetChildBox(); + + // no area fail. + if (!box) + return nullptr; + + // get the first child in the grouped area, that is the caption + box = box->GetChildBox(); + + // nothing in the area? fail + if (!box) + return nullptr; + + // now get the caption itself. It is in the caption frame. + nsIFrame* child = box->GetChildBox(); + + if (child) { + // convert to our coordinates. + nsRect parentRect(box->GetRect()); + aCaptionRect = child->GetRect(); + aCaptionRect.x += parentRect.x; + aCaptionRect.y += parentRect.y; + } + + return child; +} + +NS_IMETHODIMP +nsGroupBoxFrame::GetBorderAndPadding(nsMargin& aBorderAndPadding) +{ + aBorderAndPadding.SizeTo(0,0,0,0); + return NS_OK; +} + diff --git a/layout/xul/base/src/nsIRootBox.h b/layout/xul/base/src/nsIRootBox.h new file mode 100644 index 000000000..9311f547e --- /dev/null +++ b/layout/xul/base/src/nsIRootBox.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifndef nsIRootBox_h___ +#define nsIRootBox_h___ + +#include "nsQueryFrame.h" +class nsPopupSetFrame; +class nsIContent; +class nsIPresShell; + +class nsIRootBox +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsIRootBox) + + virtual nsPopupSetFrame* GetPopupSetFrame() = 0; + virtual void SetPopupSetFrame(nsPopupSetFrame* aPopupSet) = 0; + + virtual nsIContent* GetDefaultTooltip() = 0; + virtual void SetDefaultTooltip(nsIContent* aTooltip) = 0; + + virtual nsresult AddTooltipSupport(nsIContent* aNode) = 0; + virtual nsresult RemoveTooltipSupport(nsIContent* aNode) = 0; + + static nsIRootBox* GetRootBox(nsIPresShell* aShell); +}; + +#endif + diff --git a/layout/xul/base/src/nsImageBoxFrame.cpp b/layout/xul/base/src/nsImageBoxFrame.cpp new file mode 100644 index 000000000..4f707bb71 --- /dev/null +++ b/layout/xul/base/src/nsImageBoxFrame.cpp @@ -0,0 +1,746 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsImageBoxFrame.h" +#include "nsGkAtoms.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsCOMPtr.h" +#include "nsPresContext.h" +#include "nsBoxLayoutState.h" + +#include "nsHTMLParts.h" +#include "nsString.h" +#include "nsLeafFrame.h" +#include "nsIPresShell.h" +#include "nsIDocument.h" +#include "nsImageMap.h" +#include "nsILinkHandler.h" +#include "nsIURL.h" +#include "nsILoadGroup.h" +#include "nsContainerFrame.h" +#include "prprf.h" +#include "nsCSSRendering.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsINameSpaceManager.h" +#include "nsTextFragment.h" +#include "nsIDOMHTMLMapElement.h" +#include "nsTransform2D.h" +#include "nsITheme.h" + +#include "nsIServiceManager.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "nsGUIEvent.h" +#include "nsEventDispatcher.h" +#include "nsDisplayList.h" +#include "ImageLayers.h" +#include "ImageContainer.h" + +#include "nsContentUtils.h" + +#define ONLOAD_CALLED_TOO_EARLY 1 + +using namespace mozilla::layers; + +class nsImageBoxFrameEvent : public nsRunnable +{ +public: + nsImageBoxFrameEvent(nsIContent *content, uint32_t message) + : mContent(content), mMessage(message) {} + + NS_IMETHOD Run(); + +private: + nsCOMPtr<nsIContent> mContent; + uint32_t mMessage; +}; + +NS_IMETHODIMP +nsImageBoxFrameEvent::Run() +{ + nsIPresShell *pres_shell = mContent->OwnerDoc()->GetShell(); + if (!pres_shell) { + return NS_OK; + } + + nsRefPtr<nsPresContext> pres_context = pres_shell->GetPresContext(); + if (!pres_context) { + return NS_OK; + } + + nsEventStatus status = nsEventStatus_eIgnore; + nsEvent event(true, mMessage); + + event.mFlags.mBubbles = false; + nsEventDispatcher::Dispatch(mContent, pres_context, &event, nullptr, &status); + return NS_OK; +} + +// Fire off an event that'll asynchronously call the image elements +// onload handler once handled. This is needed since the image library +// can't decide if it wants to call it's observer methods +// synchronously or asynchronously. If an image is loaded from the +// cache the notifications come back synchronously, but if the image +// is loaded from the netswork the notifications come back +// asynchronously. + +void +FireImageDOMEvent(nsIContent* aContent, uint32_t aMessage) +{ + NS_ASSERTION(aMessage == NS_LOAD || aMessage == NS_LOAD_ERROR, + "invalid message"); + + nsCOMPtr<nsIRunnable> event = new nsImageBoxFrameEvent(aContent, aMessage); + if (NS_FAILED(NS_DispatchToCurrentThread(event))) + NS_WARNING("failed to dispatch image event"); +} + +// +// NS_NewImageBoxFrame +// +// Creates a new image frame and returns it +// +nsIFrame* +NS_NewImageBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsImageBoxFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsImageBoxFrame) + +NS_IMETHODIMP +nsImageBoxFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsLeafBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + + if (aAttribute == nsGkAtoms::src) { + UpdateImage(); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + else if (aAttribute == nsGkAtoms::validate) + UpdateLoadFlags(); + + return rv; +} + +nsImageBoxFrame::nsImageBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsLeafBoxFrame(aShell, aContext), + mIntrinsicSize(0,0), + mRequestRegistered(false), + mLoadFlags(nsIRequest::LOAD_NORMAL), + mUseSrcAttr(false), + mSuppressStyleCheck(false), + mFireEventOnDecode(false) +{ + MarkIntrinsicWidthsDirty(); +} + +nsImageBoxFrame::~nsImageBoxFrame() +{ +} + + +/* virtual */ void +nsImageBoxFrame::MarkIntrinsicWidthsDirty() +{ + SizeNeedsRecalc(mImageSize); + nsLeafBoxFrame::MarkIntrinsicWidthsDirty(); +} + +void +nsImageBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (mImageRequest) { + nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest, + &mRequestRegistered); + + // Release image loader first so that it's refcnt can go to zero + mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); + } + + if (mListener) + reinterpret_cast<nsImageBoxListener*>(mListener.get())->SetFrame(nullptr); // set the frame to null so we don't send messages to a dead object. + + nsLeafBoxFrame::DestroyFrom(aDestructRoot); +} + + +void +nsImageBoxFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + if (!mListener) { + nsImageBoxListener *listener = new nsImageBoxListener(); + NS_ADDREF(listener); + listener->SetFrame(this); + listener->QueryInterface(NS_GET_IID(imgINotificationObserver), getter_AddRefs(mListener)); + NS_RELEASE(listener); + } + + mSuppressStyleCheck = true; + nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow); + mSuppressStyleCheck = false; + + UpdateLoadFlags(); + UpdateImage(); +} + +void +nsImageBoxFrame::UpdateImage() +{ + nsPresContext* presContext = PresContext(); + + if (mImageRequest) { + nsLayoutUtils::DeregisterImageRequest(presContext, mImageRequest, + &mRequestRegistered); + mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); + mImageRequest = nullptr; + } + + // get the new image src + nsAutoString src; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src); + mUseSrcAttr = !src.IsEmpty(); + if (mUseSrcAttr) { + nsIDocument* doc = mContent->GetDocument(); + if (!doc) { + // No need to do anything here... + return; + } + nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI(); + nsCOMPtr<nsIURI> uri; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), + src, + doc, + baseURI); + + if (uri && nsContentUtils::CanLoadImage(uri, mContent, doc, + mContent->NodePrincipal())) { + nsContentUtils::LoadImage(uri, doc, mContent->NodePrincipal(), + doc->GetDocumentURI(), mListener, mLoadFlags, + getter_AddRefs(mImageRequest)); + + if (mImageRequest) { + nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, + mImageRequest, + &mRequestRegistered); + } + } + } else { + // Only get the list-style-image if we aren't being drawn + // by a native theme. + uint8_t appearance = StyleDisplay()->mAppearance; + if (!(appearance && nsBox::gTheme && + nsBox::gTheme->ThemeSupportsWidget(nullptr, this, appearance))) { + // get the list-style-image + imgRequestProxy *styleRequest = StyleList()->GetListStyleImage(); + if (styleRequest) { + styleRequest->Clone(mListener, getter_AddRefs(mImageRequest)); + } + } + } + + if (!mImageRequest) { + // We have no image, so size to 0 + mIntrinsicSize.SizeTo(0, 0); + } else { + // We don't want discarding or decode-on-draw for xul images. + mImageRequest->StartDecoding(); + mImageRequest->LockImage(); + } +} + +void +nsImageBoxFrame::UpdateLoadFlags() +{ + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::always, &nsGkAtoms::never, nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::validate, + strings, eCaseMatters)) { + case 0: + mLoadFlags = nsIRequest::VALIDATE_ALWAYS; + break; + case 1: + mLoadFlags = nsIRequest::VALIDATE_NEVER|nsIRequest::LOAD_FROM_CACHE; + break; + default: + mLoadFlags = nsIRequest::LOAD_NORMAL; + break; + } +} + +void +nsImageBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); + + if ((0 == mRect.width) || (0 == mRect.height)) { + // Do not render when given a zero area. This avoids some useless + // scaling work while we wait for our image dimensions to arrive + // asynchronously. + return; + } + + if (!IsVisibleForPainting(aBuilder)) + return; + + nsDisplayList list; + list.AppendNewToTop( + new (aBuilder) nsDisplayXULImage(aBuilder, this)); + + CreateOwnLayerIfNeeded(aBuilder, &list); + + aLists.Content()->AppendToTop(&list); +} + +void +nsImageBoxFrame::PaintImage(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt, + uint32_t aFlags) +{ + nsRect rect; + GetClientRect(rect); + + rect += aPt; + + if (!mImageRequest) + return; + + // don't draw if the image is not dirty + nsRect dirty; + if (!dirty.IntersectRect(aDirtyRect, rect)) + return; + + nsCOMPtr<imgIContainer> imgCon; + mImageRequest->GetImage(getter_AddRefs(imgCon)); + + if (imgCon) { + bool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0); + nsLayoutUtils::DrawSingleImage(&aRenderingContext, imgCon, + nsLayoutUtils::GetGraphicsFilterForFrame(this), + rect, dirty, nullptr, aFlags, hasSubRect ? &mSubRect : nullptr); + } +} + +void nsDisplayXULImage::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + uint32_t flags = imgIContainer::FLAG_NONE; + if (aBuilder->ShouldSyncDecodeImages()) + flags |= imgIContainer::FLAG_SYNC_DECODE; + if (aBuilder->IsPaintingToWindow()) + flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + + static_cast<nsImageBoxFrame*>(mFrame)-> + PaintImage(*aCtx, mVisibleRect, ToReferenceFrame(), flags); +} + +void +nsDisplayXULImage::ConfigureLayer(ImageLayer* aLayer, const nsIntPoint& aOffset) +{ + aLayer->SetFilter(nsLayoutUtils::GetGraphicsFilterForFrame(mFrame)); + + int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsImageBoxFrame* imageFrame = static_cast<nsImageBoxFrame*>(mFrame); + + nsRect dest; + imageFrame->GetClientRect(dest); + dest += ToReferenceFrame(); + gfxRect destRect(dest.x, dest.y, dest.width, dest.height); + destRect.ScaleInverse(factor); + + nsCOMPtr<imgIContainer> imgCon; + imageFrame->mImageRequest->GetImage(getter_AddRefs(imgCon)); + int32_t imageWidth; + int32_t imageHeight; + imgCon->GetWidth(&imageWidth); + imgCon->GetHeight(&imageHeight); + + NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); + + gfxMatrix transform; + transform.Translate(destRect.TopLeft() + aOffset); + transform.Scale(destRect.Width()/imageWidth, + destRect.Height()/imageHeight); + aLayer->SetBaseTransform(gfx3DMatrix::From2D(transform)); + + aLayer->SetVisibleRegion(nsIntRect(0, 0, imageWidth, imageHeight)); +} + +already_AddRefed<ImageContainer> +nsDisplayXULImage::GetContainer(LayerManager* aManager, nsDisplayListBuilder* aBuilder) +{ + return static_cast<nsImageBoxFrame*>(mFrame)->GetContainer(aManager); +} + +already_AddRefed<ImageContainer> +nsImageBoxFrame::GetContainer(LayerManager* aManager) +{ + bool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0); + if (hasSubRect || !mImageRequest) { + return nullptr; + } + + nsCOMPtr<imgIContainer> imgCon; + mImageRequest->GetImage(getter_AddRefs(imgCon)); + if (!imgCon) { + return nullptr; + } + + nsRefPtr<ImageContainer> container; + nsresult rv = imgCon->GetImageContainer(aManager, getter_AddRefs(container)); + NS_ENSURE_SUCCESS(rv, nullptr); + return container.forget(); +} + + +// +// DidSetStyleContext +// +// When the style context changes, make sure that all of our image is up to date. +// +/* virtual */ void +nsImageBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext); + + // Fetch our subrect. + const nsStyleList* myList = StyleList(); + mSubRect = myList->mImageRegion; // before |mSuppressStyleCheck| test! + + if (mUseSrcAttr || mSuppressStyleCheck) + return; // No more work required, since the image isn't specified by style. + + // If we're using a native theme implementation, we shouldn't draw anything. + const nsStyleDisplay* disp = StyleDisplay(); + if (disp->mAppearance && nsBox::gTheme && + nsBox::gTheme->ThemeSupportsWidget(nullptr, this, disp->mAppearance)) + return; + + // If list-style-image changes, we have a new image. + nsCOMPtr<nsIURI> oldURI, newURI; + if (mImageRequest) + mImageRequest->GetURI(getter_AddRefs(oldURI)); + if (myList->GetListStyleImage()) + myList->GetListStyleImage()->GetURI(getter_AddRefs(newURI)); + bool equal; + if (newURI == oldURI || // handles null==null + (newURI && oldURI && + NS_SUCCEEDED(newURI->Equals(oldURI, &equal)) && equal)) + return; + + UpdateImage(); +} // DidSetStyleContext + +void +nsImageBoxFrame::GetImageSize() +{ + if (mIntrinsicSize.width > 0 && mIntrinsicSize.height > 0) { + mImageSize.width = mIntrinsicSize.width; + mImageSize.height = mIntrinsicSize.height; + } else { + mImageSize.width = 0; + mImageSize.height = 0; + } +} + +/** + * Ok return our dimensions + */ +nsSize +nsImageBoxFrame::GetPrefSize(nsBoxLayoutState& aState) +{ + nsSize size(0,0); + DISPLAY_PREF_SIZE(this, size); + if (DoesNeedRecalc(mImageSize)) + GetImageSize(); + + if (!mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0)) + size = nsSize(mSubRect.width, mSubRect.height); + else + size = mImageSize; + + nsSize intrinsicSize = size; + + nsMargin borderPadding(0,0,0,0); + GetBorderAndPadding(borderPadding); + size.width += borderPadding.LeftRight(); + size.height += borderPadding.TopBottom(); + + bool widthSet, heightSet; + nsIFrame::AddCSSPrefSize(this, size, widthSet, heightSet); + NS_ASSERTION(size.width != NS_INTRINSICSIZE && size.height != NS_INTRINSICSIZE, + "non-intrinsic size expected"); + + nsSize minSize = GetMinSize(aState); + nsSize maxSize = GetMaxSize(aState); + + if (!widthSet && !heightSet) { + if (minSize.width != NS_INTRINSICSIZE) + minSize.width -= borderPadding.LeftRight(); + if (minSize.height != NS_INTRINSICSIZE) + minSize.height -= borderPadding.TopBottom(); + if (maxSize.width != NS_INTRINSICSIZE) + maxSize.width -= borderPadding.LeftRight(); + if (maxSize.height != NS_INTRINSICSIZE) + maxSize.height -= borderPadding.TopBottom(); + + size = nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(minSize.width, minSize.height, + maxSize.width, maxSize.height, + intrinsicSize.width, intrinsicSize.height); + NS_ASSERTION(size.width != NS_INTRINSICSIZE && size.height != NS_INTRINSICSIZE, + "non-intrinsic size expected"); + size.width += borderPadding.LeftRight(); + size.height += borderPadding.TopBottom(); + return size; + } + + if (!widthSet) { + if (intrinsicSize.height > 0) { + // Subtract off the border and padding from the height because the + // content-box needs to be used to determine the ratio + nscoord height = size.height - borderPadding.TopBottom(); + size.width = nscoord(int64_t(height) * int64_t(intrinsicSize.width) / + int64_t(intrinsicSize.height)); + } + else { + size.width = intrinsicSize.width; + } + + size.width += borderPadding.LeftRight(); + } + else if (!heightSet) { + if (intrinsicSize.width > 0) { + nscoord width = size.width - borderPadding.LeftRight(); + size.height = nscoord(int64_t(width) * int64_t(intrinsicSize.height) / + int64_t(intrinsicSize.width)); + } + else { + size.height = intrinsicSize.height; + } + + size.height += borderPadding.TopBottom(); + } + + return BoundsCheck(minSize, size, maxSize); +} + +nsSize +nsImageBoxFrame::GetMinSize(nsBoxLayoutState& aState) +{ + // An image can always scale down to (0,0). + nsSize size(0,0); + DISPLAY_MIN_SIZE(this, size); + AddBorderAndPadding(size); + bool widthSet, heightSet; + nsIFrame::AddCSSMinSize(aState, this, size, widthSet, heightSet); + return size; +} + +nscoord +nsImageBoxFrame::GetBoxAscent(nsBoxLayoutState& aState) +{ + return GetPrefSize(aState).height; +} + +nsIAtom* +nsImageBoxFrame::GetType() const +{ + return nsGkAtoms::imageBoxFrame; +} + +#ifdef DEBUG +NS_IMETHODIMP +nsImageBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("ImageBox"), aResult); +} +#endif + +nsresult +nsImageBoxFrame::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) +{ + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + nsCOMPtr<imgIContainer> image; + aRequest->GetImage(getter_AddRefs(image)); + return OnStartContainer(aRequest, image); + } + + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + return OnStopDecode(aRequest); + } + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + uint32_t imgStatus; + aRequest->GetImageStatus(&imgStatus); + nsresult status = + imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; + return OnStopRequest(aRequest, status); + } + + if (aType == imgINotificationObserver::IS_ANIMATED) { + return OnImageIsAnimated(aRequest); + } + + if (aType == imgINotificationObserver::FRAME_UPDATE) { + return FrameChanged(aRequest); + } + + return NS_OK; +} + +nsresult nsImageBoxFrame::OnStartContainer(imgIRequest *request, + imgIContainer *image) +{ + NS_ENSURE_ARG_POINTER(image); + + // Ensure the animation (if any) is started. Note: There is no + // corresponding call to Decrement for this. This Increment will be + // 'cleaned up' by the Request when it is destroyed, but only then. + request->IncrementAnimationConsumers(); + + nscoord w, h; + image->GetWidth(&w); + image->GetHeight(&h); + + mIntrinsicSize.SizeTo(nsPresContext::CSSPixelsToAppUnits(w), + nsPresContext::CSSPixelsToAppUnits(h)); + + if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + + return NS_OK; +} + +nsresult nsImageBoxFrame::OnStopDecode(imgIRequest *request) +{ + if (mFireEventOnDecode) { + mFireEventOnDecode = false; + + uint32_t reqStatus; + request->GetImageStatus(&reqStatus); + if (!(reqStatus & imgIRequest::STATUS_ERROR)) { + FireImageDOMEvent(mContent, NS_LOAD); + } else { + // Fire an onerror DOM event. + mIntrinsicSize.SizeTo(0, 0); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + FireImageDOMEvent(mContent, NS_LOAD_ERROR); + } + } + + nsBoxLayoutState state(PresContext()); + this->Redraw(state); + + return NS_OK; +} + +nsresult nsImageBoxFrame::OnStopRequest(imgIRequest *request, + nsresult aStatus) +{ + uint32_t reqStatus; + request->GetImageStatus(&reqStatus); + + // We want to give the decoder a chance to find errors. If we haven't found + // an error yet and we've already started decoding, we must only fire these + // events after we finish decoding. + if (NS_SUCCEEDED(aStatus) && !(reqStatus & imgIRequest::STATUS_ERROR) && + reqStatus & imgIRequest::STATUS_DECODE_STARTED) { + mFireEventOnDecode = true; + } else { + if (NS_SUCCEEDED(aStatus)) { + // Fire an onload DOM event. + FireImageDOMEvent(mContent, NS_LOAD); + } else { + // Fire an onerror DOM event. + mIntrinsicSize.SizeTo(0, 0); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + FireImageDOMEvent(mContent, NS_LOAD_ERROR); + } + } + + return NS_OK; +} + +nsresult nsImageBoxFrame::OnImageIsAnimated(imgIRequest *aRequest) +{ + // Register with our refresh driver, if we're animated. + nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest, + &mRequestRegistered); + + return NS_OK; +} + +nsresult nsImageBoxFrame::FrameChanged(imgIRequest *aRequest) +{ + if ((0 == mRect.width) || (0 == mRect.height)) { + return NS_OK; + } + + InvalidateLayer(nsDisplayItem::TYPE_XUL_IMAGE); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS2(nsImageBoxListener, imgINotificationObserver, imgIOnloadBlocker) + +nsImageBoxListener::nsImageBoxListener() +{ +} + +nsImageBoxListener::~nsImageBoxListener() +{ +} + +NS_IMETHODIMP +nsImageBoxListener::Notify(imgIRequest *request, int32_t aType, const nsIntRect* aData) +{ + if (!mFrame) + return NS_OK; + + return mFrame->Notify(request, aType, aData); +} + +/* void blockOnload (in imgIRequest aRequest); */ +NS_IMETHODIMP +nsImageBoxListener::BlockOnload(imgIRequest *aRequest) +{ + if (mFrame && mFrame->GetContent() && mFrame->GetContent()->GetCurrentDoc()) { + mFrame->GetContent()->GetCurrentDoc()->BlockOnload(); + } + + return NS_OK; +} + +/* void unblockOnload (in imgIRequest aRequest); */ +NS_IMETHODIMP +nsImageBoxListener::UnblockOnload(imgIRequest *aRequest) +{ + if (mFrame && mFrame->GetContent() && mFrame->GetContent()->GetCurrentDoc()) { + mFrame->GetContent()->GetCurrentDoc()->UnblockOnload(false); + } + + return NS_OK; +} diff --git a/layout/xul/base/src/nsImageBoxFrame.h b/layout/xul/base/src/nsImageBoxFrame.h new file mode 100644 index 000000000..0f4e5c5ca --- /dev/null +++ b/layout/xul/base/src/nsImageBoxFrame.h @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsImageBoxFrame_h___ +#define nsImageBoxFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsLeafBoxFrame.h" + +#include "imgILoader.h" +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "imgINotificationObserver.h" +#include "imgIOnloadBlocker.h" + +class imgRequestProxy; +class nsImageBoxFrame; + +class nsDisplayXULImage; + +class nsImageBoxListener : public imgINotificationObserver, + public imgIOnloadBlocker +{ +public: + nsImageBoxListener(); + virtual ~nsImageBoxListener(); + + NS_DECL_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + NS_DECL_IMGIONLOADBLOCKER + + void SetFrame(nsImageBoxFrame *frame) { mFrame = frame; } + +private: + nsImageBoxFrame *mFrame; +}; + +class nsImageBoxFrame : public nsLeafBoxFrame +{ +public: + typedef mozilla::layers::LayerManager LayerManager; + + friend class nsDisplayXULImage; + NS_DECL_FRAMEARENA_HELPERS + + virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nscoord GetBoxAscent(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void MarkIntrinsicWidthsDirty() MOZ_OVERRIDE; + + nsresult Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData); + + friend nsIFrame* NS_NewImageBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* asPrevInFlow) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) MOZ_OVERRIDE; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + virtual nsIAtom* GetType() const MOZ_OVERRIDE; +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE; +#endif + + /** + * Update mUseSrcAttr from appropriate content attributes or from + * style, throw away the current image, and load the appropriate + * image. + * */ + void UpdateImage(); + + /** + * Update mLoadFlags from content attributes. Does not attempt to reload the + * image using the new load flags. + */ + void UpdateLoadFlags(); + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + virtual ~nsImageBoxFrame(); + + void PaintImage(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, uint32_t aFlags); + + already_AddRefed<mozilla::layers::ImageContainer> GetContainer(LayerManager* aManager); +protected: + nsImageBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext); + + virtual void GetImageSize(); + +private: + nsresult OnStartContainer(imgIRequest *request, imgIContainer *image); + nsresult OnStopDecode(imgIRequest *request); + nsresult OnStopRequest(imgIRequest *request, nsresult status); + nsresult OnImageIsAnimated(imgIRequest* aRequest); + nsresult FrameChanged(imgIRequest *aRequest); + + nsRect mSubRect; ///< If set, indicates that only the portion of the image specified by the rect should be used. + nsSize mIntrinsicSize; + nsSize mImageSize; + + // Boolean variable to determine if the current image request has been + // registered with the refresh driver. + bool mRequestRegistered; + + nsRefPtr<imgRequestProxy> mImageRequest; + nsCOMPtr<imgINotificationObserver> mListener; + + int32_t mLoadFlags; + + bool mUseSrcAttr; ///< Whether or not the image src comes from an attribute. + bool mSuppressStyleCheck; + bool mFireEventOnDecode; +}; // class nsImageBoxFrame + +class nsDisplayXULImage : public nsDisplayImageContainer { +public: + nsDisplayXULImage(nsDisplayListBuilder* aBuilder, + nsImageBoxFrame* aFrame) : + nsDisplayImageContainer(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayXULImage); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayXULImage() { + MOZ_COUNT_DTOR(nsDisplayXULImage); + } +#endif + + virtual already_AddRefed<ImageContainer> GetContainer(LayerManager* aManager, + nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE; + virtual void ConfigureLayer(ImageLayer* aLayer, const nsIntPoint& aOffset) MOZ_OVERRIDE; + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) MOZ_OVERRIDE + { + *aSnap = true; + return nsRect(ToReferenceFrame(), Frame()->GetSize()); + } + + // Doesn't handle HitTest because nsLeafBoxFrame already creates an + // event receiver for us + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) MOZ_OVERRIDE; + NS_DISPLAY_DECL_NAME("XULImage", TYPE_XUL_IMAGE) +}; + +#endif /* nsImageBoxFrame_h___ */ diff --git a/layout/xul/base/src/nsLeafBoxFrame.cpp b/layout/xul/base/src/nsLeafBoxFrame.cpp new file mode 100644 index 000000000..01733912a --- /dev/null +++ b/layout/xul/base/src/nsLeafBoxFrame.cpp @@ -0,0 +1,381 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsLeafBoxFrame.h" +#include "nsBoxFrame.h" +#include "nsCOMPtr.h" +#include "nsGkAtoms.h" +#include "nsPresContext.h" +#include "nsStyleContext.h" +#include "nsIContent.h" +#include "nsINameSpaceManager.h" +#include "nsBoxLayoutState.h" +#include "nsWidgetsCID.h" +#include "nsViewManager.h" +#include "nsContainerFrame.h" +#include "nsDisplayList.h" +#include <algorithm> + +// +// NS_NewLeafBoxFrame +// +// Creates a new Toolbar frame and returns it +// +nsIFrame* +NS_NewLeafBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsLeafBoxFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsLeafBoxFrame) + +nsLeafBoxFrame::nsLeafBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext) + : nsLeafFrame(aContext) +{ +} + +#ifdef DEBUG_LAYOUT +void +nsLeafBoxFrame::GetBoxName(nsAutoString& aName) +{ + GetFrameName(aName); +} +#endif + + +/** + * Initialize us. This is a good time to get the alignment of the box + */ +void +nsLeafBoxFrame::Init( + nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsLeafFrame::Init(aContent, aParent, aPrevInFlow); + + if (GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER) { + AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); + } + + UpdateMouseThrough(); +} + +NS_IMETHODIMP +nsLeafBoxFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsLeafFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + + if (aAttribute == nsGkAtoms::mousethrough) + UpdateMouseThrough(); + + return rv; +} + +void nsLeafBoxFrame::UpdateMouseThrough() +{ + if (mContent) { + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::never, &nsGkAtoms::always, nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::mousethrough, + strings, eCaseMatters)) { + case 0: AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); break; + case 1: AddStateBits(NS_FRAME_MOUSE_THROUGH_ALWAYS); break; + case 2: { + RemoveStateBits(NS_FRAME_MOUSE_THROUGH_ALWAYS); + RemoveStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); + break; + } + } + } +} + +void +nsLeafBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // REVIEW: GetFrameForPoint used to not report events for the background + // layer, whereas this code will put an event receiver for this frame in the + // BlockBorderBackground() list. But I don't see any need to preserve + // that anomalous behaviour. The important thing I'm preserving is that + // leaf boxes continue to receive events in the foreground layer. + DisplayBorderBackgroundOutline(aBuilder, aLists); + + if (!aBuilder->IsForEventDelivery() || !IsVisibleForPainting(aBuilder)) + return; + + aLists.Content()->AppendNewToTop(new (aBuilder) + nsDisplayEventReceiver(aBuilder, this)); +} + +/* virtual */ nscoord +nsLeafBoxFrame::GetMinWidth(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + nsBoxLayoutState state(PresContext(), aRenderingContext); + nsSize minSize = GetMinSize(state); + + // GetMinSize returns border-box width, and we want to return content + // width. Since Reflow uses the reflow state's border and padding, we + // actually just want to subtract what GetMinSize added, which is the + // result of GetBorderAndPadding. + nsMargin bp; + GetBorderAndPadding(bp); + + result = minSize.width - bp.LeftRight(); + + return result; +} + +/* virtual */ nscoord +nsLeafBoxFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + nsBoxLayoutState state(PresContext(), aRenderingContext); + nsSize prefSize = GetPrefSize(state); + + // GetPrefSize returns border-box width, and we want to return content + // width. Since Reflow uses the reflow state's border and padding, we + // actually just want to subtract what GetPrefSize added, which is the + // result of GetBorderAndPadding. + nsMargin bp; + GetBorderAndPadding(bp); + + result = prefSize.width - bp.LeftRight(); + + return result; +} + +nscoord +nsLeafBoxFrame::GetIntrinsicWidth() +{ + // No intrinsic width + return 0; +} + +nsSize +nsLeafBoxFrame::ComputeAutoSize(nsRenderingContext *aRenderingContext, + nsSize aCBSize, nscoord aAvailableWidth, + nsSize aMargin, nsSize aBorder, + nsSize aPadding, bool aShrinkWrap) +{ + // Important: NOT calling our direct superclass here! + return nsFrame::ComputeAutoSize(aRenderingContext, aCBSize, aAvailableWidth, + aMargin, aBorder, aPadding, aShrinkWrap); +} + +NS_IMETHODIMP +nsLeafBoxFrame::Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + // This is mostly a copy of nsBoxFrame::Reflow(). + // We aren't able to share an implementation because of the frame + // class hierarchy. If you make changes here, please keep + // nsBoxFrame::Reflow in sync. + + DO_GLOBAL_REFLOW_COUNT("nsLeafBoxFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); + + NS_ASSERTION(aReflowState.ComputedWidth() >=0 && + aReflowState.ComputedHeight() >= 0, "Computed Size < 0"); + +#ifdef DO_NOISY_REFLOW + printf("\n-------------Starting LeafBoxFrame Reflow ----------------------------\n"); + printf("%p ** nsLBF::Reflow %d R: ", this, myCounter++); + switch (aReflowState.reason) { + case eReflowReason_Initial: + printf("Ini");break; + case eReflowReason_Incremental: + printf("Inc");break; + case eReflowReason_Resize: + printf("Rsz");break; + case eReflowReason_StyleChange: + printf("Sty");break; + case eReflowReason_Dirty: + printf("Drt "); + break; + default:printf("<unknown>%d", aReflowState.reason);break; + } + + printSize("AW", aReflowState.availableWidth); + printSize("AH", aReflowState.availableHeight); + printSize("CW", aReflowState.ComputedWidth()); + printSize("CH", aReflowState.ComputedHeight()); + + printf(" *\n"); + +#endif + + aStatus = NS_FRAME_COMPLETE; + + // create the layout state + nsBoxLayoutState state(aPresContext, aReflowState.rendContext); + + nsSize computedSize(aReflowState.ComputedWidth(),aReflowState.ComputedHeight()); + + nsMargin m; + m = aReflowState.mComputedBorderPadding; + + //GetBorderAndPadding(m); + + // this happens sometimes. So lets handle it gracefully. + if (aReflowState.ComputedHeight() == 0) { + nsSize minSize = GetMinSize(state); + computedSize.height = minSize.height - m.top - m.bottom; + } + + nsSize prefSize(0,0); + + // if we are told to layout intrinic then get our preferred size. + if (computedSize.width == NS_INTRINSICSIZE || computedSize.height == NS_INTRINSICSIZE) { + prefSize = GetPrefSize(state); + nsSize minSize = GetMinSize(state); + nsSize maxSize = GetMaxSize(state); + prefSize = BoundsCheck(minSize, prefSize, maxSize); + } + + // get our desiredSize + if (aReflowState.ComputedWidth() == NS_INTRINSICSIZE) { + computedSize.width = prefSize.width; + } else { + computedSize.width += m.left + m.right; + } + + if (aReflowState.ComputedHeight() == NS_INTRINSICSIZE) { + computedSize.height = prefSize.height; + } else { + computedSize.height += m.top + m.bottom; + } + + // handle reflow state min and max sizes + // XXXbz the width handling here seems to be wrong, since + // mComputedMin/MaxWidth is a content-box size, whole + // computedSize.width is a border-box size... + if (computedSize.width > aReflowState.mComputedMaxWidth) + computedSize.width = aReflowState.mComputedMaxWidth; + + if (computedSize.width < aReflowState.mComputedMinWidth) + computedSize.width = aReflowState.mComputedMinWidth; + + // Now adjust computedSize.height for our min and max computed + // height. The only problem is that those are content-box sizes, + // while computedSize.height is a border-box size. So subtract off + // m.TopBottom() before adjusting, then readd it. + computedSize.height = std::max(0, computedSize.height - m.TopBottom()); + computedSize.height = NS_CSS_MINMAX(computedSize.height, + aReflowState.mComputedMinHeight, + aReflowState.mComputedMaxHeight); + computedSize.height += m.TopBottom(); + + nsRect r(mRect.x, mRect.y, computedSize.width, computedSize.height); + + SetBounds(state, r); + + // layout our children + Layout(state); + + // ok our child could have gotten bigger. So lets get its bounds + aDesiredSize.width = mRect.width; + aDesiredSize.height = mRect.height; + aDesiredSize.ascent = GetBoxAscent(state); + + // the overflow rect is set in SetBounds() above + aDesiredSize.mOverflowAreas = GetOverflowAreas(); + +#ifdef DO_NOISY_REFLOW + { + printf("%p ** nsLBF(done) W:%d H:%d ", this, aDesiredSize.width, aDesiredSize.height); + + if (maxElementWidth) { + printf("MW:%d\n", *maxElementWidth); + } else { + printf("MW:?\n"); + } + + } +#endif + + return NS_OK; +} + +#ifdef DEBUG +NS_IMETHODIMP +nsLeafBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("LeafBox"), aResult); +} +#endif + +nsIAtom* +nsLeafBoxFrame::GetType() const +{ + return nsGkAtoms::leafBoxFrame; +} + +NS_IMETHODIMP +nsLeafBoxFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) +{ + MarkIntrinsicWidthsDirty(); + return nsLeafFrame::CharacterDataChanged(aInfo); +} + +/* virtual */ nsSize +nsLeafBoxFrame::GetPrefSize(nsBoxLayoutState& aState) +{ + return nsBox::GetPrefSize(aState); +} + +/* virtual */ nsSize +nsLeafBoxFrame::GetMinSize(nsBoxLayoutState& aState) +{ + return nsBox::GetMinSize(aState); +} + +/* virtual */ nsSize +nsLeafBoxFrame::GetMaxSize(nsBoxLayoutState& aState) +{ + return nsBox::GetMaxSize(aState); +} + +/* virtual */ nscoord +nsLeafBoxFrame::GetFlex(nsBoxLayoutState& aState) +{ + return nsBox::GetFlex(aState); +} + +/* virtual */ nscoord +nsLeafBoxFrame::GetBoxAscent(nsBoxLayoutState& aState) +{ + return nsBox::GetBoxAscent(aState); +} + +/* virtual */ void +nsLeafBoxFrame::MarkIntrinsicWidthsDirty() +{ + // Don't call base class method, since everything it does is within an + // IsBoxWrapped check. +} + +NS_IMETHODIMP +nsLeafBoxFrame::DoLayout(nsBoxLayoutState& aState) +{ + return nsBox::DoLayout(aState); +} diff --git a/layout/xul/base/src/nsLeafBoxFrame.h b/layout/xul/base/src/nsLeafBoxFrame.h new file mode 100644 index 000000000..791e63145 --- /dev/null +++ b/layout/xul/base/src/nsLeafBoxFrame.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsLeafBoxFrame_h___ +#define nsLeafBoxFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsLeafFrame.h" +#include "nsBox.h" + +class nsAccessKeyInfo; + +class nsLeafBoxFrame : public nsLeafFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewLeafBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + virtual nsSize GetPrefSize(nsBoxLayoutState& aState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsBoxLayoutState& aState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsBoxLayoutState& aState) MOZ_OVERRIDE; + virtual nscoord GetFlex(nsBoxLayoutState& aState) MOZ_OVERRIDE; + virtual nscoord GetBoxAscent(nsBoxLayoutState& aState) MOZ_OVERRIDE; + + virtual nsIAtom* GetType() const MOZ_OVERRIDE; + virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE + { + // This is bogus, but it's what we've always done. + // Note that nsLeafFrame is also eReplacedContainsBlock. + return nsLeafFrame::IsFrameOfType(aFlags & + ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock | nsIFrame::eXULBox)); + } + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE; +#endif + + // nsIHTMLReflow overrides + + virtual void MarkIntrinsicWidthsDirty() MOZ_OVERRIDE; + virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE; + virtual nscoord GetPrefWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE; + + // Our auto size is that provided by nsFrame, not nsLeafFrame + virtual nsSize ComputeAutoSize(nsRenderingContext *aRenderingContext, + nsSize aCBSize, nscoord aAvailableWidth, + nsSize aMargin, nsSize aBorder, + nsSize aPadding, bool aShrinkWrap) MOZ_OVERRIDE; + + NS_IMETHOD Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) MOZ_OVERRIDE; + + NS_IMETHOD CharacterDataChanged(CharacterDataChangeInfo* aInfo) MOZ_OVERRIDE; + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* asPrevInFlow) MOZ_OVERRIDE; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + virtual bool ComputesOwnOverflowArea() MOZ_OVERRIDE { return false; } + +protected: + + NS_IMETHOD DoLayout(nsBoxLayoutState& aState) MOZ_OVERRIDE; + +#ifdef DEBUG_LAYOUT + virtual void GetBoxName(nsAutoString& aName) MOZ_OVERRIDE; +#endif + + virtual nscoord GetIntrinsicWidth() MOZ_OVERRIDE; + + nsLeafBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext); + +private: + + void UpdateMouseThrough(); + + +}; // class nsLeafBoxFrame + +#endif /* nsLeafBoxFrame_h___ */ diff --git a/layout/xul/base/src/nsListBoxBodyFrame.cpp b/layout/xul/base/src/nsListBoxBodyFrame.cpp new file mode 100644 index 000000000..2a4caf42a --- /dev/null +++ b/layout/xul/base/src/nsListBoxBodyFrame.cpp @@ -0,0 +1,1513 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsListBoxBodyFrame.h" + +#include "nsListBoxLayout.h" + +#include "nsCOMPtr.h" +#include "nsGridRowGroupLayout.h" +#include "nsIServiceManager.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsINameSpaceManager.h" +#include "nsIDocument.h" +#include "nsIDOMMouseEvent.h" +#include "nsIDOMElement.h" +#include "nsIDOMNodeList.h" +#include "nsCSSFrameConstructor.h" +#include "nsIScrollableFrame.h" +#include "nsScrollbarFrame.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsStyleContext.h" +#include "nsFontMetrics.h" +#include "nsITimer.h" +#include "nsAutoPtr.h" +#include "nsStyleSet.h" +#include "nsPIBoxObject.h" +#include "nsINodeInfo.h" +#include "nsLayoutUtils.h" +#include "nsPIListBoxObject.h" +#include "nsContentUtils.h" +#include "nsChildIterator.h" +#include "nsRenderingContext.h" +#include <algorithm> + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +/////////////// nsListScrollSmoother ////////////////// + +/* A mediator used to smooth out scrolling. It works by seeing if + * we have time to scroll the amount of rows requested. This is determined + * by measuring how long it takes to scroll a row. If we can scroll the + * rows in time we do so. If not we start a timer and skip the request. We + * do this until the timer finally first because the user has stopped moving + * the mouse. Then do all the queued requests in on shot. + */ + +// the longest amount of time that can go by before the use +// notices it as a delay. +#define USER_TIME_THRESHOLD 150000 + +// how long it takes to layout a single row initial value. +// we will time this after we scroll a few rows. +#define TIME_PER_ROW_INITAL 50000 + +// if we decide we can't layout the rows in the amount of time. How long +// do we wait before checking again? +#define SMOOTH_INTERVAL 100 + +class nsListScrollSmoother : public nsITimerCallback +{ +public: + NS_DECL_ISUPPORTS + + nsListScrollSmoother(nsListBoxBodyFrame* aOuter); + virtual ~nsListScrollSmoother(); + + // nsITimerCallback + NS_DECL_NSITIMERCALLBACK + + void Start(); + void Stop(); + bool IsRunning(); + + nsCOMPtr<nsITimer> mRepeatTimer; + int32_t mDelta; + nsListBoxBodyFrame* mOuter; +}; + +nsListScrollSmoother::nsListScrollSmoother(nsListBoxBodyFrame* aOuter) +{ + mDelta = 0; + mOuter = aOuter; +} + +nsListScrollSmoother::~nsListScrollSmoother() +{ + Stop(); +} + +NS_IMETHODIMP +nsListScrollSmoother::Notify(nsITimer *timer) +{ + Stop(); + + NS_ASSERTION(mOuter, "mOuter is null, see bug #68365"); + if (!mOuter) return NS_OK; + + // actually do some work. + mOuter->InternalPositionChangedCallback(); + return NS_OK; +} + +bool +nsListScrollSmoother::IsRunning() +{ + return mRepeatTimer ? true : false; +} + +void +nsListScrollSmoother::Start() +{ + Stop(); + mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1"); + mRepeatTimer->InitWithCallback(this, SMOOTH_INTERVAL, nsITimer::TYPE_ONE_SHOT); +} + +void +nsListScrollSmoother::Stop() +{ + if ( mRepeatTimer ) { + mRepeatTimer->Cancel(); + mRepeatTimer = nullptr; + } +} + +NS_IMPL_ISUPPORTS1(nsListScrollSmoother, nsITimerCallback) + +/////////////// nsListBoxBodyFrame ////////////////// + +nsListBoxBodyFrame::nsListBoxBodyFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + nsBoxLayout* aLayoutManager) + : nsBoxFrame(aPresShell, aContext, false, aLayoutManager), + mTopFrame(nullptr), + mBottomFrame(nullptr), + mLinkupFrame(nullptr), + mScrollSmoother(nullptr), + mRowsToPrepend(0), + mRowCount(-1), + mRowHeight(0), + mAvailableHeight(0), + mStringWidth(-1), + mCurrentIndex(0), + mOldIndex(0), + mYPosition(0), + mTimePerRow(TIME_PER_ROW_INITAL), + mRowHeightWasSet(false), + mScrolling(false), + mAdjustScroll(false), + mReflowCallbackPosted(false) +{ +} + +nsListBoxBodyFrame::~nsListBoxBodyFrame() +{ + NS_IF_RELEASE(mScrollSmoother); + +#if USE_TIMER_TO_DELAY_SCROLLING + StopScrollTracking(); + mAutoScrollTimer = nullptr; +#endif + +} + +NS_QUERYFRAME_HEAD(nsListBoxBodyFrame) + NS_QUERYFRAME_ENTRY(nsIScrollbarMediator) + NS_QUERYFRAME_ENTRY(nsListBoxBodyFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +////////// nsIFrame ///////////////// + +void +nsListBoxBodyFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this); + if (scrollFrame) { + nsIFrame* verticalScrollbar = scrollFrame->GetScrollbarBox(true); + nsScrollbarFrame* scrollbarFrame = do_QueryFrame(verticalScrollbar); + if (scrollbarFrame) { + scrollbarFrame->SetScrollbarMediatorContent(GetContent()); + } + } + nsRefPtr<nsFontMetrics> fm; + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); + mRowHeight = fm->MaxHeight(); +} + +void +nsListBoxBodyFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // make sure we cancel any posted callbacks. + if (mReflowCallbackPosted) + PresContext()->PresShell()->CancelReflowCallback(this); + + // Revoke any pending position changed events + for (uint32_t i = 0; i < mPendingPositionChangeEvents.Length(); ++i) { + mPendingPositionChangeEvents[i]->Revoke(); + } + + // Make sure we tell our listbox's box object we're being destroyed. + if (mBoxObject) { + mBoxObject->ClearCachedValues(); + } + + nsBoxFrame::DestroyFrom(aDestructRoot); +} + +NS_IMETHODIMP +nsListBoxBodyFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = NS_OK; + + if (aAttribute == nsGkAtoms::rows) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + else + rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); + + return rv; + +} + +/* virtual */ void +nsListBoxBodyFrame::MarkIntrinsicWidthsDirty() +{ + mStringWidth = -1; + nsBoxFrame::MarkIntrinsicWidthsDirty(); +} + +/////////// nsBox /////////////// + +NS_IMETHODIMP +nsListBoxBodyFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState) +{ + if (mScrolling) + aBoxLayoutState.SetPaintingDisabled(true); + + nsresult rv = nsBoxFrame::DoLayout(aBoxLayoutState); + + // determine the real height for the scrollable area from the total number + // of rows, since non-visible rows don't yet have frames + nsRect rect(nsPoint(0, 0), GetSize()); + nsOverflowAreas overflow(rect, rect); + if (mLayoutManager) { + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + ConsiderChildOverflow(overflow, childFrame); + childFrame = childFrame->GetNextSibling(); + } + + nsSize prefSize = mLayoutManager->GetPrefSize(this, aBoxLayoutState); + NS_FOR_FRAME_OVERFLOW_TYPES(otype) { + nsRect& o = overflow.Overflow(otype); + o.height = std::max(o.height, prefSize.height); + } + } + FinishAndStoreOverflow(overflow, GetSize()); + + if (mScrolling) + aBoxLayoutState.SetPaintingDisabled(false); + + // if we are scrolled and the row height changed + // make sure we are scrolled to a correct index. + if (mAdjustScroll) + PostReflowCallback(); + + return rv; +} + +nsSize +nsListBoxBodyFrame::GetMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) +{ + nsSize result(0, 0); + if (nsContentUtils::HasNonEmptyAttr(GetContent(), kNameSpaceID_None, + nsGkAtoms::sizemode)) { + result = GetPrefSize(aBoxLayoutState); + result.height = 0; + nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this); + if (scrollFrame && + scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) { + nsMargin scrollbars = + scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState); + result.width += scrollbars.left + scrollbars.right; + } + } + return result; +} + +nsSize +nsListBoxBodyFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState) +{ + nsSize pref = nsBoxFrame::GetPrefSize(aBoxLayoutState); + + int32_t size = GetFixedRowSize(); + if (size > -1) + pref.height = size*GetRowHeightAppUnits(); + + nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this); + if (scrollFrame && + scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) { + nsMargin scrollbars = scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState); + pref.width += scrollbars.left + scrollbars.right; + } + return pref; +} + +///////////// nsIScrollbarMediator /////////////// + +NS_IMETHODIMP +nsListBoxBodyFrame::PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) +{ + if (mScrolling || mRowHeight == 0) + return NS_OK; + + nscoord oldTwipIndex, newTwipIndex; + oldTwipIndex = mCurrentIndex*mRowHeight; + newTwipIndex = nsPresContext::CSSPixelsToAppUnits(aNewIndex); + int32_t twipDelta = newTwipIndex > oldTwipIndex ? newTwipIndex - oldTwipIndex : oldTwipIndex - newTwipIndex; + + int32_t rowDelta = twipDelta / mRowHeight; + int32_t remainder = twipDelta % mRowHeight; + if (remainder > (mRowHeight/2)) + rowDelta++; + + if (rowDelta == 0) + return NS_OK; + + // update the position to be row based. + + int32_t newIndex = newTwipIndex > oldTwipIndex ? mCurrentIndex + rowDelta : mCurrentIndex - rowDelta; + //aNewIndex = newIndex*mRowHeight/mOnePixel; + + nsListScrollSmoother* smoother = GetSmoother(); + + // if we can't scroll the rows in time then start a timer. We will eat + // events until the user stops moving and the timer stops. + if (smoother->IsRunning() || rowDelta*mTimePerRow > USER_TIME_THRESHOLD) { + + smoother->Stop(); + + smoother->mDelta = newTwipIndex > oldTwipIndex ? rowDelta : -rowDelta; + + smoother->Start(); + + return NS_OK; + } + + smoother->Stop(); + + mCurrentIndex = newIndex; + smoother->mDelta = 0; + + if (mCurrentIndex < 0) { + mCurrentIndex = 0; + return NS_OK; + } + + return InternalPositionChanged(newTwipIndex < oldTwipIndex, rowDelta); +} + +NS_IMETHODIMP +nsListBoxBodyFrame::VisibilityChanged(bool aVisible) +{ + if (mRowHeight == 0) + return NS_OK; + + int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight); + if (lastPageTopRow < 0) + lastPageTopRow = 0; + int32_t delta = mCurrentIndex - lastPageTopRow; + if (delta > 0) { + mCurrentIndex = lastPageTopRow; + InternalPositionChanged(true, delta); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsListBoxBodyFrame::ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) +{ + if (aOldIndex == aNewIndex) + return NS_OK; + if (aNewIndex < aOldIndex) + mCurrentIndex--; + else mCurrentIndex++; + if (mCurrentIndex < 0) { + mCurrentIndex = 0; + return NS_OK; + } + InternalPositionChanged(aNewIndex < aOldIndex, 1); + + return NS_OK; +} + +///////////// nsIReflowCallback /////////////// + +bool +nsListBoxBodyFrame::ReflowFinished() +{ + nsAutoScriptBlocker scriptBlocker; + // now create or destroy any rows as needed + CreateRows(); + + // keep scrollbar in sync + if (mAdjustScroll) { + VerticalScroll(mYPosition); + mAdjustScroll = false; + } + + // if the row height changed then mark everything as a style change. + // That will dirty the entire listbox + if (mRowHeightWasSet) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + int32_t pos = mCurrentIndex * mRowHeight; + if (mYPosition != pos) + mAdjustScroll = true; + mRowHeightWasSet = false; + } + + mReflowCallbackPosted = false; + return true; +} + +void +nsListBoxBodyFrame::ReflowCallbackCanceled() +{ + mReflowCallbackPosted = false; +} + +///////// nsIListBoxObject /////////////// + +nsresult +nsListBoxBodyFrame::GetRowCount(int32_t* aResult) +{ + *aResult = GetRowCount(); + return NS_OK; +} + +nsresult +nsListBoxBodyFrame::GetNumberOfVisibleRows(int32_t *aResult) +{ + *aResult= mRowHeight ? GetAvailableHeight() / mRowHeight : 0; + return NS_OK; +} + +nsresult +nsListBoxBodyFrame::GetIndexOfFirstVisibleRow(int32_t *aResult) +{ + *aResult = mCurrentIndex; + return NS_OK; +} + +nsresult +nsListBoxBodyFrame::EnsureIndexIsVisible(int32_t aRowIndex) +{ + if (aRowIndex < 0) + return NS_ERROR_ILLEGAL_VALUE; + + int32_t rows = 0; + if (mRowHeight) + rows = GetAvailableHeight()/mRowHeight; + if (rows <= 0) + rows = 1; + int32_t bottomIndex = mCurrentIndex + rows; + + // if row is visible, ignore + if (mCurrentIndex <= aRowIndex && aRowIndex < bottomIndex) + return NS_OK; + + int32_t delta; + + bool up = aRowIndex < mCurrentIndex; + if (up) { + delta = mCurrentIndex - aRowIndex; + mCurrentIndex = aRowIndex; + } + else { + // Check to be sure we're not scrolling off the bottom of the tree + if (aRowIndex >= GetRowCount()) + return NS_ERROR_ILLEGAL_VALUE; + + // Bring it just into view. + delta = 1 + (aRowIndex-bottomIndex); + mCurrentIndex += delta; + } + + // Safe to not go off an event here, since this is coming from the + // box object. + DoInternalPositionChangedSync(up, delta); + return NS_OK; +} + +nsresult +nsListBoxBodyFrame::ScrollByLines(int32_t aNumLines) +{ + int32_t scrollIndex, visibleRows; + GetIndexOfFirstVisibleRow(&scrollIndex); + GetNumberOfVisibleRows(&visibleRows); + + scrollIndex += aNumLines; + + if (scrollIndex < 0) + scrollIndex = 0; + else { + int32_t numRows = GetRowCount(); + int32_t lastPageTopRow = numRows - visibleRows; + if (scrollIndex > lastPageTopRow) + scrollIndex = lastPageTopRow; + } + + ScrollToIndex(scrollIndex); + + return NS_OK; +} + +// walks the DOM to get the zero-based row index of the content +nsresult +nsListBoxBodyFrame::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval) +{ + if (aItem) { + *_retval = 0; + nsCOMPtr<nsIContent> itemContent(do_QueryInterface(aItem)); + + ChildIterator iter, last; + for (ChildIterator::Init(mContent, &iter, &last); + iter != last; + ++iter) { + nsIContent *child = (*iter); + // we hit a list row, count it + if (child->Tag() == nsGkAtoms::listitem) { + // is this it? + if (child == itemContent) + return NS_OK; + + ++(*_retval); + } + } + } + + // not found + *_retval = -1; + return NS_OK; +} + +nsresult +nsListBoxBodyFrame::GetItemAtIndex(int32_t aIndex, nsIDOMElement** aItem) +{ + *aItem = nullptr; + if (aIndex < 0) + return NS_OK; + + int32_t itemCount = 0; + ChildIterator iter, last; + for (ChildIterator::Init(mContent, &iter, &last); + iter != last; + ++iter) { + nsIContent *child = (*iter); + // we hit a list row, check if it is the one we are looking for + if (child->Tag() == nsGkAtoms::listitem) { + // is this it? + if (itemCount == aIndex) { + return CallQueryInterface(child, aItem); + } + ++itemCount; + } + } + + // not found + return NS_OK; +} + +/////////// nsListBoxBodyFrame /////////////// + +int32_t +nsListBoxBodyFrame::GetRowCount() +{ + if (mRowCount < 0) + ComputeTotalRowCount(); + return mRowCount; +} + +int32_t +nsListBoxBodyFrame::GetFixedRowSize() +{ + nsresult dummy; + + nsAutoString rows; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows); + if (!rows.IsEmpty()) + return rows.ToInteger(&dummy); + + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::size, rows); + + if (!rows.IsEmpty()) + return rows.ToInteger(&dummy); + + return -1; +} + +void +nsListBoxBodyFrame::SetRowHeight(nscoord aRowHeight) +{ + if (aRowHeight > mRowHeight) { + mRowHeight = aRowHeight; + + // signal we need to dirty everything + // and we want to be notified after reflow + // so we can create or destory rows as needed + mRowHeightWasSet = true; + PostReflowCallback(); + } +} + +nscoord +nsListBoxBodyFrame::GetAvailableHeight() +{ + nsIScrollableFrame* scrollFrame = + nsLayoutUtils::GetScrollableFrameFor(this); + if (scrollFrame) { + return scrollFrame->GetScrollPortRect().height; + } + return 0; +} + +nscoord +nsListBoxBodyFrame::GetYPosition() +{ + return mYPosition; +} + +nscoord +nsListBoxBodyFrame::ComputeIntrinsicWidth(nsBoxLayoutState& aBoxLayoutState) +{ + if (mStringWidth != -1) + return mStringWidth; + + nscoord largestWidth = 0; + + int32_t index = 0; + nsCOMPtr<nsIDOMElement> firstRowEl; + GetItemAtIndex(index, getter_AddRefs(firstRowEl)); + nsCOMPtr<nsIContent> firstRowContent(do_QueryInterface(firstRowEl)); + + if (firstRowContent) { + nsRefPtr<nsStyleContext> styleContext; + nsPresContext *presContext = aBoxLayoutState.PresContext(); + styleContext = presContext->StyleSet()-> + ResolveStyleFor(firstRowContent->AsElement(), nullptr); + + nscoord width = 0; + nsMargin margin(0,0,0,0); + + if (styleContext->StylePadding()->GetPadding(margin)) + width += margin.LeftRight(); + width += styleContext->StyleBorder()->GetComputedBorder().LeftRight(); + if (styleContext->StyleMargin()->GetMargin(margin)) + width += margin.LeftRight(); + + + ChildIterator iter, last; + uint32_t i = 0; + for (ChildIterator::Init(mContent, &iter, &last); + iter != last && i < 100; + ++iter, ++i) { + nsIContent *child = (*iter); + + if (child->Tag() == nsGkAtoms::listitem) { + nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext(); + if (rendContext) { + nsAutoString value; + uint32_t textCount = child->GetChildCount(); + for (uint32_t j = 0; j < textCount; ++j) { + nsIContent* text = child->GetChildAt(j); + if (text && text->IsNodeOfType(nsINode::eTEXT)) { + text->AppendTextTo(value); + } + } + + nsRefPtr<nsFontMetrics> fm; + nsLayoutUtils::GetFontMetricsForStyleContext(styleContext, + getter_AddRefs(fm)); + rendContext->SetFont(fm); + + nscoord textWidth = + nsLayoutUtils::GetStringWidth(this, rendContext, value.get(), value.Length()); + textWidth += width; + + if (textWidth > largestWidth) + largestWidth = textWidth; + } + } + } + } + + mStringWidth = largestWidth; + return mStringWidth; +} + +void +nsListBoxBodyFrame::ComputeTotalRowCount() +{ + mRowCount = 0; + + ChildIterator iter, last; + for (ChildIterator::Init(mContent, &iter, &last); + iter != last; + ++iter) { + if ((*iter)->Tag() == nsGkAtoms::listitem) + ++mRowCount; + } +} + +void +nsListBoxBodyFrame::PostReflowCallback() +{ + if (!mReflowCallbackPosted) { + mReflowCallbackPosted = true; + PresContext()->PresShell()->PostReflowCallback(this); + } +} + +////////// scrolling + +nsresult +nsListBoxBodyFrame::ScrollToIndex(int32_t aRowIndex) +{ + if (( aRowIndex < 0 ) || (mRowHeight == 0)) + return NS_OK; + + int32_t newIndex = aRowIndex; + int32_t delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex; + bool up = newIndex < mCurrentIndex; + + // Check to be sure we're not scrolling off the bottom of the tree + int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight); + if (lastPageTopRow < 0) + lastPageTopRow = 0; + + if (aRowIndex > lastPageTopRow) + return NS_OK; + + mCurrentIndex = newIndex; + + nsWeakFrame weak(this); + + // Since we're going to flush anyway, we need to not do this off an event + DoInternalPositionChangedSync(up, delta); + + if (!weak.IsAlive()) { + return NS_OK; + } + + // This change has to happen immediately. + // Flush any pending reflow commands. + // XXXbz why, exactly? + mContent->GetDocument()->FlushPendingNotifications(Flush_Layout); + + return NS_OK; +} + +nsresult +nsListBoxBodyFrame::InternalPositionChangedCallback() +{ + nsListScrollSmoother* smoother = GetSmoother(); + + if (smoother->mDelta == 0) + return NS_OK; + + mCurrentIndex += smoother->mDelta; + + if (mCurrentIndex < 0) + mCurrentIndex = 0; + + return DoInternalPositionChangedSync(smoother->mDelta < 0, + smoother->mDelta < 0 ? + -smoother->mDelta : smoother->mDelta); +} + +nsresult +nsListBoxBodyFrame::InternalPositionChanged(bool aUp, int32_t aDelta) +{ + nsRefPtr<nsPositionChangedEvent> ev = + new nsPositionChangedEvent(this, aUp, aDelta); + nsresult rv = NS_DispatchToCurrentThread(ev); + if (NS_SUCCEEDED(rv)) { + if (!mPendingPositionChangeEvents.AppendElement(ev)) { + rv = NS_ERROR_OUT_OF_MEMORY; + ev->Revoke(); + } + } + return rv; +} + +nsresult +nsListBoxBodyFrame::DoInternalPositionChangedSync(bool aUp, int32_t aDelta) +{ + nsWeakFrame weak(this); + + // Process all the pending position changes first + nsTArray< nsRefPtr<nsPositionChangedEvent> > temp; + temp.SwapElements(mPendingPositionChangeEvents); + for (uint32_t i = 0; i < temp.Length(); ++i) { + if (weak.IsAlive()) { + temp[i]->Run(); + } + temp[i]->Revoke(); + } + + if (!weak.IsAlive()) { + return NS_OK; + } + + return DoInternalPositionChanged(aUp, aDelta); +} + +nsresult +nsListBoxBodyFrame::DoInternalPositionChanged(bool aUp, int32_t aDelta) +{ + if (aDelta == 0) + return NS_OK; + + nsRefPtr<nsPresContext> presContext(PresContext()); + nsBoxLayoutState state(presContext); + + // begin timing how long it takes to scroll a row + PRTime start = PR_Now(); + + nsWeakFrame weakThis(this); + mContent->GetDocument()->FlushPendingNotifications(Flush_Layout); + if (!weakThis.IsAlive()) { + return NS_OK; + } + + { + nsAutoScriptBlocker scriptBlocker; + + int32_t visibleRows = 0; + if (mRowHeight) + visibleRows = GetAvailableHeight()/mRowHeight; + + if (aDelta < visibleRows) { + int32_t loseRows = aDelta; + if (aUp) { + // scrolling up, destroy rows from the bottom downwards + ReverseDestroyRows(loseRows); + mRowsToPrepend += aDelta; + mLinkupFrame = nullptr; + } + else { + // scrolling down, destroy rows from the top upwards + DestroyRows(loseRows); + mRowsToPrepend = 0; + } + } + else { + // We have scrolled so much that all of our current frames will + // go off screen, so blow them all away. Weeee! + nsIFrame *currBox = mFrames.FirstChild(); + nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor(); + fc->BeginUpdate(); + while (currBox) { + nsIFrame *nextBox = currBox->GetNextSibling(); + RemoveChildFrame(state, currBox); + currBox = nextBox; + } + fc->EndUpdate(); + } + + // clear frame markers so that CreateRows will re-create + mTopFrame = mBottomFrame = nullptr; + + mYPosition = mCurrentIndex*mRowHeight; + mScrolling = true; + presContext->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN); + } + if (!weakThis.IsAlive()) { + return NS_OK; + } + // Flush calls CreateRows + // XXXbz there has to be a better way to do this than flushing! + presContext->PresShell()->FlushPendingNotifications(Flush_Layout); + if (!weakThis.IsAlive()) { + return NS_OK; + } + + mScrolling = false; + + VerticalScroll(mYPosition); + + PRTime end = PR_Now(); + + int32_t newTime = int32_t(end - start) / aDelta; + + // average old and new + mTimePerRow = (newTime + mTimePerRow)/2; + + return NS_OK; +} + +nsListScrollSmoother* +nsListBoxBodyFrame::GetSmoother() +{ + if (!mScrollSmoother) { + mScrollSmoother = new nsListScrollSmoother(this); + NS_ASSERTION(mScrollSmoother, "out of memory"); + NS_IF_ADDREF(mScrollSmoother); + } + + return mScrollSmoother; +} + +void +nsListBoxBodyFrame::VerticalScroll(int32_t aPosition) +{ + nsIScrollableFrame* scrollFrame + = nsLayoutUtils::GetScrollableFrameFor(this); + if (!scrollFrame) { + return; + } + + nsPoint scrollPosition = scrollFrame->GetScrollPosition(); + + nsWeakFrame weakFrame(this); + scrollFrame->ScrollTo(nsPoint(scrollPosition.x, aPosition), + nsIScrollableFrame::INSTANT); + if (!weakFrame.IsAlive()) { + return; + } + + mYPosition = aPosition; +} + +////////// frame and box retrieval + +nsIFrame* +nsListBoxBodyFrame::GetFirstFrame() +{ + mTopFrame = mFrames.FirstChild(); + return mTopFrame; +} + +nsIFrame* +nsListBoxBodyFrame::GetLastFrame() +{ + return mFrames.LastChild(); +} + +bool +nsListBoxBodyFrame::SupportsOrdinalsInChildren() +{ + return false; +} + +////////// lazy row creation and destruction + +void +nsListBoxBodyFrame::CreateRows() +{ + // Get our client rect. + nsRect clientRect; + GetClientRect(clientRect); + + // Get the starting y position and the remaining available + // height. + nscoord availableHeight = GetAvailableHeight(); + + if (availableHeight <= 0) { + bool fixed = (GetFixedRowSize() != -1); + if (fixed) + availableHeight = 10; + else + return; + } + + // get the first tree box. If there isn't one create one. + bool created = false; + nsIFrame* box = GetFirstItemBox(0, &created); + nscoord rowHeight = GetRowHeightAppUnits(); + while (box) { + if (created && mRowsToPrepend > 0) + --mRowsToPrepend; + + // if the row height is 0 then fail. Wait until someone + // laid out and sets the row height. + if (rowHeight == 0) + return; + + availableHeight -= rowHeight; + + // should we continue? Is the enought height? + if (!ContinueReflow(availableHeight)) + break; + + // get the next tree box. Create one if needed. + box = GetNextItemBox(box, 0, &created); + } + + mRowsToPrepend = 0; + mLinkupFrame = nullptr; +} + +void +nsListBoxBodyFrame::DestroyRows(int32_t& aRowsToLose) +{ + // We need to destroy frames until our row count has been properly + // reduced. A reflow will then pick up and create the new frames. + nsIFrame* childFrame = GetFirstFrame(); + nsBoxLayoutState state(PresContext()); + + nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor(); + fc->BeginUpdate(); + while (childFrame && aRowsToLose > 0) { + --aRowsToLose; + + nsIFrame* nextFrame = childFrame->GetNextSibling(); + RemoveChildFrame(state, childFrame); + + mTopFrame = childFrame = nextFrame; + } + fc->EndUpdate(); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void +nsListBoxBodyFrame::ReverseDestroyRows(int32_t& aRowsToLose) +{ + // We need to destroy frames until our row count has been properly + // reduced. A reflow will then pick up and create the new frames. + nsIFrame* childFrame = GetLastFrame(); + nsBoxLayoutState state(PresContext()); + + nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor(); + fc->BeginUpdate(); + while (childFrame && aRowsToLose > 0) { + --aRowsToLose; + + nsIFrame* prevFrame; + prevFrame = childFrame->GetPrevSibling(); + RemoveChildFrame(state, childFrame); + + mBottomFrame = childFrame = prevFrame; + } + fc->EndUpdate(); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +static bool +IsListItemChild(nsListBoxBodyFrame* aParent, nsIContent* aChild, + nsIFrame** aChildFrame) +{ + *aChildFrame = nullptr; + if (!aChild->IsXUL() || aChild->Tag() != nsGkAtoms::listitem) { + return false; + } + nsIFrame* existingFrame = aChild->GetPrimaryFrame(); + if (existingFrame && existingFrame->GetParent() != aParent) { + return false; + } + *aChildFrame = existingFrame; + return true; +} + +// +// Get the nsIFrame for the first visible listitem, and if none exists, +// create one. +// +nsIFrame* +nsListBoxBodyFrame::GetFirstItemBox(int32_t aOffset, bool* aCreated) +{ + if (aCreated) + *aCreated = false; + + // Clear ourselves out. + mBottomFrame = mTopFrame; + + if (mTopFrame) { + return mTopFrame->IsBoxFrame() ? mTopFrame : nullptr; + } + + // top frame was cleared out + mTopFrame = GetFirstFrame(); + mBottomFrame = mTopFrame; + + if (mTopFrame && mRowsToPrepend <= 0) { + return mTopFrame->IsBoxFrame() ? mTopFrame : nullptr; + } + + // At this point, we either have no frames at all, + // or the user has scrolled upwards, leaving frames + // to be created at the top. Let's determine which + // content needs a new frame first. + + nsCOMPtr<nsIContent> startContent; + if (mTopFrame && mRowsToPrepend > 0) { + // We need to insert rows before the top frame + nsIContent* topContent = mTopFrame->GetContent(); + nsIContent* topParent = topContent->GetParent(); + int32_t contentIndex = topParent->IndexOf(topContent); + contentIndex -= aOffset; + if (contentIndex < 0) + return nullptr; + startContent = topParent->GetChildAt(contentIndex - mRowsToPrepend); + } else { + // This will be the first item frame we create. Use the content + // at the current index, which is the first index scrolled into view + GetListItemContentAt(mCurrentIndex+aOffset, getter_AddRefs(startContent)); + } + + if (startContent) { + nsIFrame* existingFrame; + if (!IsListItemChild(this, startContent, &existingFrame)) { + return GetFirstItemBox(++aOffset, aCreated); + } + if (existingFrame) { + return existingFrame->IsBoxFrame() ? existingFrame : nullptr; + } + + // Either append the new frame, or prepend it (at index 0) + // XXX check here if frame was even created, it may not have been if + // display: none was on listitem content + bool isAppend = mRowsToPrepend <= 0; + + nsPresContext* presContext = PresContext(); + nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor(); + nsIFrame* topFrame = nullptr; + fc->CreateListBoxContent(presContext, this, nullptr, startContent, + &topFrame, isAppend, false, nullptr); + mTopFrame = topFrame; + if (mTopFrame) { + if (aCreated) + *aCreated = true; + + mBottomFrame = mTopFrame; + + return mTopFrame->IsBoxFrame() ? mTopFrame : nullptr; + } else + return GetFirstItemBox(++aOffset, 0); + } + + return nullptr; +} + +// +// Get the nsIFrame for the next visible listitem after aBox, and if none +// exists, create one. +// +nsIFrame* +nsListBoxBodyFrame::GetNextItemBox(nsIFrame* aBox, int32_t aOffset, + bool* aCreated) +{ + if (aCreated) + *aCreated = false; + + nsIFrame* result = aBox->GetNextSibling(); + + if (!result || result == mLinkupFrame || mRowsToPrepend > 0) { + // No result found. See if there's a content node that wants a frame. + nsIContent* prevContent = aBox->GetContent(); + nsIContent* parentContent = prevContent->GetParent(); + + int32_t i = parentContent->IndexOf(prevContent); + + uint32_t childCount = parentContent->GetChildCount(); + if (((uint32_t)i + aOffset + 1) < childCount) { + // There is a content node that wants a frame. + nsIContent *nextContent = parentContent->GetChildAt(i + aOffset + 1); + + nsIFrame* existingFrame; + if (!IsListItemChild(this, nextContent, &existingFrame)) { + return GetNextItemBox(aBox, ++aOffset, aCreated); + } + if (!existingFrame) { + // Either append the new frame, or insert it after the current frame + bool isAppend = result != mLinkupFrame && mRowsToPrepend <= 0; + nsIFrame* prevFrame = isAppend ? nullptr : aBox; + + nsPresContext* presContext = PresContext(); + nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor(); + fc->CreateListBoxContent(presContext, this, prevFrame, nextContent, + &result, isAppend, false, nullptr); + + if (result) { + if (aCreated) + *aCreated = true; + } else + return GetNextItemBox(aBox, ++aOffset, aCreated); + } else { + result = existingFrame; + } + + mLinkupFrame = nullptr; + } + } + + if (!result) + return nullptr; + + mBottomFrame = result; + + NS_ASSERTION(!result->IsBoxFrame() || result->GetParent() == this, + "returning frame that is not in childlist"); + + return result->IsBoxFrame() ? result : nullptr; +} + +bool +nsListBoxBodyFrame::ContinueReflow(nscoord height) +{ +#ifdef ACCESSIBILITY + if (nsIPresShell::IsAccessibilityActive()) { + // Create all the frames at once so screen readers and + // onscreen keyboards can see the full list right away + return true; + } +#endif + + if (height <= 0) { + nsIFrame* lastChild = GetLastFrame(); + nsIFrame* startingPoint = mBottomFrame; + if (startingPoint == nullptr) { + // We just want to delete everything but the first item. + startingPoint = GetFirstFrame(); + } + + if (lastChild != startingPoint) { + // We have some hangers on (probably caused by shrinking the size of the window). + // Nuke them. + nsIFrame* currFrame = startingPoint->GetNextSibling(); + nsBoxLayoutState state(PresContext()); + + nsCSSFrameConstructor* fc = + PresContext()->PresShell()->FrameConstructor(); + fc->BeginUpdate(); + while (currFrame) { + nsIFrame* nextFrame = currFrame->GetNextSibling(); + RemoveChildFrame(state, currFrame); + currFrame = nextFrame; + } + fc->EndUpdate(); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + return false; + } + else + return true; +} + +NS_IMETHODIMP +nsListBoxBodyFrame::ListBoxAppendFrames(nsFrameList& aFrameList) +{ + // append them after + nsBoxLayoutState state(PresContext()); + const nsFrameList::Slice& newFrames = mFrames.AppendFrames(nullptr, aFrameList); + if (mLayoutManager) + mLayoutManager->ChildrenAppended(this, state, newFrames); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + + return NS_OK; +} + +NS_IMETHODIMP +nsListBoxBodyFrame::ListBoxInsertFrames(nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + // insert the frames to our info list + nsBoxLayoutState state(PresContext()); + const nsFrameList::Slice& newFrames = + mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); + if (mLayoutManager) + mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + + return NS_OK; +} + +// +// Called by nsCSSFrameConstructor when a new listitem content is inserted. +// +void +nsListBoxBodyFrame::OnContentInserted(nsPresContext* aPresContext, nsIContent* aChildContent) +{ + if (mRowCount >= 0) + ++mRowCount; + + // The RDF content builder will build content nodes such that they are all + // ready when OnContentInserted is first called, meaning the first call + // to CreateRows will create all the frames, but OnContentInserted will + // still be called again for each content node - so we need to make sure + // that the frame for each content node hasn't already been created. + nsIFrame* childFrame = aChildContent->GetPrimaryFrame(); + if (childFrame) + return; + + int32_t siblingIndex; + nsCOMPtr<nsIContent> nextSiblingContent; + GetListItemNextSibling(aChildContent, getter_AddRefs(nextSiblingContent), siblingIndex); + + // if we're inserting our item before the first visible content, + // then we need to shift all rows down by one + if (siblingIndex >= 0 && siblingIndex-1 <= mCurrentIndex) { + mTopFrame = nullptr; + mRowsToPrepend = 1; + } else if (nextSiblingContent) { + // we may be inserting before a frame that is on screen + nsIFrame* nextSiblingFrame = nextSiblingContent->GetPrimaryFrame(); + mLinkupFrame = nextSiblingFrame; + } + + CreateRows(); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +// +// Called by nsCSSFrameConstructor when listitem content is removed. +// +void +nsListBoxBodyFrame::OnContentRemoved(nsPresContext* aPresContext, + nsIContent* aContainer, + nsIFrame* aChildFrame, + nsIContent* aOldNextSibling) +{ + NS_ASSERTION(!aChildFrame || aChildFrame->GetParent() == this, + "Removing frame that's not our child... Not good"); + + if (mRowCount >= 0) + --mRowCount; + + if (aContainer) { + if (!aChildFrame) { + // The row we are removing is out of view, so we need to try to + // determine the index of its next sibling. + int32_t siblingIndex = -1; + if (aOldNextSibling) { + nsCOMPtr<nsIContent> nextSiblingContent; + GetListItemNextSibling(aOldNextSibling, + getter_AddRefs(nextSiblingContent), + siblingIndex); + } + + // if the row being removed is off-screen and above the top frame, we need to + // adjust our top index and tell the scrollbar to shift up one row. + if (siblingIndex >= 0 && siblingIndex-1 < mCurrentIndex) { + NS_PRECONDITION(mCurrentIndex > 0, "mCurrentIndex > 0"); + --mCurrentIndex; + mYPosition = mCurrentIndex*mRowHeight; + nsWeakFrame weakChildFrame(aChildFrame); + VerticalScroll(mYPosition); + if (!weakChildFrame.IsAlive()) { + return; + } + } + } else if (mCurrentIndex > 0) { + // At this point, we know we have a scrollbar, and we need to know + // if we are scrolled to the last row. In this case, the behavior + // of the scrollbar is to stay locked to the bottom. Since we are + // removing visible content, the first visible row will have to move + // down by one, and we will have to insert a new frame at the top. + + // if the last content node has a frame, we are scrolled to the bottom + ChildIterator iter, last; + ChildIterator::Init(mContent, &iter, &last); + if (iter != last) { + iter = last; + --iter; + nsIContent *lastChild = *iter; + nsIFrame* lastChildFrame = lastChild->GetPrimaryFrame(); + + if (lastChildFrame) { + mTopFrame = nullptr; + mRowsToPrepend = 1; + --mCurrentIndex; + mYPosition = mCurrentIndex*mRowHeight; + nsWeakFrame weakChildFrame(aChildFrame); + VerticalScroll(mYPosition); + if (!weakChildFrame.IsAlive()) { + return; + } + } + } + } + } + + // if we're removing the top row, the new top row is the next row + if (mTopFrame && mTopFrame == aChildFrame) + mTopFrame = mTopFrame->GetNextSibling(); + + // Go ahead and delete the frame. + nsBoxLayoutState state(aPresContext); + if (aChildFrame) { + RemoveChildFrame(state, aChildFrame); + } + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void +nsListBoxBodyFrame::GetListItemContentAt(int32_t aIndex, nsIContent** aContent) +{ + *aContent = nullptr; + + int32_t itemsFound = 0; + ChildIterator iter, last; + for (ChildIterator::Init(mContent, &iter, &last); + iter != last; + ++iter) { + nsIContent *kid = (*iter); + if (kid->Tag() == nsGkAtoms::listitem) { + ++itemsFound; + if (itemsFound-1 == aIndex) { + *aContent = kid; + NS_IF_ADDREF(*aContent); + return; + } + } + } +} + +void +nsListBoxBodyFrame::GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, int32_t& aSiblingIndex) +{ + *aContent = nullptr; + aSiblingIndex = -1; + nsIContent *prevKid = nullptr; + ChildIterator iter, last; + for (ChildIterator::Init(mContent, &iter, &last); + iter != last; + ++iter) { + nsIContent *kid = (*iter); + + if (kid->Tag() == nsGkAtoms::listitem) { + ++aSiblingIndex; + if (prevKid == aListItem) { + *aContent = kid; + NS_IF_ADDREF(*aContent); + return; + } + } + prevKid = kid; + } + + aSiblingIndex = -1; // no match, so there is no next sibling +} + +void +nsListBoxBodyFrame::RemoveChildFrame(nsBoxLayoutState &aState, + nsIFrame *aFrame) +{ + MOZ_ASSERT(mFrames.ContainsFrame(aFrame)); + MOZ_ASSERT(aFrame != GetContentInsertionFrame()); + +#ifdef ACCESSIBILITY + nsAccessibilityService* accService = nsIPresShell::AccService(); + if (accService) { + nsIContent* content = aFrame->GetContent(); + accService->ContentRemoved(PresContext()->PresShell(), content->GetParent(), + content); + } +#endif + + mFrames.RemoveFrame(aFrame); + if (mLayoutManager) + mLayoutManager->ChildrenRemoved(this, aState, aFrame); + aFrame->Destroy(); +} + +// Creation Routines /////////////////////////////////////////////////////////////////////// + +already_AddRefed<nsBoxLayout> NS_NewListBoxLayout(); + +nsIFrame* +NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + nsCOMPtr<nsBoxLayout> layout = NS_NewListBoxLayout(); + if (!layout) { + return nullptr; + } + + return new (aPresShell) nsListBoxBodyFrame(aPresShell, aContext, layout); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsListBoxBodyFrame) diff --git a/layout/xul/base/src/nsListBoxBodyFrame.h b/layout/xul/base/src/nsListBoxBodyFrame.h new file mode 100644 index 000000000..85f965667 --- /dev/null +++ b/layout/xul/base/src/nsListBoxBodyFrame.h @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsListBoxBodyFrame_h +#define nsListBoxBodyFrame_h + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsBoxFrame.h" +#include "nsIListBoxObject.h" +#include "nsIScrollbarMediator.h" +#include "nsIReflowCallback.h" +#include "nsBoxLayoutState.h" +#include "nsThreadUtils.h" +#include "nsPIBoxObject.h" + +class nsPresContext; +class nsListScrollSmoother; +nsIFrame* NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + +class nsListBoxBodyFrame : public nsBoxFrame, + public nsIScrollbarMediator, + public nsIReflowCallback +{ + nsListBoxBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, + nsBoxLayout* aLayoutManager); + virtual ~nsListBoxBodyFrame(); + +public: + NS_DECL_QUERYFRAME_TARGET(nsListBoxBodyFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // non-virtual nsIListBoxObject + nsresult GetRowCount(int32_t *aResult); + nsresult GetNumberOfVisibleRows(int32_t *aResult); + nsresult GetIndexOfFirstVisibleRow(int32_t *aResult); + nsresult EnsureIndexIsVisible(int32_t aRowIndex); + nsresult ScrollToIndex(int32_t aRowIndex); + nsresult ScrollByLines(int32_t aNumLines); + nsresult GetItemAtIndex(int32_t aIndex, nsIDOMElement **aResult); + nsresult GetIndexOfItem(nsIDOMElement *aItem, int32_t *aResult); + + friend nsIFrame* NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + // nsIFrame + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType) MOZ_OVERRIDE; + + // nsIScrollbarMediator + NS_IMETHOD PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) MOZ_OVERRIDE; + NS_IMETHOD ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) MOZ_OVERRIDE; + NS_IMETHOD VisibilityChanged(bool aVisible) MOZ_OVERRIDE; + + // nsIReflowCallback + virtual bool ReflowFinished() MOZ_OVERRIDE; + virtual void ReflowCallbackCanceled() MOZ_OVERRIDE; + + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void MarkIntrinsicWidthsDirty() MOZ_OVERRIDE; + + virtual nsSize GetMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + // size calculation + int32_t GetRowCount(); + int32_t GetRowHeightAppUnits() { return mRowHeight; } + int32_t GetFixedRowSize(); + void SetRowHeight(nscoord aRowHeight); + nscoord GetYPosition(); + nscoord GetAvailableHeight(); + nscoord ComputeIntrinsicWidth(nsBoxLayoutState& aBoxLayoutState); + + // scrolling + nsresult InternalPositionChangedCallback(); + nsresult InternalPositionChanged(bool aUp, int32_t aDelta); + // Process pending position changed events, then do the position change. + // This can wipe out the frametree. + nsresult DoInternalPositionChangedSync(bool aUp, int32_t aDelta); + // Actually do the internal position change. This can wipe out the frametree + nsresult DoInternalPositionChanged(bool aUp, int32_t aDelta); + nsListScrollSmoother* GetSmoother(); + void VerticalScroll(int32_t aDelta); + + // frames + nsIFrame* GetFirstFrame(); + nsIFrame* GetLastFrame(); + + // lazy row creation and destruction + void CreateRows(); + void DestroyRows(int32_t& aRowsToLose); + void ReverseDestroyRows(int32_t& aRowsToLose); + nsIFrame* GetFirstItemBox(int32_t aOffset, bool* aCreated); + nsIFrame* GetNextItemBox(nsIFrame* aBox, int32_t aOffset, bool* aCreated); + bool ContinueReflow(nscoord height); + NS_IMETHOD ListBoxAppendFrames(nsFrameList& aFrameList); + NS_IMETHOD ListBoxInsertFrames(nsIFrame* aPrevFrame, nsFrameList& aFrameList); + void OnContentInserted(nsPresContext* aPresContext, nsIContent* aContent); + void OnContentRemoved(nsPresContext* aPresContext, nsIContent* aContainer, + nsIFrame* aChildFrame, nsIContent* aOldNextSibling); + + void GetListItemContentAt(int32_t aIndex, nsIContent** aContent); + void GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, int32_t& aSiblingIndex); + + void PostReflowCallback(); + + bool SetBoxObject(nsPIBoxObject* aBoxObject) + { + NS_ENSURE_TRUE(!mBoxObject, false); + mBoxObject = aBoxObject; + return true; + } + + virtual bool SupportsOrdinalsInChildren() MOZ_OVERRIDE; + + virtual bool ComputesOwnOverflowArea() MOZ_OVERRIDE { return true; } + +protected: + class nsPositionChangedEvent; + friend class nsPositionChangedEvent; + + class nsPositionChangedEvent : public nsRunnable + { + public: + nsPositionChangedEvent(nsListBoxBodyFrame* aFrame, + bool aUp, int32_t aDelta) : + mFrame(aFrame), mUp(aUp), mDelta(aDelta) + {} + + NS_IMETHOD Run() MOZ_OVERRIDE + { + if (!mFrame) { + return NS_OK; + } + + mFrame->mPendingPositionChangeEvents.RemoveElement(this); + + return mFrame->DoInternalPositionChanged(mUp, mDelta); + } + + void Revoke() { + mFrame = nullptr; + } + + nsListBoxBodyFrame* mFrame; + bool mUp; + int32_t mDelta; + }; + + void ComputeTotalRowCount(); + void RemoveChildFrame(nsBoxLayoutState &aState, nsIFrame *aChild); + + nsTArray< nsRefPtr<nsPositionChangedEvent> > mPendingPositionChangeEvents; + nsCOMPtr<nsPIBoxObject> mBoxObject; + + // frame markers + nsWeakFrame mTopFrame; + nsIFrame* mBottomFrame; + nsIFrame* mLinkupFrame; + + nsListScrollSmoother* mScrollSmoother; + + int32_t mRowsToPrepend; + + // row height + int32_t mRowCount; + nscoord mRowHeight; + nscoord mAvailableHeight; + nscoord mStringWidth; + + // scrolling + int32_t mCurrentIndex; // Row-based + int32_t mOldIndex; + int32_t mYPosition; + int32_t mTimePerRow; + + // row height + bool mRowHeightWasSet; + // scrolling + bool mScrolling; + bool mAdjustScroll; + + bool mReflowCallbackPosted; +}; + +#endif // nsListBoxBodyFrame_h diff --git a/layout/xul/base/src/nsListBoxLayout.cpp b/layout/xul/base/src/nsListBoxLayout.cpp new file mode 100644 index 000000000..029e4754b --- /dev/null +++ b/layout/xul/base/src/nsListBoxLayout.cpp @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsListBoxLayout.h" + +#include "nsListBoxBodyFrame.h" +#include "nsBox.h" +#include "nsBoxLayoutState.h" +#include "nsIScrollableFrame.h" +#include "nsIReflowCallback.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" + +nsListBoxLayout::nsListBoxLayout() : nsGridRowGroupLayout() +{ +} + +////////// nsBoxLayout ////////////// + +nsSize +nsListBoxLayout::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize pref = nsGridRowGroupLayout::GetPrefSize(aBox, aBoxLayoutState); + + nsListBoxBodyFrame* frame = static_cast<nsListBoxBodyFrame*>(aBox); + if (frame) { + nscoord rowheight = frame->GetRowHeightAppUnits(); + pref.height = frame->GetRowCount() * rowheight; + // Pad the height. + nscoord y = frame->GetAvailableHeight(); + if (pref.height > y && y > 0 && rowheight > 0) { + nscoord m = (pref.height-y)%rowheight; + nscoord remainder = m == 0 ? 0 : rowheight - m; + pref.height += remainder; + } + if (nsContentUtils::HasNonEmptyAttr(frame->GetContent(), kNameSpaceID_None, + nsGkAtoms::sizemode)) { + nscoord width = frame->ComputeIntrinsicWidth(aBoxLayoutState); + if (width > pref.width) + pref.width = width; + } + } + return pref; +} + +nsSize +nsListBoxLayout::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize minSize = nsGridRowGroupLayout::GetMinSize(aBox, aBoxLayoutState); + + nsListBoxBodyFrame* frame = static_cast<nsListBoxBodyFrame*>(aBox); + if (frame) { + nscoord rowheight = frame->GetRowHeightAppUnits(); + minSize.height = frame->GetRowCount() * rowheight; + // Pad the height. + nscoord y = frame->GetAvailableHeight(); + if (minSize.height > y && y > 0 && rowheight > 0) { + nscoord m = (minSize.height-y)%rowheight; + nscoord remainder = m == 0 ? 0 : rowheight - m; + minSize.height += remainder; + } + if (nsContentUtils::HasNonEmptyAttr(frame->GetContent(), kNameSpaceID_None, + nsGkAtoms::sizemode)) { + nscoord width = frame->ComputeIntrinsicWidth(aBoxLayoutState); + if (width > minSize.width) + minSize.width = width; + } + } + return minSize; +} + +nsSize +nsListBoxLayout::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize maxSize = nsGridRowGroupLayout::GetMaxSize(aBox, aBoxLayoutState); + + nsListBoxBodyFrame* frame = static_cast<nsListBoxBodyFrame*>(aBox); + if (frame) { + nscoord rowheight = frame->GetRowHeightAppUnits(); + maxSize.height = frame->GetRowCount() * rowheight; + // Pad the height. + nscoord y = frame->GetAvailableHeight(); + if (maxSize.height > y && y > 0 && rowheight > 0) { + nscoord m = (maxSize.height-y)%rowheight; + nscoord remainder = m == 0 ? 0 : rowheight - m; + maxSize.height += remainder; + } + } + return maxSize; +} + +NS_IMETHODIMP +nsListBoxLayout::Layout(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + return LayoutInternal(aBox, aState); +} + + +/////////// nsListBoxLayout ///////////////////////// + +/** + * Called to layout our our children. Does no frame construction + */ +NS_IMETHODIMP +nsListBoxLayout::LayoutInternal(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t redrawStart = -1; + + // Get the start y position. + nsListBoxBodyFrame* body = static_cast<nsListBoxBodyFrame*>(aBox); + if (!body) { + NS_ERROR("Frame encountered that isn't a listboxbody!"); + return NS_ERROR_FAILURE; + } + + nsMargin margin; + + // Get our client rect. + nsRect clientRect; + aBox->GetClientRect(clientRect); + + // Get the starting y position and the remaining available + // height. + nscoord availableHeight = body->GetAvailableHeight(); + nscoord yOffset = body->GetYPosition(); + + if (availableHeight <= 0) { + bool fixed = (body->GetFixedRowSize() != -1); + if (fixed) + availableHeight = 10; + else + return NS_OK; + } + + // run through all our currently created children + nsIFrame* box = body->GetChildBox(); + + // if the reason is resize or initial we must relayout. + nscoord rowHeight = body->GetRowHeightAppUnits(); + + while (box) { + // If this box is dirty or if it has dirty children, we + // call layout on it. + nsRect childRect(box->GetRect()); + box->GetMargin(margin); + + // relayout if we must or we are dirty or some of our children are dirty + // or the client area is wider than us + // XXXldb There should probably be a resize check here too! + if (NS_SUBTREE_DIRTY(box) || childRect.width < clientRect.width) { + childRect.x = 0; + childRect.y = yOffset; + childRect.width = clientRect.width; + + nsSize size = box->GetPrefSize(aState); + body->SetRowHeight(size.height); + + childRect.height = rowHeight; + + childRect.Deflate(margin); + box->SetBounds(aState, childRect); + box->Layout(aState); + } else { + // if the child did not need to be relayed out. Then its easy. + // Place the child by just grabbing its rect and adjusting the y. + int32_t newPos = yOffset+margin.top; + + // are we pushing down or pulling up any rows? + // Then we may have to redraw everything below the moved + // rows. + if (redrawStart == -1 && childRect.y != newPos) + redrawStart = newPos; + + childRect.y = newPos; + box->SetBounds(aState, childRect); + } + + // Ok now the available size gets smaller and we move the + // starting position of the next child down some. + nscoord size = childRect.height + margin.top + margin.bottom; + + yOffset += size; + availableHeight -= size; + + box = box->GetNextBox(); + } + + // We have enough available height left to add some more rows + // Since we can't do this during layout, we post a callback + // that will be processed after the reflow completes. + body->PostReflowCallback(); + + // if rows were pushed down or pulled up because some rows were added + // before them then redraw everything under the inserted rows. The inserted + // rows will automatically be redrawn because the were marked dirty on insertion. + if (redrawStart > -1) { + aBox->Redraw(aState); + } + + return NS_OK; +} + +// Creation Routines /////////////////////////////////////////////////////////////////////// + +already_AddRefed<nsBoxLayout> NS_NewListBoxLayout() +{ + nsRefPtr<nsBoxLayout> layout = new nsListBoxLayout(); + return layout.forget(); +} diff --git a/layout/xul/base/src/nsListBoxLayout.h b/layout/xul/base/src/nsListBoxLayout.h new file mode 100644 index 000000000..c0685c58b --- /dev/null +++ b/layout/xul/base/src/nsListBoxLayout.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsListBoxLayout_h___ +#define nsListBoxLayout_h___ + +#include "mozilla/Attributes.h" +#include "nsGridRowGroupLayout.h" + +class nsIFrame; +typedef class nsIFrame nsIFrame; +class nsBoxLayoutState; + +class nsListBoxLayout : public nsGridRowGroupLayout +{ +public: + nsListBoxLayout(); + + // nsBoxLayout + NS_IMETHOD Layout(nsIFrame* aBox, nsBoxLayoutState& aState) MOZ_OVERRIDE; + virtual nsSize GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + +protected: + NS_IMETHOD LayoutInternal(nsIFrame* aBox, nsBoxLayoutState& aState); +}; + +#endif + diff --git a/layout/xul/base/src/nsListBoxObject.cpp b/layout/xul/base/src/nsListBoxObject.cpp new file mode 100644 index 000000000..da6616afd --- /dev/null +++ b/layout/xul/base/src/nsListBoxObject.cpp @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsPIListBoxObject.h" +#include "nsBoxObject.h" +#include "nsIFrame.h" +#include "nsBindingManager.h" +#include "nsIDOMElement.h" +#include "nsIDOMNodeList.h" +#include "nsGkAtoms.h" +#include "nsIScrollableFrame.h" +#include "nsListBoxBodyFrame.h" + +class nsListBoxObject : public nsPIListBoxObject, public nsBoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSILISTBOXOBJECT + + // nsPIListBoxObject + virtual nsListBoxBodyFrame* GetListBoxBody(bool aFlush); + + nsListBoxObject(); + + // nsPIBoxObject + virtual void Clear(); + virtual void ClearCachedValues(); + +protected: + nsListBoxBodyFrame *mListBoxBody; +}; + +NS_IMPL_ISUPPORTS_INHERITED2(nsListBoxObject, nsBoxObject, nsIListBoxObject, + nsPIListBoxObject) + +nsListBoxObject::nsListBoxObject() + : mListBoxBody(nullptr) +{ +} + +////////////////////////////////////////////////////////////////////////// +//// nsIListBoxObject + +NS_IMETHODIMP +nsListBoxObject::GetRowCount(int32_t *aResult) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) + return body->GetRowCount(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsListBoxObject::GetNumberOfVisibleRows(int32_t *aResult) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) + return body->GetNumberOfVisibleRows(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsListBoxObject::GetIndexOfFirstVisibleRow(int32_t *aResult) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) + return body->GetIndexOfFirstVisibleRow(aResult); + return NS_OK; +} + +NS_IMETHODIMP nsListBoxObject::EnsureIndexIsVisible(int32_t aRowIndex) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) + return body->EnsureIndexIsVisible(aRowIndex); + return NS_OK; +} + +NS_IMETHODIMP +nsListBoxObject::ScrollToIndex(int32_t aRowIndex) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) + return body->ScrollToIndex(aRowIndex); + return NS_OK; +} + +NS_IMETHODIMP +nsListBoxObject::ScrollByLines(int32_t aNumLines) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) + return body->ScrollByLines(aNumLines); + return NS_OK; +} + +NS_IMETHODIMP +nsListBoxObject::GetItemAtIndex(int32_t index, nsIDOMElement **_retval) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) + return body->GetItemAtIndex(index, _retval); + return NS_OK; +} + +NS_IMETHODIMP +nsListBoxObject::GetIndexOfItem(nsIDOMElement* aElement, int32_t *aResult) +{ + *aResult = 0; + + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) + return body->GetIndexOfItem(aElement, aResult); + return NS_OK; +} + +////////////////////// + +static void +FindBodyContent(nsIContent* aParent, nsIContent** aResult) +{ + if (aParent->Tag() == nsGkAtoms::listboxbody) { + *aResult = aParent; + NS_IF_ADDREF(*aResult); + } + else { + nsCOMPtr<nsIDOMNodeList> kids; + aParent->OwnerDoc()->BindingManager()->GetXBLChildNodesFor(aParent, getter_AddRefs(kids)); + if (!kids) return; + + uint32_t i; + kids->GetLength(&i); + // start from the end, cuz we're smart and we know the listboxbody is probably at the end + while (i > 0) { + nsCOMPtr<nsIDOMNode> childNode; + kids->Item(--i, getter_AddRefs(childNode)); + nsCOMPtr<nsIContent> childContent(do_QueryInterface(childNode)); + FindBodyContent(childContent, aResult); + if (*aResult) + break; + } + } +} + +nsListBoxBodyFrame* +nsListBoxObject::GetListBoxBody(bool aFlush) +{ + if (mListBoxBody) { + return mListBoxBody; + } + + nsIPresShell* shell = GetPresShell(false); + if (!shell) { + return nullptr; + } + + nsIFrame* frame = aFlush ? + GetFrame(false) /* does Flush_Frames */ : + mContent->GetPrimaryFrame(); + if (!frame) + return nullptr; + + // Iterate over our content model children looking for the body. + nsCOMPtr<nsIContent> content; + FindBodyContent(frame->GetContent(), getter_AddRefs(content)); + + if (!content) + return nullptr; + + // this frame will be a nsGFXScrollFrame + frame = content->GetPrimaryFrame(); + if (!frame) + return nullptr; + nsIScrollableFrame* scrollFrame = do_QueryFrame(frame); + if (!scrollFrame) + return nullptr; + + // this frame will be the one we want + nsIFrame* yeahBaby = scrollFrame->GetScrolledFrame(); + if (!yeahBaby) + return nullptr; + + // It's a frame. Refcounts are irrelevant. + nsListBoxBodyFrame* listBoxBody = do_QueryFrame(yeahBaby); + NS_ENSURE_TRUE(listBoxBody && + listBoxBody->SetBoxObject(this), + nullptr); + mListBoxBody = listBoxBody; + return mListBoxBody; +} + +void +nsListBoxObject::Clear() +{ + ClearCachedValues(); + + nsBoxObject::Clear(); +} + +void +nsListBoxObject::ClearCachedValues() +{ + mListBoxBody = nullptr; +} + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewListBoxObject(nsIBoxObject** aResult) +{ + *aResult = new nsListBoxObject; + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/layout/xul/base/src/nsListItemFrame.cpp b/layout/xul/base/src/nsListItemFrame.cpp new file mode 100644 index 000000000..6255ecacf --- /dev/null +++ b/layout/xul/base/src/nsListItemFrame.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsListItemFrame.h" + +#include "nsCOMPtr.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsDisplayList.h" +#include "nsBoxLayout.h" +#include <algorithm> + +nsListItemFrame::nsListItemFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + bool aIsRoot, + nsBoxLayout* aLayoutManager) + : nsGridRowLeafFrame(aPresShell, aContext, aIsRoot, aLayoutManager) +{ +} + +nsListItemFrame::~nsListItemFrame() +{ +} + +nsSize +nsListItemFrame::GetPrefSize(nsBoxLayoutState& aState) +{ + nsSize size = nsBoxFrame::GetPrefSize(aState); + DISPLAY_PREF_SIZE(this, size); + + // guarantee that our preferred height doesn't exceed the standard + // listbox row height + size.height = std::max(mRect.height, size.height); + return size; +} + +void +nsListItemFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (aBuilder->IsForEventDelivery()) { + if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::allowevents, + nsGkAtoms::_true, eCaseMatters)) + return; + } + + nsGridRowLeafFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); +} + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout(); + +nsIFrame* +NS_NewListItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + nsCOMPtr<nsBoxLayout> layout = NS_NewGridRowLeafLayout(); + if (!layout) { + return nullptr; + } + + return new (aPresShell) nsListItemFrame(aPresShell, aContext, false, layout); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsListItemFrame) diff --git a/layout/xul/base/src/nsListItemFrame.h b/layout/xul/base/src/nsListItemFrame.h new file mode 100644 index 000000000..f28a79a52 --- /dev/null +++ b/layout/xul/base/src/nsListItemFrame.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include "nsGridRowLeafFrame.h" + +nsIFrame* NS_NewListItemFrame(nsIPresShell* aPresShell, + nsStyleContext *aContext); + +class nsListItemFrame : public nsGridRowLeafFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewListItemFrame(nsIPresShell* aPresShell, + nsStyleContext *aContext); + + // overridden so that children of listitems don't handle mouse events, + // unless allowevents="true" is specified on the listitem + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + virtual nsSize GetPrefSize(nsBoxLayoutState& aState) MOZ_OVERRIDE; + +protected: + nsListItemFrame(nsIPresShell* aPresShell, + nsStyleContext *aContext, + bool aIsRoot = false, + nsBoxLayout* aLayoutManager = nullptr); + virtual ~nsListItemFrame(); + +}; // class nsListItemFrame diff --git a/layout/xul/base/src/nsMenuBarFrame.cpp b/layout/xul/base/src/nsMenuBarFrame.cpp new file mode 100644 index 000000000..f57ed5dc4 --- /dev/null +++ b/layout/xul/base/src/nsMenuBarFrame.cpp @@ -0,0 +1,438 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMenuBarFrame.h" +#include "nsIServiceManager.h" +#include "nsIContent.h" +#include "nsIAtom.h" +#include "nsPresContext.h" +#include "nsStyleContext.h" +#include "nsCSSRendering.h" +#include "nsINameSpaceManager.h" +#include "nsIDocument.h" +#include "nsGkAtoms.h" +#include "nsMenuFrame.h" +#include "nsMenuPopupFrame.h" +#include "nsGUIEvent.h" +#include "nsUnicharUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsCSSFrameConstructor.h" +#ifdef XP_WIN +#include "nsISound.h" +#include "nsWidgetsCID.h" +#endif +#include "nsContentUtils.h" +#include "nsUTF8Utils.h" + + +// +// NS_NewMenuBarFrame +// +// Wrapper for creating a new menu Bar container +// +nsIFrame* +NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsMenuBarFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame) + +NS_QUERYFRAME_HEAD(nsMenuBarFrame) + NS_QUERYFRAME_ENTRY(nsMenuBarFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +// +// nsMenuBarFrame cntr +// +nsMenuBarFrame::nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsBoxFrame(aShell, aContext), + mMenuBarListener(nullptr), + mStayActive(false), + mIsActive(false), + mCurrentMenu(nullptr), + mTarget(nullptr) +{ +} // cntr + +void +nsMenuBarFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + // Create the menu bar listener. + mMenuBarListener = new nsMenuBarListener(this); + NS_ADDREF(mMenuBarListener); + + // Hook up the menu bar as a key listener on the whole document. It will see every + // key press that occurs, but after everyone else does. + mTarget = aContent->GetDocument(); + + // Also hook up the listener to the window listening for focus events. This is so we can keep proper + // state as the user alt-tabs through processes. + + mTarget->AddEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false); + mTarget->AddEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false); + mTarget->AddEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false); + + // mousedown event should be handled in all phase + mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true); + mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false); + mTarget->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true); +} + +NS_IMETHODIMP +nsMenuBarFrame::SetActive(bool aActiveFlag) +{ + // If the activity is not changed, there is nothing to do. + if (mIsActive == aActiveFlag) + return NS_OK; + + if (!aActiveFlag) { + // Don't deactivate when switching between menus on the menubar. + if (mStayActive) + return NS_OK; + + // if there is a request to deactivate the menu bar, check to see whether + // there is a menu popup open for the menu bar. In this case, don't + // deactivate the menu bar. + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && pm->IsPopupOpenForMenuParent(this)) + return NS_OK; + } + + mIsActive = aActiveFlag; + if (mIsActive) { + InstallKeyboardNavigator(); + } + else { + mActiveByKeyboard = false; + RemoveKeyboardNavigator(); + } + + NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive"); + NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive"); + + FireDOMEvent(mIsActive ? active : inactive, mContent); + + return NS_OK; +} + +nsMenuFrame* +nsMenuBarFrame::ToggleMenuActiveState() +{ + if (mIsActive) { + // Deactivate the menu bar + SetActive(false); + if (mCurrentMenu) { + nsMenuFrame* closeframe = mCurrentMenu; + closeframe->SelectMenu(false); + mCurrentMenu = nullptr; + return closeframe; + } + } + else { + // if the menu bar is already selected (eg. mouseover), deselect it + if (mCurrentMenu) + mCurrentMenu->SelectMenu(false); + + // Set the active menu to be the top left item (e.g., the File menu). + // We use an attribute called "menuactive" to track the current + // active menu. + nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nullptr, false); + if (firstFrame) { + // Activate the menu bar + SetActive(true); + +#ifdef MOZ_WIDGET_GTK2 + firstFrame->OpenMenu(true); +#else + firstFrame->SelectMenu(true); +#endif + + // Track this item for keyboard navigation. + mCurrentMenu = firstFrame; + } + } + + return nullptr; +} + +static void +GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild, + nsIFrame** aResult) +{ + nsIContent* child = nullptr; + if (aChild) + child = aChild->GetContent(); + aShell->FrameConstructor()->GetInsertionPoint(aFrame, child, aResult); +} + +nsMenuFrame* +nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) +{ + uint32_t charCode; + aKeyEvent->GetCharCode(&charCode); + + nsAutoTArray<uint32_t, 10> accessKeys; + nsEvent* nativeEvent = nsContentUtils::GetNativeEvent(aKeyEvent); + nsKeyEvent* nativeKeyEvent = static_cast<nsKeyEvent*>(nativeEvent); + if (nativeKeyEvent) + nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, accessKeys); + if (accessKeys.IsEmpty() && charCode) + accessKeys.AppendElement(charCode); + + if (accessKeys.IsEmpty()) + return nullptr; // no character was pressed so just return + + // Enumerate over our list of frames. + nsIFrame* immediateParent = nullptr; + GetInsertionPoint(PresContext()->PresShell(), this, nullptr, &immediateParent); + if (!immediateParent) + immediateParent = this; + + // Find a most preferred accesskey which should be returned. + nsIFrame* foundMenu = nullptr; + uint32_t foundIndex = accessKeys.NoIndex; + nsIFrame* currFrame = immediateParent->GetFirstPrincipalChild(); + + while (currFrame) { + nsIContent* current = currFrame->GetContent(); + + // See if it's a menu item. + if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, false)) { + // Get the shortcut attribute. + nsAutoString shortcutKey; + current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey); + if (!shortcutKey.IsEmpty()) { + ToLowerCase(shortcutKey); + const PRUnichar* start = shortcutKey.BeginReading(); + const PRUnichar* end = shortcutKey.EndReading(); + uint32_t ch = UTF16CharEnumerator::NextChar(&start, end); + uint32_t index = accessKeys.IndexOf(ch); + if (index != accessKeys.NoIndex && + (foundIndex == accessKeys.NoIndex || index < foundIndex)) { + foundMenu = currFrame; + foundIndex = index; + } + } + } + currFrame = currFrame->GetNextSibling(); + } + if (foundMenu) { + return do_QueryFrame(foundMenu); + } + + // didn't find a matching menu item +#ifdef XP_WIN + // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar + if (mIsActive) { + nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1"); + if (soundInterface) + soundInterface->Beep(); + } + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny); + if (popup) + pm->HidePopup(popup->GetContent(), true, true, true); + } + + SetCurrentMenuItem(nullptr); + SetActive(false); + +#endif // #ifdef XP_WIN + + return nullptr; +} + +/* virtual */ nsMenuFrame* +nsMenuBarFrame::GetCurrentMenuItem() +{ + return mCurrentMenu; +} + +NS_IMETHODIMP +nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) +{ + if (mCurrentMenu == aMenuItem) + return NS_OK; + + if (mCurrentMenu) + mCurrentMenu->SelectMenu(false); + + if (aMenuItem) + aMenuItem->SelectMenu(true); + + mCurrentMenu = aMenuItem; + + return NS_OK; +} + +void +nsMenuBarFrame::CurrentMenuIsBeingDestroyed() +{ + mCurrentMenu->SelectMenu(false); + mCurrentMenu = nullptr; +} + +class nsMenuBarSwitchMenu : public nsRunnable +{ +public: + nsMenuBarSwitchMenu(nsIContent* aMenuBar, + nsIContent *aOldMenu, + nsIContent *aNewMenu, + bool aSelectFirstItem) + : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu), + mSelectFirstItem(aSelectFirstItem) + { + } + + NS_IMETHOD Run() + { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) + return NS_ERROR_UNEXPECTED; + + // if switching from one menu to another, set a flag so that the call to + // HidePopup doesn't deactivate the menubar when the first menu closes. + nsMenuBarFrame* menubar = nullptr; + if (mOldMenu && mNewMenu) { + menubar = do_QueryFrame(mMenuBar->GetPrimaryFrame()); + if (menubar) + menubar->SetStayActive(true); + } + + if (mOldMenu) { + nsWeakFrame weakMenuBar(menubar); + pm->HidePopup(mOldMenu, false, false, false); + // clear the flag again + if (mNewMenu && weakMenuBar.IsAlive()) + menubar->SetStayActive(false); + } + + if (mNewMenu) + pm->ShowMenu(mNewMenu, mSelectFirstItem, false); + + return NS_OK; + } + +private: + nsCOMPtr<nsIContent> mMenuBar; + nsCOMPtr<nsIContent> mOldMenu; + nsCOMPtr<nsIContent> mNewMenu; + bool mSelectFirstItem; +}; + +NS_IMETHODIMP +nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, + bool aSelectFirstItem) +{ + if (mCurrentMenu == aMenuItem) + return NS_OK; + + // check if there's an open context menu, we ignore this + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && pm->HasContextMenu(nullptr)) + return NS_OK; + + nsIContent* aOldMenu = nullptr, *aNewMenu = nullptr; + + // Unset the current child. + bool wasOpen = false; + if (mCurrentMenu) { + wasOpen = mCurrentMenu->IsOpen(); + mCurrentMenu->SelectMenu(false); + if (wasOpen) { + nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup(); + if (popupFrame) + aOldMenu = popupFrame->GetContent(); + } + } + + // set to null first in case the IsAlive check below returns false + mCurrentMenu = nullptr; + + // Set the new child. + if (aMenuItem) { + nsCOMPtr<nsIContent> content = aMenuItem->GetContent(); + aMenuItem->SelectMenu(true); + mCurrentMenu = aMenuItem; + if (wasOpen && !aMenuItem->IsDisabled()) + aNewMenu = content; + } + + // use an event so that hiding and showing can be done synchronously, which + // avoids flickering + nsCOMPtr<nsIRunnable> event = + new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem); + return NS_DispatchToCurrentThread(event); +} + +nsMenuFrame* +nsMenuBarFrame::Enter(nsGUIEvent* aEvent) +{ + if (!mCurrentMenu) + return nullptr; + + if (mCurrentMenu->IsOpen()) + return mCurrentMenu->Enter(aEvent); + + return mCurrentMenu; +} + +bool +nsMenuBarFrame::MenuClosed() +{ + SetActive(false); + if (!mIsActive && mCurrentMenu) { + mCurrentMenu->SelectMenu(false); + mCurrentMenu = nullptr; + return true; + } + return false; +} + +void +nsMenuBarFrame::InstallKeyboardNavigator() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + pm->SetActiveMenuBar(this, true); +} + +void +nsMenuBarFrame::RemoveKeyboardNavigator() +{ + if (!mIsActive) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + pm->SetActiveMenuBar(this, false); + } +} + +void +nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + pm->SetActiveMenuBar(this, false); + + mTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false); + mTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false); + mTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false); + + mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true); + mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false); + mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true); + + NS_IF_RELEASE(mMenuBarListener); + + nsBoxFrame::DestroyFrom(aDestructRoot); +} diff --git a/layout/xul/base/src/nsMenuBarFrame.h b/layout/xul/base/src/nsMenuBarFrame.h new file mode 100644 index 000000000..ae1dbd36f --- /dev/null +++ b/layout/xul/base/src/nsMenuBarFrame.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// nsMenuBarFrame +// + +#ifndef nsMenuBarFrame_h__ +#define nsMenuBarFrame_h__ + +#include "mozilla/Attributes.h" +#include "nsIAtom.h" +#include "nsCOMPtr.h" +#include "nsBoxFrame.h" +#include "nsMenuFrame.h" +#include "nsMenuBarListener.h" +#include "nsMenuParent.h" + +class nsIContent; + +nsIFrame* NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsMenuBarFrame : public nsBoxFrame, public nsMenuParent +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsMenuBarFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext); + + // nsMenuParent interface + virtual nsMenuFrame* GetCurrentMenuItem() MOZ_OVERRIDE; + NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) MOZ_OVERRIDE; + virtual void CurrentMenuIsBeingDestroyed() MOZ_OVERRIDE; + NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, bool aSelectFirstItem) MOZ_OVERRIDE; + + NS_IMETHOD SetActive(bool aActiveFlag) MOZ_OVERRIDE; + + virtual bool IsMenuBar() MOZ_OVERRIDE { return true; } + virtual bool IsContextMenu() MOZ_OVERRIDE { return false; } + virtual bool IsActive() MOZ_OVERRIDE { return mIsActive; } + virtual bool IsMenu() MOZ_OVERRIDE { return false; } + virtual bool IsOpen() MOZ_OVERRIDE { return true; } // menubars are considered always open + + bool IsMenuOpen() { return mCurrentMenu && mCurrentMenu->IsOpen(); } + + void InstallKeyboardNavigator(); + void RemoveKeyboardNavigator(); + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + virtual void LockMenuUntilClosed(bool aLock) MOZ_OVERRIDE {} + virtual bool IsMenuLocked() MOZ_OVERRIDE { return false; } + +// Non-interface helpers + + // The 'stay active' flag is set when navigating from one top-level menu + // to another, to prevent the menubar from deactivating and submenus from + // firing extra DOMMenuItemActive events. + bool GetStayActive() { return mStayActive; } + void SetStayActive(bool aStayActive) { mStayActive = aStayActive; } + + // Called when a menu on the menu bar is clicked on. Returns a menu if one + // needs to be closed. + nsMenuFrame* ToggleMenuActiveState(); + + bool IsActiveByKeyboard() { return mActiveByKeyboard; } + void SetActiveByKeyboard() { mActiveByKeyboard = true; } + + // indicate that a menu on the menubar was closed. Returns true if the caller + // may deselect the menuitem. + virtual bool MenuClosed() MOZ_OVERRIDE; + + // Called when Enter is pressed while the menubar is focused. If the current + // menu is open, let the child handle the key. + nsMenuFrame* Enter(nsGUIEvent* aEvent); + + // Used to handle ALT+key combos + nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent); + + virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE + { + // Override bogus IsFrameOfType in nsBoxFrame. + if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced)) + return false; + return nsBoxFrame::IsFrameOfType(aFlags); + } + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE + { + return MakeFrameName(NS_LITERAL_STRING("MenuBar"), aResult); + } +#endif + +protected: + nsMenuBarListener* mMenuBarListener; // The listener that tells us about key and mouse events. + + // flag that is temporarily set when switching from one menu on the menubar to another + // to indicate that the menubar should not be deactivated. + bool mStayActive; + + bool mIsActive; // Whether or not the menu bar is active (a menu item is highlighted or shown). + + // whether the menubar was made active via the keyboard. + bool mActiveByKeyboard; + + // The current menu that is active (highlighted), which may not be open. This will + // be null if no menu is active. + nsMenuFrame* mCurrentMenu; + + mozilla::dom::EventTarget* mTarget; + +}; // class nsMenuBarFrame + +#endif diff --git a/layout/xul/base/src/nsMenuBarListener.cpp b/layout/xul/base/src/nsMenuBarListener.cpp new file mode 100644 index 000000000..91788ee07 --- /dev/null +++ b/layout/xul/base/src/nsMenuBarListener.cpp @@ -0,0 +1,426 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMenuBarListener.h" +#include "nsMenuBarFrame.h" +#include "nsMenuPopupFrame.h" +#include "nsIDOMEvent.h" +#include "nsGUIEvent.h" + +// Drag & Drop, Clipboard +#include "nsIServiceManager.h" +#include "nsWidgetsCID.h" +#include "nsCOMPtr.h" +#include "nsIDOMKeyEvent.h" +#include "nsIContent.h" +#include "nsIDOMNode.h" +#include "nsIDOMElement.h" + +#include "nsContentUtils.h" +#include "mozilla/Preferences.h" + +using namespace mozilla; + +/* + * nsMenuBarListener implementation + */ + +NS_IMPL_ISUPPORTS1(nsMenuBarListener, nsIDOMEventListener) + +#define MODIFIER_SHIFT 1 +#define MODIFIER_CONTROL 2 +#define MODIFIER_ALT 4 +#define MODIFIER_META 8 +#define MODIFIER_OS 16 + +//////////////////////////////////////////////////////////////////////// + +int32_t nsMenuBarListener::mAccessKey = -1; +uint32_t nsMenuBarListener::mAccessKeyMask = 0; +bool nsMenuBarListener::mAccessKeyFocuses = false; + +nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBar) + :mAccessKeyDown(false), mAccessKeyDownCanceled(false) +{ + mMenuBarFrame = aMenuBar; +} + +//////////////////////////////////////////////////////////////////////// +nsMenuBarListener::~nsMenuBarListener() +{ +} + +nsresult +nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey) +{ + if (!aAccessKey) + return NS_ERROR_INVALID_POINTER; + InitAccessKey(); + *aAccessKey = mAccessKey; + return NS_OK; +} + +void nsMenuBarListener::InitAccessKey() +{ + if (mAccessKey >= 0) + return; + + // Compiled-in defaults, in case we can't get LookAndFeel -- + // mac doesn't have menu shortcuts, other platforms use alt. +#ifdef XP_MACOSX + mAccessKey = 0; + mAccessKeyMask = 0; +#else + mAccessKey = nsIDOMKeyEvent::DOM_VK_ALT; + mAccessKeyMask = MODIFIER_ALT; +#endif + + // Get the menu access key value from prefs, overriding the default: + mAccessKey = Preferences::GetInt("ui.key.menuAccessKey", mAccessKey); + if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) + mAccessKeyMask = MODIFIER_SHIFT; + else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) + mAccessKeyMask = MODIFIER_CONTROL; + else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) + mAccessKeyMask = MODIFIER_ALT; + else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) + mAccessKeyMask = MODIFIER_META; + else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_WIN) + mAccessKeyMask = MODIFIER_OS; + + mAccessKeyFocuses = Preferences::GetBool("ui.key.menuAccessKeyFocuses"); +} + +void +nsMenuBarListener::ToggleMenuActiveState() +{ + nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState(); + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && closemenu) { + nsMenuPopupFrame* popupFrame = closemenu->GetPopup(); + if (popupFrame) + pm->HidePopup(popupFrame->GetContent(), false, false, true); + } +} + +//////////////////////////////////////////////////////////////////////// +nsresult +nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent) +{ + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); + if (!keyEvent) { + return NS_OK; + } + + InitAccessKey(); + + //handlers shouldn't be triggered by non-trusted events. + bool trustedEvent = false; + aKeyEvent->GetIsTrusted(&trustedEvent); + + if (!trustedEvent) { + return NS_OK; + } + + if (mAccessKey && mAccessKeyFocuses) + { + bool defaultPrevented = false; + aKeyEvent->GetDefaultPrevented(&defaultPrevented); + + // On a press of the ALT key by itself, we toggle the menu's + // active/inactive state. + // Get the ascii key code. + uint32_t theChar; + keyEvent->GetKeyCode(&theChar); + + if (!defaultPrevented && mAccessKeyDown && !mAccessKeyDownCanceled && + (int32_t)theChar == mAccessKey) + { + // The access key was down and is now up, and no other + // keys were pressed in between. + if (!mMenuBarFrame->IsActive()) { + mMenuBarFrame->SetActiveByKeyboard(); + } + ToggleMenuActiveState(); + } + mAccessKeyDown = false; + mAccessKeyDownCanceled = false; + + bool active = mMenuBarFrame->IsActive(); + if (active) { + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + return NS_OK; // I am consuming event + } + } + + return NS_OK; // means I am NOT consuming event +} + +//////////////////////////////////////////////////////////////////////// +nsresult +nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) +{ + // if event has already been handled, bail + if (aKeyEvent) { + bool eventHandled = false; + aKeyEvent->GetDefaultPrevented(&eventHandled); + if (eventHandled) { + return NS_OK; // don't consume event + } + } + + //handlers shouldn't be triggered by non-trusted events. + bool trustedEvent = false; + if (aKeyEvent) { + aKeyEvent->GetIsTrusted(&trustedEvent); + } + + if (!trustedEvent) + return NS_OK; + + nsresult retVal = NS_OK; // default is to not consume event + + InitAccessKey(); + + if (mAccessKey) + { + bool preventDefault; + aKeyEvent->GetDefaultPrevented(&preventDefault); + if (!preventDefault) { + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); + uint32_t keyCode, charCode; + keyEvent->GetKeyCode(&keyCode); + keyEvent->GetCharCode(&charCode); + + bool hasAccessKeyCandidates = charCode != 0; + if (!hasAccessKeyCandidates) { + nsEvent* nativeEvent = nsContentUtils::GetNativeEvent(aKeyEvent); + nsKeyEvent* nativeKeyEvent = static_cast<nsKeyEvent*>(nativeEvent); + if (nativeKeyEvent) { + nsAutoTArray<uint32_t, 10> keys; + nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, keys); + hasAccessKeyCandidates = !keys.IsEmpty(); + } + } + + // Cancel the access key flag unless we are pressing the access key. + if (keyCode != (uint32_t)mAccessKey) { + mAccessKeyDownCanceled = true; + } + + if (IsAccessKeyPressed(keyEvent) && hasAccessKeyCandidates) { + // Do shortcut navigation. + // A letter was pressed. We want to see if a shortcut gets matched. If + // so, we'll know the menu got activated. + nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent); + if (result) { + mMenuBarFrame->SetActiveByKeyboard(); + mMenuBarFrame->SetActive(true); + result->OpenMenu(true); + + // The opened menu will listen next keyup event. + // Therefore, we should clear the keydown flags here. + mAccessKeyDown = mAccessKeyDownCanceled = false; + + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + retVal = NS_OK; // I am consuming event + } + } +#ifndef XP_MACOSX + // Also need to handle F10 specially on Non-Mac platform. + else if (keyCode == NS_VK_F10) { + if ((GetModifiers(keyEvent) & ~MODIFIER_CONTROL) == 0) { + // The F10 key just went down by itself or with ctrl pressed. + // In Windows, both of these activate the menu bar. + mMenuBarFrame->SetActiveByKeyboard(); + ToggleMenuActiveState(); + + if (mMenuBarFrame->IsActive()) { + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + return NS_OK; // consume the event + } + } + } +#endif // !XP_MACOSX + } + } + + return retVal; +} + +bool +nsMenuBarListener::IsAccessKeyPressed(nsIDOMKeyEvent* aKeyEvent) +{ + InitAccessKey(); + // No other modifiers are allowed to be down except for Shift. + uint32_t modifiers = GetModifiers(aKeyEvent); + + return (mAccessKeyMask != MODIFIER_SHIFT && + (modifiers & mAccessKeyMask) && + (modifiers & ~(mAccessKeyMask | MODIFIER_SHIFT)) == 0); +} + +uint32_t +nsMenuBarListener::GetModifiers(nsIDOMKeyEvent* aKeyEvent) +{ + uint32_t modifiers = 0; + nsInputEvent* inputEvent = + static_cast<nsInputEvent*>(aKeyEvent->GetInternalNSEvent()); + MOZ_ASSERT(inputEvent); + + if (inputEvent->IsShift()) { + modifiers |= MODIFIER_SHIFT; + } + + if (inputEvent->IsControl()) { + modifiers |= MODIFIER_CONTROL; + } + + if (inputEvent->IsAlt()) { + modifiers |= MODIFIER_ALT; + } + + if (inputEvent->IsMeta()) { + modifiers |= MODIFIER_META; + } + + if (inputEvent->IsOS()) { + modifiers |= MODIFIER_OS; + } + + return modifiers; +} + +//////////////////////////////////////////////////////////////////////// +nsresult +nsMenuBarListener::KeyDown(nsIDOMEvent* aKeyEvent) +{ + InitAccessKey(); + + //handlers shouldn't be triggered by non-trusted events. + bool trustedEvent = false; + if (aKeyEvent) { + aKeyEvent->GetIsTrusted(&trustedEvent); + } + + if (!trustedEvent) + return NS_OK; + + if (mAccessKey && mAccessKeyFocuses) + { + bool defaultPrevented = false; + aKeyEvent->GetDefaultPrevented(&defaultPrevented); + + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); + uint32_t theChar; + keyEvent->GetKeyCode(&theChar); + + // No other modifiers can be down. + // Especially CTRL. CTRL+ALT == AltGR, and we'll fuck up on non-US + // enhanced 102-key keyboards if we don't check this. + bool isAccessKeyDownEvent = + ((theChar == (uint32_t)mAccessKey) && + (GetModifiers(keyEvent) & ~mAccessKeyMask) == 0); + + if (!mAccessKeyDown) { + // If accesskey isn't being pressed and the key isn't the accesskey, + // ignore the event. + if (!isAccessKeyDownEvent) { + return NS_OK; + } + + // Otherwise, accept the accesskey state. + mAccessKeyDown = true; + // If default is prevented already, cancel the access key down. + mAccessKeyDownCanceled = defaultPrevented; + return NS_OK; + } + + // If the pressed accesskey was canceled already or the event was + // consumed already, ignore the event. + if (mAccessKeyDownCanceled || defaultPrevented) { + return NS_OK; + } + + // Some key other than the access key just went down, + // so we won't activate the menu bar when the access key is released. + mAccessKeyDownCanceled = !isAccessKeyDownEvent; + } + + return NS_OK; // means I am NOT consuming event +} + +//////////////////////////////////////////////////////////////////////// + +nsresult +nsMenuBarListener::Blur(nsIDOMEvent* aEvent) +{ + if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) { + ToggleMenuActiveState(); + } + // Reset the accesskey state because we cannot receive the keyup event for + // the pressing accesskey. + mAccessKeyDown = false; + mAccessKeyDownCanceled = false; + return NS_OK; // means I am NOT consuming event +} + +//////////////////////////////////////////////////////////////////////// +nsresult +nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent) +{ + // NOTE: MouseDown method listens all phases + + // Even if the mousedown event is canceled, it means the user don't want + // to activate the menu. Therefore, we need to record it at capturing (or + // target) phase. + if (mAccessKeyDown) { + mAccessKeyDownCanceled = true; + } + + uint16_t phase = 0; + nsresult rv = aMouseEvent->GetEventPhase(&phase); + NS_ENSURE_SUCCESS(rv, rv); + // Don't do anything at capturing phase, any behavior should be cancelable. + if (phase == nsIDOMEvent::CAPTURING_PHASE) { + return NS_OK; + } + + if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) + ToggleMenuActiveState(); + + return NS_OK; // means I am NOT consuming event +} + +//////////////////////////////////////////////////////////////////////// +nsresult +nsMenuBarListener::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString eventType; + aEvent->GetType(eventType); + + if (eventType.EqualsLiteral("keyup")) { + return KeyUp(aEvent); + } + if (eventType.EqualsLiteral("keydown")) { + return KeyDown(aEvent); + } + if (eventType.EqualsLiteral("keypress")) { + return KeyPress(aEvent); + } + if (eventType.EqualsLiteral("blur")) { + return Blur(aEvent); + } + if (eventType.EqualsLiteral("mousedown")) { + return MouseDown(aEvent); + } + + NS_ABORT(); + + return NS_OK; +} diff --git a/layout/xul/base/src/nsMenuBarListener.h b/layout/xul/base/src/nsMenuBarListener.h new file mode 100644 index 000000000..7cda4b65e --- /dev/null +++ b/layout/xul/base/src/nsMenuBarListener.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsMenuBarListener_h__ +#define nsMenuBarListener_h__ + +#include "mozilla/Attributes.h" +#include "nsIDOMEventListener.h" + +// X.h defines KeyPress +#ifdef KeyPress +#undef KeyPress +#endif + +class nsMenuBarFrame; +class nsIDOMKeyEvent; + +/** editor Implementation of the DragListener interface + */ +class nsMenuBarListener : public nsIDOMEventListener +{ +public: + /** default constructor + */ + nsMenuBarListener(nsMenuBarFrame* aMenuBar); + /** default destructor + */ + virtual ~nsMenuBarListener(); + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE; + + nsresult KeyUp(nsIDOMEvent* aMouseEvent); + nsresult KeyDown(nsIDOMEvent* aMouseEvent); + nsresult KeyPress(nsIDOMEvent* aMouseEvent); + nsresult Blur(nsIDOMEvent* aEvent); + nsresult MouseDown(nsIDOMEvent* aMouseEvent); + + static nsresult GetMenuAccessKey(int32_t* aAccessKey); + + NS_DECL_ISUPPORTS + + static bool IsAccessKeyPressed(nsIDOMKeyEvent* event); + +protected: + static void InitAccessKey(); + + static uint32_t GetModifiers(nsIDOMKeyEvent* event); + + // This should only be called by the nsMenuBarListener during event dispatch, + // thus ensuring that this doesn't get destroyed during the process. + void ToggleMenuActiveState(); + + nsMenuBarFrame* mMenuBarFrame; // The menu bar object. + // Whether or not the ALT key is currently down. + bool mAccessKeyDown; + // Whether or not the ALT key down is canceled by other action. + bool mAccessKeyDownCanceled; + static bool mAccessKeyFocuses; // Does the access key by itself focus the menubar? + static int32_t mAccessKey; // See nsIDOMKeyEvent.h for sample values + static uint32_t mAccessKeyMask;// Modifier mask for the access key +}; + + +#endif diff --git a/layout/xul/base/src/nsMenuBoxObject.cpp b/layout/xul/base/src/nsMenuBoxObject.cpp new file mode 100644 index 000000000..a773059b5 --- /dev/null +++ b/layout/xul/base/src/nsMenuBoxObject.cpp @@ -0,0 +1,159 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsISupportsUtils.h" +#include "nsIMenuBoxObject.h" +#include "nsBoxObject.h" +#include "nsIFrame.h" +#include "nsGUIEvent.h" +#include "nsMenuBarFrame.h" +#include "nsMenuBarListener.h" +#include "nsMenuFrame.h" +#include "nsMenuPopupFrame.h" + +class nsMenuBoxObject : public nsIMenuBoxObject, + public nsBoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIMENUBOXOBJECT + + nsMenuBoxObject(); + virtual ~nsMenuBoxObject(); +}; + +nsMenuBoxObject::nsMenuBoxObject() +{ +} + +nsMenuBoxObject::~nsMenuBoxObject() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED1(nsMenuBoxObject, nsBoxObject, nsIMenuBoxObject) + +/* void openMenu (in boolean openFlag); */ +NS_IMETHODIMP nsMenuBoxObject::OpenMenu(bool aOpenFlag) +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsIFrame* frame = GetFrame(false); + if (frame) { + if (aOpenFlag) { + nsCOMPtr<nsIContent> content = mContent; + pm->ShowMenu(content, false, false); + } + else { + nsMenuFrame* menu = do_QueryFrame(frame); + if (menu) { + nsMenuPopupFrame* popupFrame = menu->GetPopup(); + if (popupFrame) + pm->HidePopup(popupFrame->GetContent(), false, true, false); + } + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMenuBoxObject::GetActiveChild(nsIDOMElement** aResult) +{ + *aResult = nullptr; + nsMenuFrame* menu = do_QueryFrame(GetFrame(false)); + if (menu) + return menu->GetActiveChild(aResult); + return NS_OK; +} + +NS_IMETHODIMP nsMenuBoxObject::SetActiveChild(nsIDOMElement* aResult) +{ + nsMenuFrame* menu = do_QueryFrame(GetFrame(false)); + if (menu) + return menu->SetActiveChild(aResult); + return NS_OK; +} + +/* boolean handleKeyPress (in nsIDOMKeyEvent keyEvent); */ +NS_IMETHODIMP nsMenuBoxObject::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent, bool* aHandledFlag) +{ + *aHandledFlag = false; + NS_ENSURE_ARG(aKeyEvent); + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) + return NS_OK; + + // if event has already been handled, bail + bool eventHandled = false; + aKeyEvent->GetDefaultPrevented(&eventHandled); + if (eventHandled) + return NS_OK; + + if (nsMenuBarListener::IsAccessKeyPressed(aKeyEvent)) + return NS_OK; + + nsMenuFrame* menu = do_QueryFrame(GetFrame(false)); + if (!menu) + return NS_OK; + + nsMenuPopupFrame* popupFrame = menu->GetPopup(); + if (!popupFrame) + return NS_OK; + + uint32_t keyCode; + aKeyEvent->GetKeyCode(&keyCode); + switch (keyCode) { + case NS_VK_UP: + case NS_VK_DOWN: + case NS_VK_HOME: + case NS_VK_END: + { + nsNavigationDirection theDirection; + theDirection = NS_DIRECTION_FROM_KEY_CODE(popupFrame, keyCode); + *aHandledFlag = + pm->HandleKeyboardNavigationInPopup(popupFrame, theDirection); + return NS_OK; + } + default: + *aHandledFlag = pm->HandleShortcutNavigation(aKeyEvent, popupFrame); + return NS_OK; + } +} + +NS_IMETHODIMP +nsMenuBoxObject::GetOpenedWithKey(bool* aOpenedWithKey) +{ + *aOpenedWithKey = false; + + nsMenuFrame* menuframe = do_QueryFrame(GetFrame(false)); + if (!menuframe) + return NS_OK; + + nsIFrame* frame = menuframe->GetParent(); + while (frame) { + nsMenuBarFrame* menubar = do_QueryFrame(frame); + if (menubar) { + *aOpenedWithKey = menubar->IsActiveByKeyboard(); + return NS_OK; + } + frame = frame->GetParent(); + } + + return NS_OK; +} + + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewMenuBoxObject(nsIBoxObject** aResult) +{ + *aResult = new nsMenuBoxObject; + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} + diff --git a/layout/xul/base/src/nsMenuFrame.cpp b/layout/xul/base/src/nsMenuFrame.cpp new file mode 100644 index 000000000..2c31aa832 --- /dev/null +++ b/layout/xul/base/src/nsMenuFrame.cpp @@ -0,0 +1,1494 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsGkAtoms.h" +#include "nsHTMLParts.h" +#include "nsMenuFrame.h" +#include "nsBoxFrame.h" +#include "nsIContent.h" +#include "nsIAtom.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsStyleContext.h" +#include "nsCSSRendering.h" +#include "nsINameSpaceManager.h" +#include "nsMenuPopupFrame.h" +#include "nsMenuBarFrame.h" +#include "nsIDocument.h" +#include "nsIDOMElement.h" +#include "nsIComponentManager.h" +#include "nsBoxLayoutState.h" +#include "nsIScrollableFrame.h" +#include "nsBindingManager.h" +#include "nsIServiceManager.h" +#include "nsCSSFrameConstructor.h" +#include "nsIDOMKeyEvent.h" +#include "nsEventDispatcher.h" +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsIStringBundle.h" +#include "nsGUIEvent.h" +#include "nsContentUtils.h" +#include "nsDisplayList.h" +#include "nsIReflowCallback.h" +#include "nsISound.h" +#include "nsEventStateManager.h" +#include "nsIDOMXULMenuListElement.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include <algorithm> + +using namespace mozilla; + +#define NS_MENU_POPUP_LIST_INDEX 0 + +#if defined(XP_WIN) || defined(XP_OS2) +#define NSCONTEXTMENUISMOUSEUP 1 +#endif + +static void +AssertNotCalled(void* aPropertyValue) +{ + NS_ERROR("popup list should never be destroyed by the FramePropertyTable"); +} +NS_DECLARE_FRAME_PROPERTY(PopupListProperty, AssertNotCalled) + +static int32_t gEatMouseMove = false; + +const int32_t kBlinkDelay = 67; // milliseconds + +// this class is used for dispatching menu activation events asynchronously. +class nsMenuActivateEvent : public nsRunnable +{ +public: + nsMenuActivateEvent(nsIContent *aMenu, + nsPresContext* aPresContext, + bool aIsActivate) + : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate) + { + } + + NS_IMETHOD Run() + { + nsAutoString domEventToFire; + + if (mIsActivate) { + // Highlight the menu. + mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, + NS_LITERAL_STRING("true"), true); + // The menuactivated event is used by accessibility to track the user's + // movements through menus + domEventToFire.AssignLiteral("DOMMenuItemActive"); + } + else { + // Unhighlight the menu. + mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true); + domEventToFire.AssignLiteral("DOMMenuItemInactive"); + } + + nsCOMPtr<nsIDOMEvent> event; + if (NS_SUCCEEDED(nsEventDispatcher::CreateEvent(mMenu, mPresContext, nullptr, + NS_LITERAL_STRING("Events"), + getter_AddRefs(event)))) { + event->InitEvent(domEventToFire, true, true); + + event->SetTrusted(true); + + nsEventDispatcher::DispatchDOMEvent(mMenu, nullptr, event, + mPresContext, nullptr); + } + + return NS_OK; + } + +private: + nsCOMPtr<nsIContent> mMenu; + nsRefPtr<nsPresContext> mPresContext; + bool mIsActivate; +}; + +class nsMenuAttributeChangedEvent : public nsRunnable +{ +public: + nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr) + : mFrame(aFrame), mAttr(aAttr) + { + } + + NS_IMETHOD Run() + { + nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame()); + NS_ENSURE_STATE(frame); + if (mAttr == nsGkAtoms::checked) { + frame->UpdateMenuSpecialState(frame->PresContext()); + } else if (mAttr == nsGkAtoms::acceltext) { + // someone reset the accelText attribute, + // so clear the bit that says *we* set it + frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED); + frame->BuildAcceleratorText(true); + } + else if (mAttr == nsGkAtoms::key) { + frame->BuildAcceleratorText(true); + } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) { + frame->UpdateMenuType(frame->PresContext()); + } + return NS_OK; + } +protected: + nsWeakFrame mFrame; + nsCOMPtr<nsIAtom> mAttr; +}; + +// +// NS_NewMenuFrame and NS_NewMenuItemFrame +// +// Wrappers for creating a new menu popup container +// +nsIFrame* +NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext); + it->SetIsMenu(true); + return it; +} + +nsIFrame* +NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext); + it->SetIsMenu(false); + return it; +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame) + +NS_QUERYFRAME_HEAD(nsMenuFrame) + NS_QUERYFRAME_ENTRY(nsMenuFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsBoxFrame(aShell, aContext), + mIsMenu(false), + mChecked(false), + mIgnoreAccelTextChange(false), + mType(eMenuType_Normal), + mMenuParent(nullptr), + mBlinkState(0) +{ +} + +void +nsMenuFrame::SetParent(nsIFrame* aParent) +{ + nsBoxFrame::SetParent(aParent); + InitMenuParent(aParent); +} + +void +nsMenuFrame::InitMenuParent(nsIFrame* aParent) +{ + while (aParent) { + nsMenuPopupFrame* popup = do_QueryFrame(aParent); + if (popup) { + mMenuParent = popup; + break; + } + + nsMenuBarFrame* menubar = do_QueryFrame(aParent); + if (menubar) { + mMenuParent = menubar; + break; + } + + aParent = aParent->GetParent(); + } +} + +class nsASyncMenuInitialization MOZ_FINAL : public nsIReflowCallback +{ +public: + nsASyncMenuInitialization(nsIFrame* aFrame) + : mWeakFrame(aFrame) + { + } + + virtual bool ReflowFinished() + { + bool shouldFlush = false; + nsMenuFrame* menu = do_QueryFrame(mWeakFrame.GetFrame()); + if (menu) { + menu->UpdateMenuType(menu->PresContext()); + shouldFlush = true; + } + delete this; + return shouldFlush; + } + + virtual void ReflowCallbackCanceled() + { + delete this; + } + + nsWeakFrame mWeakFrame; +}; + +void +nsMenuFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + // Set up a mediator which can be used for callbacks on this frame. + mTimerMediator = new nsMenuTimerMediator(this); + + InitMenuParent(aParent); + + BuildAcceleratorText(false); + nsIReflowCallback* cb = new nsASyncMenuInitialization(this); + PresContext()->PresShell()->PostReflowCallback(cb); +} + +const nsFrameList& +nsMenuFrame::GetChildList(ChildListID aListID) const +{ + if (kPopupList == aListID) { + nsFrameList* list = GetPopupList(); + return list ? *list : nsFrameList::EmptyList(); + } + return nsBoxFrame::GetChildList(aListID); +} + +void +nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const +{ + nsBoxFrame::GetChildLists(aLists); + nsFrameList* list = GetPopupList(); + if (list) { + list->AppendIfNonempty(aLists, kPopupList); + } +} + +nsMenuPopupFrame* +nsMenuFrame::GetPopup() +{ + nsFrameList* popupList = GetPopupList(); + return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild()) : + nullptr; +} + +nsFrameList* +nsMenuFrame::GetPopupList() const +{ + if (!HasPopup()) { + return nullptr; + } + nsFrameList* prop = + static_cast<nsFrameList*>(Properties().Get(PopupListProperty())); + NS_ASSERTION(prop && prop->GetLength() == 1 && + prop->FirstChild()->GetType() == nsGkAtoms::menuPopupFrame, + "popup list should have exactly one nsMenuPopupFrame"); + return prop; +} + +void +nsMenuFrame::DestroyPopupList() +{ + NS_ASSERTION(HasPopup(), "huh?"); + nsFrameList* prop = + static_cast<nsFrameList*>(Properties().Remove(PopupListProperty())); + NS_ASSERTION(prop && prop->IsEmpty(), + "popup list must exist and be empty when destroying"); + RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST); + prop->Delete(PresContext()->PresShell()); +} + +void +nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList) +{ + for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { + nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get()); + if (popupFrame) { + // Remove the frame from the list and store it in a nsFrameList* property. + aFrameList.RemoveFrame(popupFrame); + nsFrameList* popupList = new (PresContext()->PresShell()) nsFrameList(popupFrame, popupFrame); + Properties().Set(PopupListProperty(), popupList); + AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST); + break; + } + } +} + +NS_IMETHODIMP +nsMenuFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?"); + if (aListID == kPrincipalList || aListID == kPopupList) { + SetPopupFrame(aChildList); + } + return nsBoxFrame::SetInitialChildList(aListID, aChildList); +} + +void +nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // Kill our timer if one is active. This is not strictly necessary as + // the pointer to this frame will be cleared from the mediator, but + // this is done for added safety. + if (mOpenTimer) { + mOpenTimer->Cancel(); + } + + StopBlinking(); + + // Null out the pointer to this frame in the mediator wrapper so that it + // doesn't try to interact with a deallocated frame. + mTimerMediator->ClearFrame(); + + // if the menu content is just being hidden, it may be made visible again + // later, so make sure to clear the highlighting. + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, false); + + // are we our menu parent's current menu item? + if (mMenuParent && mMenuParent->GetCurrentMenuItem() == this) { + // yes; tell it that we're going away + mMenuParent->CurrentMenuIsBeingDestroyed(); + } + + nsFrameList* popupList = GetPopupList(); + if (popupList) { + popupList->DestroyFramesFrom(aDestructRoot); + DestroyPopupList(); + } + + nsBoxFrame::DestroyFrom(aDestructRoot); +} + +void +nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!aBuilder->IsForEventDelivery()) { + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); + return; + } + + nsDisplayListCollection set; + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set); + + WrapListsInRedirector(aBuilder, set, aLists); +} + +NS_IMETHODIMP +nsMenuFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus || + (mMenuParent && mMenuParent->IsMenuLocked())) { + return NS_OK; + } + + nsWeakFrame weakFrame(this); + if (*aEventStatus == nsEventStatus_eIgnore) + *aEventStatus = nsEventStatus_eConsumeDoDefault; + + bool onmenu = IsOnMenu(); + + if (aEvent->message == NS_KEY_PRESS && !IsDisabled()) { + nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent; + uint32_t keyCode = keyEvent->keyCode; +#ifdef XP_MACOSX + // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed) + if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->IsMeta()) || + (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + OpenMenu(false); + } +#else + // On other platforms, toggle menulist on unmodified F4 or Alt arrow + if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) || + ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + ToggleMenuState(); + } +#endif + } + else if (aEvent->eventStructType == NS_MOUSE_EVENT && + aEvent->message == NS_MOUSE_BUTTON_DOWN && + static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eLeftButton && + !IsDisabled() && IsMenu()) { + // The menu item was selected. Bring up the menu. + // We have children. + // Don't prevent the default action here, since that will also cancel + // potential drag starts. + if (!mMenuParent || mMenuParent->IsMenuBar()) { + ToggleMenuState(); + } + else { + if (!IsOpen()) { + OpenMenu(false); + } + } + } + else if ( +#ifndef NSCONTEXTMENUISMOUSEUP + (aEvent->eventStructType == NS_MOUSE_EVENT && + aEvent->message == NS_MOUSE_BUTTON_UP && + static_cast<nsMouseEvent*>(aEvent)->button == + nsMouseEvent::eRightButton) && +#else + aEvent->message == NS_CONTEXTMENU && +#endif + onmenu && !IsMenu() && !IsDisabled()) { + // if this menu is a context menu it accepts right-clicks...fire away! + // Make sure we cancel default processing of the context menu event so + // that it doesn't bubble and get seen again by the popuplistener and show + // another context menu. + // + // Furthermore (there's always more, isn't there?), on some platforms (win32 + // being one of them) we get the context menu event on a mouse up while + // on others we get it on a mouse down. For the ones where we get it on a + // mouse down, we must continue listening for the right button up event to + // dismiss the menu. + if (mMenuParent->IsContextMenu()) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + Execute(aEvent); + } + } + else if (aEvent->eventStructType == NS_MOUSE_EVENT && + aEvent->message == NS_MOUSE_BUTTON_UP && + static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eLeftButton && + !IsMenu() && !IsDisabled()) { + // Execute the execute event handler. + *aEventStatus = nsEventStatus_eConsumeNoDefault; + Execute(aEvent); + } + else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) { + // Kill our timer if one is active. + if (mOpenTimer) { + mOpenTimer->Cancel(); + mOpenTimer = nullptr; + } + + // Deactivate the menu. + if (mMenuParent) { + bool onmenubar = mMenuParent->IsMenuBar(); + if (!(onmenubar && mMenuParent->IsActive())) { + if (IsMenu() && !onmenubar && IsOpen()) { + // Submenus don't get closed up immediately. + } + else if (this == mMenuParent->GetCurrentMenuItem()) { + mMenuParent->ChangeMenuItem(nullptr, false); + } + } + } + } + else if (aEvent->message == NS_MOUSE_MOVE && + (onmenu || (mMenuParent && mMenuParent->IsMenuBar()))) { + if (gEatMouseMove) { + gEatMouseMove = false; + return NS_OK; + } + + // Let the menu parent know we're the new item. + mMenuParent->ChangeMenuItem(this, false); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + NS_ENSURE_TRUE(mMenuParent, NS_OK); + + // we need to check if we really became the current menu + // item or not + nsMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem(); + if (realCurrentItem != this) { + // we didn't (presumably because a context menu was active) + return NS_OK; + } + + // Hovering over a menu in a popup should open it without a need for a click. + // A timer is used so that it doesn't open if the user moves the mouse quickly + // past the menu. This conditional check ensures that only menus have this + // behaviour + if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !mMenuParent->IsMenuBar()) { + int32_t menuDelay = + LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms + + // We're a menu, we're built, we're closed, and no timer has been kicked off. + mOpenTimer = do_CreateInstance("@mozilla.org/timer;1"); + mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT); + } + } + + return NS_OK; +} + +void +nsMenuFrame::ToggleMenuState() +{ + if (IsOpen()) + CloseMenu(false); + else + OpenMenu(false); +} + +void +nsMenuFrame::PopupOpened() +{ + nsWeakFrame weakFrame(this); + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, + NS_LITERAL_STRING("true"), true); + if (!weakFrame.IsAlive()) + return; + + if (mMenuParent) { + mMenuParent->SetActive(true); + // Make sure the current menu which is being toggled on + // the menubar is highlighted + mMenuParent->SetCurrentMenuItem(this); + } +} + +void +nsMenuFrame::PopupClosed(bool aDeselectMenu) +{ + nsWeakFrame weakFrame(this); + nsContentUtils::AddScriptRunner( + new nsUnsetAttrRunnable(mContent, nsGkAtoms::open)); + if (!weakFrame.IsAlive()) + return; + + // if the popup is for a menu on a menubar, inform menubar to deactivate + if (mMenuParent && mMenuParent->MenuClosed()) { + if (aDeselectMenu) { + SelectMenu(false); + } else { + // We are not deselecting the parent menu while closing the popup, so send + // a DOMMenuItemActive event to the menu to indicate that the menu is + // becoming active again. + nsMenuFrame *current = mMenuParent->GetCurrentMenuItem(); + if (current) { + // However, if the menu is a descendant on a menubar, and the menubar + // has the 'stay active' flag set, it means that the menubar is switching + // to another toplevel menu entirely (for example from Edit to View), so + // don't fire the DOMMenuItemActive event or else we'll send extraneous + // events for submenus. nsMenuBarFrame::ChangeMenuItem has already deselected + // the old menu, so it doesn't need to happen again here, and the new + // menu can be selected right away. + nsIFrame* parent = current; + while (parent) { + nsMenuBarFrame* menubar = do_QueryFrame(parent); + if (menubar && menubar->GetStayActive()) + return; + + parent = parent->GetParent(); + } + + nsCOMPtr<nsIRunnable> event = + new nsMenuActivateEvent(current->GetContent(), + PresContext(), true); + NS_DispatchToCurrentThread(event); + } + } + } +} + +NS_IMETHODIMP +nsMenuFrame::SelectMenu(bool aActivateFlag) +{ + if (mContent) { + // When a menu opens a submenu, the mouse will often be moved onto a + // sibling before moving onto an item within the submenu, causing the + // parent to become deselected. We need to ensure that the parent menu + // is reselected when an item in the submenu is selected, so navigate up + // from the item to its popup, and then to the popup above that. + if (aActivateFlag) { + nsIFrame* parent = GetParent(); + while (parent) { + nsMenuPopupFrame* menupopup = do_QueryFrame(parent); + if (menupopup) { + // a menu is always the direct parent of a menupopup + nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent()); + if (menu) { + // a popup however is not necessarily the direct parent of a menu + nsIFrame* popupParent = menu->GetParent(); + while (popupParent) { + menupopup = do_QueryFrame(popupParent); + if (menupopup) { + menupopup->SetCurrentMenuItem(menu); + break; + } + popupParent = popupParent->GetParent(); + } + } + break; + } + parent = parent->GetParent(); + } + } + + // cancel the close timer if selecting a menu within the popup to be closed + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + pm->CancelMenuTimer(mMenuParent); + + nsCOMPtr<nsIRunnable> event = + new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag); + NS_DispatchToCurrentThread(event); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) { + // Reset the flag so that only one change is ignored. + mIgnoreAccelTextChange = false; + return NS_OK; + } + + if (aAttribute == nsGkAtoms::checked || + aAttribute == nsGkAtoms::acceltext || + aAttribute == nsGkAtoms::key || + aAttribute == nsGkAtoms::type || + aAttribute == nsGkAtoms::name) { + nsCOMPtr<nsIRunnable> event = + new nsMenuAttributeChangedEvent(this, aAttribute); + nsContentUtils::AddScriptRunner(event); + } + return NS_OK; +} + +void +nsMenuFrame::OpenMenu(bool aSelectFirstItem) +{ + if (!mContent) + return; + + gEatMouseMove = true; + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + pm->KillMenuTimer(); + // This opens the menu asynchronously + pm->ShowMenu(mContent, aSelectFirstItem, true); + } +} + +void +nsMenuFrame::CloseMenu(bool aDeselectMenu) +{ + gEatMouseMove = true; + + // Close the menu asynchronously + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && HasPopup()) + pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true); +} + +bool +nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways) +{ + bool sizeToPopup; + if (aContent->Tag() == nsGkAtoms::select) + sizeToPopup = true; + else { + nsAutoString sizedToPopup; + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup); + sizeToPopup = sizedToPopup.EqualsLiteral("always") || + (!aRequireAlways && sizedToPopup.EqualsLiteral("pref")); + } + + return sizeToPopup; +} + +nsSize +nsMenuFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState) +{ + nsSize size = nsBoxFrame::GetMinSize(aBoxLayoutState); + DISPLAY_MIN_SIZE(this, size); + + if (IsSizedToPopup(mContent, true)) + SizeToPopup(aBoxLayoutState, size); + + return size; +} + +NS_IMETHODIMP +nsMenuFrame::DoLayout(nsBoxLayoutState& aState) +{ + // lay us out + nsresult rv = nsBoxFrame::DoLayout(aState); + + nsMenuPopupFrame* popupFrame = GetPopup(); + if (popupFrame) { + bool sizeToPopup = IsSizedToPopup(mContent, false); + popupFrame->LayoutPopup(aState, this, sizeToPopup); + } + + return rv; +} + +#ifdef DEBUG_LAYOUT +NS_IMETHODIMP +nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug) +{ + // see if our state matches the given debug state + bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG; + bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet); + + // if it doesn't then tell each child below us the new debug state + if (debugChanged) + { + nsBoxFrame::SetDebug(aState, aDebug); + nsMenuPopupFrame* popupFrame = GetPopup(); + if (popupFrame) + SetDebug(aState, popupFrame, aDebug); + } + + return NS_OK; +} + +nsresult +nsMenuFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug) +{ + if (!aList) + return NS_OK; + + while (aList) { + if (aList->IsBoxFrame()) + aList->SetDebug(aState, aDebug); + + aList = aList->GetNextSibling(); + } + + return NS_OK; +} +#endif + +// +// Enter +// +// Called when the user hits the <Enter>/<Return> keys or presses the +// shortcut key. If this is a leaf item, the item's action will be executed. +// In either case, do nothing if the item is disabled. +// +nsMenuFrame* +nsMenuFrame::Enter(nsGUIEvent *aEvent) +{ + if (IsDisabled()) { +#ifdef XP_WIN + // behavior on Windows - close the popup chain + if (mMenuParent) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny); + if (popup) + pm->HidePopup(popup->GetContent(), true, true, true); + } + } +#endif // #ifdef XP_WIN + // this menu item was disabled - exit + return nullptr; + } + + if (!IsOpen()) { + // The enter key press applies to us. + if (!IsMenu() && mMenuParent) + Execute(aEvent); // Execute our event handler + else + return this; + } + + return nullptr; +} + +bool +nsMenuFrame::IsOpen() +{ + nsMenuPopupFrame* popupFrame = GetPopup(); + return popupFrame && popupFrame->IsOpen(); +} + +bool +nsMenuFrame::IsMenu() +{ + return mIsMenu; +} + +nsMenuListType +nsMenuFrame::GetParentMenuListType() +{ + if (mMenuParent && mMenuParent->IsMenu()) { + nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(mMenuParent); + nsIFrame* parentMenu = popupFrame->GetParent(); + if (parentMenu) { + nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent()); + if (menulist) { + bool isEditable = false; + menulist->GetEditable(&isEditable); + return isEditable ? eEditableMenuList : eReadonlyMenuList; + } + } + } + return eNotMenuList; +} + +nsresult +nsMenuFrame::Notify(nsITimer* aTimer) +{ + // Our timer has fired. + if (aTimer == mOpenTimer.get()) { + mOpenTimer = nullptr; + + if (!IsOpen() && mMenuParent) { + // make sure we didn't open a context menu in the meantime + // (i.e. the user right-clicked while hovering over a submenu). + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + if ((!pm->HasContextMenu(nullptr) || mMenuParent->IsContextMenu()) && + mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, + nsGkAtoms::_true, eCaseMatters)) { + OpenMenu(false); + } + } + } + } else if (aTimer == mBlinkTimer) { + switch (mBlinkState++) { + case 0: + NS_ASSERTION(false, "Blink timer fired while not blinking"); + StopBlinking(); + break; + case 1: + { + // Turn the highlight back on and wait for a while before closing the menu. + nsWeakFrame weakFrame(this); + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, + NS_LITERAL_STRING("true"), true); + if (weakFrame.IsAlive()) { + aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT); + } + } + break; + default: + if (mMenuParent) { + mMenuParent->LockMenuUntilClosed(false); + } + PassMenuCommandEventToPopupManager(); + StopBlinking(); + break; + } + } + + return NS_OK; +} + +bool +nsMenuFrame::IsDisabled() +{ + return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters); +} + +void +nsMenuFrame::UpdateMenuType(nsPresContext* aPresContext) +{ + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, + strings, eCaseMatters)) { + case 0: mType = eMenuType_Checkbox; break; + case 1: + mType = eMenuType_Radio; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName); + break; + + default: + if (mType != eMenuType_Normal) { + nsWeakFrame weakFrame(this); + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, + true); + ENSURE_TRUE(weakFrame.IsAlive()); + } + mType = eMenuType_Normal; + break; + } + UpdateMenuSpecialState(aPresContext); +} + +/* update checked-ness for type="checkbox" and type="radio" */ +void +nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext) +{ + bool newChecked = + mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, + nsGkAtoms::_true, eCaseMatters); + if (newChecked == mChecked) { + /* checked state didn't change */ + + if (mType != eMenuType_Radio) + return; // only Radio possibly cares about other kinds of change + + if (!mChecked || mGroupName.IsEmpty()) + return; // no interesting change + } else { + mChecked = newChecked; + if (mType != eMenuType_Radio || !mChecked) + /* + * Unchecking something requires no further changes, and only + * menuRadio has to do additional work when checked. + */ + return; + } + + /* + * If we get this far, we're type=radio, and: + * - our name= changed, or + * - we went from checked="false" to checked="true" + */ + + /* + * Behavioural note: + * If we're checked and renamed _into_ an existing radio group, we are + * made the new checked item, and we unselect the previous one. + * + * The only other reasonable behaviour would be to check for another selected + * item in that group. If found, unselect ourselves, otherwise we're the + * selected item. That, however, would be a lot more work, and I don't think + * it's better at all. + */ + + /* walk siblings, looking for the other checked item with the same name */ + // get the first sibling in this menu popup. This frame may be it, and if we're + // being called at creation time, this frame isn't yet in the parent's child list. + // All I'm saying is that this may fail, but it's most likely alright. + nsIFrame* sib = GetParent()->GetFirstPrincipalChild(); + + while (sib) { + if (sib != this) { + nsMenuFrame* menu = do_QueryFrame(sib); + if (menu && menu->GetMenuType() == eMenuType_Radio && + menu->IsChecked() && menu->GetRadioGroupName() == mGroupName) { + /* uncheck the old item */ + sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, + true); + /* XXX in DEBUG, check to make sure that there aren't two checked items */ + return; + } + } + + sib = sib->GetNextSibling(); + } +} + +void +nsMenuFrame::BuildAcceleratorText(bool aNotify) +{ + nsAutoString accelText; + + if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) { + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText); + if (!accelText.IsEmpty()) + return; + } + // accelText is definitely empty here. + + // Now we're going to compute the accelerator text, so remember that we did. + AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED); + + // If anything below fails, just leave the accelerator text blank. + nsWeakFrame weakFrame(this); + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify); + ENSURE_TRUE(weakFrame.IsAlive()); + + // See if we have a key node and use that instead. + nsAutoString keyValue; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue); + if (keyValue.IsEmpty()) + return; + + // Turn the document into a DOM document so we can use getElementById + nsIDocument *document = mContent->GetDocument(); + if (!document) + return; + + nsIContent *keyElement = document->GetElementById(keyValue); + if (!keyElement) { +#ifdef DEBUG + nsAutoString label; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label); + nsAutoString msg = NS_LITERAL_STRING("Key '") + + keyValue + + NS_LITERAL_STRING("' of menu item '") + + label + + NS_LITERAL_STRING("' could not be found"); + NS_WARNING(NS_ConvertUTF16toUTF8(msg).get()); +#endif + return; + } + + // get the string to display as accelerator text + // check the key element's attributes in this order: + // |keytext|, |key|, |keycode| + nsAutoString accelString; + keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString); + + if (accelString.IsEmpty()) { + keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString); + + if (!accelString.IsEmpty()) { + ToUpperCase(accelString); + } else { + nsAutoString keyCode; + keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode); + ToUpperCase(keyCode); + + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (bundleService) { + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://global/locale/keys.properties", + getter_AddRefs(bundle)); + + if (NS_SUCCEEDED(rv) && bundle) { + nsXPIDLString keyName; + rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName)); + if (keyName) + accelString = keyName; + } + } + + // nothing usable found, bail + if (accelString.IsEmpty()) + return; + } + } + + static int32_t accelKey = 0; + + if (!accelKey) + { + // Compiled-in defaults, in case we can't get LookAndFeel -- + // command for mac, control for all other platforms. +#ifdef XP_MACOSX + accelKey = nsIDOMKeyEvent::DOM_VK_META; +#else + accelKey = nsIDOMKeyEvent::DOM_VK_CONTROL; +#endif + + // Get the accelerator key value from prefs, overriding the default: + accelKey = Preferences::GetInt("ui.key.accelKey", accelKey); + } + + nsAutoString modifiers; + keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers); + + char* str = ToNewCString(modifiers); + char* newStr; + char* token = nsCRT::strtok(str, ", \t", &newStr); + + nsAutoString shiftText; + nsAutoString altText; + nsAutoString metaText; + nsAutoString controlText; + nsAutoString osText; + nsAutoString modifierSeparator; + + nsContentUtils::GetShiftText(shiftText); + nsContentUtils::GetAltText(altText); + nsContentUtils::GetMetaText(metaText); + nsContentUtils::GetControlText(controlText); + nsContentUtils::GetOSText(osText); + nsContentUtils::GetModifierSeparatorText(modifierSeparator); + + while (token) { + + if (PL_strcmp(token, "shift") == 0) + accelText += shiftText; + else if (PL_strcmp(token, "alt") == 0) + accelText += altText; + else if (PL_strcmp(token, "meta") == 0) + accelText += metaText; + else if (PL_strcmp(token, "os") == 0) + accelText += osText; + else if (PL_strcmp(token, "control") == 0) + accelText += controlText; + else if (PL_strcmp(token, "accel") == 0) { + switch (accelKey) + { + case nsIDOMKeyEvent::DOM_VK_META: + accelText += metaText; + break; + + case nsIDOMKeyEvent::DOM_VK_WIN: + accelText += osText; + break; + + case nsIDOMKeyEvent::DOM_VK_ALT: + accelText += altText; + break; + + case nsIDOMKeyEvent::DOM_VK_CONTROL: + default: + accelText += controlText; + break; + } + } + + accelText += modifierSeparator; + + token = nsCRT::strtok(newStr, ", \t", &newStr); + } + + nsMemory::Free(str); + + accelText += accelString; + + mIgnoreAccelTextChange = true; + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify); + ENSURE_TRUE(weakFrame.IsAlive()); + + mIgnoreAccelTextChange = false; +} + +void +nsMenuFrame::Execute(nsGUIEvent *aEvent) +{ + // flip "checked" state if we're a checkbox menu, or an un-checked radio menu + bool needToFlipChecked = false; + if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) { + needToFlipChecked = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck, + nsGkAtoms::_false, eCaseMatters); + } + + nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1")); + if (sound) + sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE); + + StartBlinking(aEvent, needToFlipChecked); +} + +bool +nsMenuFrame::ShouldBlink() +{ + int32_t shouldBlink = + LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0); + if (!shouldBlink) + return false; + + // Don't blink in editable menulists. + if (GetParentMenuListType() == eEditableMenuList) + return false; + + return true; +} + +void +nsMenuFrame::StartBlinking(nsGUIEvent *aEvent, bool aFlipChecked) +{ + StopBlinking(); + CreateMenuCommandEvent(aEvent, aFlipChecked); + + if (!ShouldBlink()) { + PassMenuCommandEventToPopupManager(); + return; + } + + // Blink off. + nsWeakFrame weakFrame(this); + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true); + if (!weakFrame.IsAlive()) + return; + + if (mMenuParent) { + // Make this menu ignore events from now on. + mMenuParent->LockMenuUntilClosed(true); + } + + // Set up a timer to blink back on. + mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1"); + mBlinkTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT); + mBlinkState = 1; +} + +void +nsMenuFrame::StopBlinking() +{ + mBlinkState = 0; + if (mBlinkTimer) { + mBlinkTimer->Cancel(); + mBlinkTimer = nullptr; + } + mDelayedMenuCommandEvent = nullptr; +} + +void +nsMenuFrame::CreateMenuCommandEvent(nsGUIEvent *aEvent, bool aFlipChecked) +{ + // Create a trusted event if the triggering event was trusted, or if + // we're called from chrome code (since at least one of our caller + // passes in a null event). + bool isTrusted = aEvent ? aEvent->mFlags.mIsTrusted : + nsContentUtils::IsCallerChrome(); + + bool shift = false, control = false, alt = false, meta = false; + if (aEvent && (aEvent->eventStructType == NS_MOUSE_EVENT || + aEvent->eventStructType == NS_KEY_EVENT)) { + shift = static_cast<nsInputEvent *>(aEvent)->IsShift(); + control = static_cast<nsInputEvent *>(aEvent)->IsControl(); + alt = static_cast<nsInputEvent *>(aEvent)->IsAlt(); + meta = static_cast<nsInputEvent *>(aEvent)->IsMeta(); + } + + // Because the command event is firing asynchronously, a flag is needed to + // indicate whether user input is being handled. This ensures that a popup + // window won't get blocked. + bool userinput = nsEventStateManager::IsHandlingUserInput(); + + mDelayedMenuCommandEvent = + new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta, + userinput, aFlipChecked); +} + +void +nsMenuFrame::PassMenuCommandEventToPopupManager() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && mMenuParent && mDelayedMenuCommandEvent) { + pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent); + } + mDelayedMenuCommandEvent = nullptr; +} + +NS_IMETHODIMP +nsMenuFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + nsFrameList* popupList = GetPopupList(); + if (popupList && popupList->FirstChild() == aOldFrame) { + popupList->RemoveFirstChild(); + aOldFrame->Destroy(); + DestroyPopupList(); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + return NS_OK; + } + return nsBoxFrame::RemoveFrame(aListID, aOldFrame); +} + +NS_IMETHODIMP +nsMenuFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { + SetPopupFrame(aFrameList); + if (HasPopup()) { +#ifdef DEBUG_LAYOUT + nsBoxLayoutState state(PresContext()); + SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); +#endif + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + } + + if (aFrameList.IsEmpty()) + return NS_OK; + + if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) { + aPrevFrame = nullptr; + } + + return nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); +} + +NS_IMETHODIMP +nsMenuFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { + SetPopupFrame(aFrameList); + if (HasPopup()) { + +#ifdef DEBUG_LAYOUT + nsBoxLayoutState state(PresContext()); + SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); +#endif + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + } + + if (aFrameList.IsEmpty()) + return NS_OK; + + return nsBoxFrame::AppendFrames(aListID, aFrameList); +} + +bool +nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) +{ + if (!IsCollapsed()) { + bool widthSet, heightSet; + nsSize tmpSize(-1, 0); + nsIFrame::AddCSSPrefSize(this, tmpSize, widthSet, heightSet); + if (!widthSet && GetFlex(aState) == 0) { + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) + return false; + tmpSize = popupFrame->GetPrefSize(aState); + + // Produce a size such that: + // (1) the menu and its popup can be the same width + // (2) there's enough room in the menu for the content and its + // border-padding + // (3) there's enough room in the popup for the content and its + // scrollbar + nsMargin borderPadding; + GetBorderAndPadding(borderPadding); + + // if there is a scroll frame, add the desired width of the scrollbar as well + nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->GetFirstPrincipalChild()); + nscoord scrollbarWidth = 0; + if (scrollFrame) { + scrollbarWidth = + scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight(); + } + + aSize.width = + tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth); + + return true; + } + } + + return false; +} + +nsSize +nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState) +{ + nsSize size = nsBoxFrame::GetPrefSize(aState); + DISPLAY_PREF_SIZE(this, size); + + // If we are using sizetopopup="always" then + // nsBoxFrame will already have enforced the minimum size + if (!IsSizedToPopup(mContent, true) && + IsSizedToPopup(mContent, false) && + SizeToPopup(aState, size)) { + // We now need to ensure that size is within the min - max range. + nsSize minSize = nsBoxFrame::GetMinSize(aState); + nsSize maxSize = GetMaxSize(aState); + size = BoundsCheck(minSize, size, maxSize); + } + + return size; +} + +NS_IMETHODIMP +nsMenuFrame::GetActiveChild(nsIDOMElement** aResult) +{ + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) + return NS_ERROR_FAILURE; + + nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem(); + if (!menuFrame) { + *aResult = nullptr; + } + else { + nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(menuFrame->GetContent())); + *aResult = elt; + NS_IF_ADDREF(*aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuFrame::SetActiveChild(nsIDOMElement* aChild) +{ + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) + return NS_ERROR_FAILURE; + + if (!aChild) { + // Remove the current selection + popupFrame->ChangeMenuItem(nullptr, false); + return NS_OK; + } + + nsCOMPtr<nsIContent> child(do_QueryInterface(aChild)); + + nsMenuFrame* menu = do_QueryFrame(child->GetPrimaryFrame()); + if (menu) + popupFrame->ChangeMenuItem(menu, false); + return NS_OK; +} + +nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame() +{ + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) + return nullptr; + nsIFrame* childFrame = popupFrame->GetFirstPrincipalChild(); + if (childFrame) + return popupFrame->GetScrollFrame(childFrame); + return nullptr; +} + +// nsMenuTimerMediator implementation. +NS_IMPL_ISUPPORTS1(nsMenuTimerMediator, nsITimerCallback) + +/** + * Constructs a wrapper around an nsMenuFrame. + * @param aFrame nsMenuFrame to create a wrapper around. + */ +nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) : + mFrame(aFrame) +{ + NS_ASSERTION(mFrame, "Must have frame"); +} + +nsMenuTimerMediator::~nsMenuTimerMediator() +{ +} + +/** + * Delegates the notification to the contained frame if it has not been destroyed. + * @param aTimer Timer which initiated the callback. + * @return NS_ERROR_FAILURE if the frame has been destroyed. + */ +NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer) +{ + if (!mFrame) + return NS_ERROR_FAILURE; + + return mFrame->Notify(aTimer); +} + +/** + * Clear the pointer to the contained nsMenuFrame. This should be called + * when the contained nsMenuFrame is destroyed. + */ +void nsMenuTimerMediator::ClearFrame() +{ + mFrame = nullptr; +} diff --git a/layout/xul/base/src/nsMenuFrame.h b/layout/xul/base/src/nsMenuFrame.h new file mode 100644 index 000000000..d88807daa --- /dev/null +++ b/layout/xul/base/src/nsMenuFrame.h @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// nsMenuFrame +// + +#ifndef nsMenuFrame_h__ +#define nsMenuFrame_h__ + +#include "nsIAtom.h" +#include "nsCOMPtr.h" + +#include "nsBoxFrame.h" +#include "nsFrameList.h" +#include "nsGkAtoms.h" +#include "nsMenuParent.h" +#include "nsXULPopupManager.h" +#include "nsITimer.h" +#include "mozilla/Attributes.h" + +nsIFrame* NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +nsIFrame* NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsIContent; +class nsMenuBarFrame; + +#define NS_STATE_ACCELTEXT_IS_DERIVED NS_STATE_BOX_CHILD_RESERVED + +// the type of menuitem +enum nsMenuType { + // a normal menuitem where a command is carried out when activated + eMenuType_Normal = 0, + // a menuitem with a checkmark that toggles when activated + eMenuType_Checkbox = 1, + // a radio menuitem where only one of it and its siblings with the same + // name attribute can be checked at a time + eMenuType_Radio = 2 +}; + +enum nsMenuListType { + eNotMenuList, // not a menulist + eReadonlyMenuList, // <menulist/> + eEditableMenuList // <menulist editable="true"/> +}; + +class nsMenuFrame; + +/** + * nsMenuTimerMediator is a wrapper around an nsMenuFrame which can be safely + * passed to timers. The class is reference counted unlike the underlying + * nsMenuFrame, so that it will exist as long as the timer holds a reference + * to it. The callback is delegated to the contained nsMenuFrame as long as + * the contained nsMenuFrame has not been destroyed. + */ +class nsMenuTimerMediator MOZ_FINAL : public nsITimerCallback +{ +public: + nsMenuTimerMediator(nsMenuFrame* aFrame); + ~nsMenuTimerMediator(); + + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + void ClearFrame(); + +private: + + // Pointer to the wrapped frame. + nsMenuFrame* mFrame; +}; + +class nsMenuFrame : public nsBoxFrame +{ +public: + nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext); + + NS_DECL_QUERYFRAME_TARGET(nsMenuFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + +#ifdef DEBUG_LAYOUT + NS_IMETHOD SetDebug(nsBoxLayoutState& aState, bool aDebug) MOZ_OVERRIDE; +#endif + + // The following methods are all overridden so that the menupopup + // can be stored in a separate list, so that it doesn't impact reflow of the + // actual menu item at all. + virtual const nsFrameList& GetChildList(ChildListID aList) const MOZ_OVERRIDE; + virtual void GetChildLists(nsTArray<ChildList>* aLists) const MOZ_OVERRIDE; + NS_IMETHOD SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) MOZ_OVERRIDE; + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + // Overridden to prevent events from going to children of the menu. + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + // this method can destroy the frame + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + NS_IMETHOD AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) MOZ_OVERRIDE; + + NS_IMETHOD InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) MOZ_OVERRIDE; + + NS_IMETHOD RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) MOZ_OVERRIDE; + + virtual nsIAtom* GetType() const MOZ_OVERRIDE { return nsGkAtoms::menuFrame; } + + NS_IMETHOD SelectMenu(bool aActivateFlag); + + virtual nsIScrollableFrame* GetScrollTargetFrame() MOZ_OVERRIDE; + + /** + * NOTE: OpenMenu will open the menu asynchronously. + */ + void OpenMenu(bool aSelectFirstItem); + // CloseMenu closes the menu asynchronously + void CloseMenu(bool aDeselectMenu); + + bool IsChecked() { return mChecked; } + + NS_IMETHOD GetActiveChild(nsIDOMElement** aResult); + NS_IMETHOD SetActiveChild(nsIDOMElement* aChild); + + // called when the Enter key is pressed while the menuitem is the current + // one in its parent popup. This will carry out the command attached to + // the menuitem. If the menu should be opened, this frame will be returned, + // otherwise null will be returned. + nsMenuFrame* Enter(nsGUIEvent* aEvent); + + virtual void SetParent(nsIFrame* aParent) MOZ_OVERRIDE; + + virtual nsMenuParent *GetMenuParent() { return mMenuParent; } + const nsAString& GetRadioGroupName() { return mGroupName; } + nsMenuType GetMenuType() { return mType; } + nsMenuPopupFrame* GetPopup(); + + /** + * @return true if this frame has a popup child frame. + */ + bool HasPopup() const + { + return (GetStateBits() & NS_STATE_MENU_HAS_POPUP_LIST) != 0; + } + + + // nsMenuFrame methods + + bool IsOnMenuBar() { return mMenuParent && mMenuParent->IsMenuBar(); } + bool IsOnActiveMenuBar() { return IsOnMenuBar() && mMenuParent->IsActive(); } + virtual bool IsOpen(); + virtual bool IsMenu(); + nsMenuListType GetParentMenuListType(); + bool IsDisabled(); + void ToggleMenuState(); + + // indiciate that the menu's popup has just been opened, so that the menu + // can update its open state. This method modifies the open attribute on + // the menu, so the frames could be gone after this call. + void PopupOpened(); + // indiciate that the menu's popup has just been closed, so that the menu + // can update its open state. The menu should be unhighlighted if + // aDeselectedMenu is true. This method modifies the open attribute on + // the menu, so the frames could be gone after this call. + void PopupClosed(bool aDeselectMenu); + + // returns true if this is a menu on another menu popup. A menu is a submenu + // if it has a parent popup or menupopup. + bool IsOnMenu() { return mMenuParent && mMenuParent->IsMenu(); } + void SetIsMenu(bool aIsMenu) { mIsMenu = aIsMenu; } + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE + { + return MakeFrameName(NS_LITERAL_STRING("Menu"), aResult); + } +#endif + + static bool IsSizedToPopup(nsIContent* aContent, bool aRequireAlways); + +protected: + friend class nsMenuTimerMediator; + friend class nsASyncMenuInitialization; + friend class nsMenuAttributeChangedEvent; + + /** + * Initialize the popup list to the first popup frame within + * aChildList. Removes the popup, if any, from aChildList. + */ + void SetPopupFrame(nsFrameList& aChildList); + + /** + * Get the popup frame list from the frame property. + * @return the property value if it exists, nullptr otherwise. + */ + nsFrameList* GetPopupList() const; + + /** + * Destroy the popup list property. The list must exist and be empty. + */ + void DestroyPopupList(); + + // set mMenuParent to the nearest enclosing menu bar or menupopup frame of + // aParent (or aParent itself). This is called when initializing the frame, + // so aParent should be the expected parent of this frame. + void InitMenuParent(nsIFrame* aParent); + + // Update the menu's type (normal, checkbox, radio). + // This method can destroy the frame. + void UpdateMenuType(nsPresContext* aPresContext); + // Update the checked state of the menu, and for radios, clear any other + // checked items. This method can destroy the frame. + void UpdateMenuSpecialState(nsPresContext* aPresContext); + + // Examines the key node and builds the accelerator. + void BuildAcceleratorText(bool aNotify); + + // Called to execute our command handler. This method can destroy the frame. + void Execute(nsGUIEvent *aEvent); + + // This method can destroy the frame + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + virtual ~nsMenuFrame() { } + + bool SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize); + + bool ShouldBlink(); + void StartBlinking(nsGUIEvent *aEvent, bool aFlipChecked); + void StopBlinking(); + void CreateMenuCommandEvent(nsGUIEvent *aEvent, bool aFlipChecked); + void PassMenuCommandEventToPopupManager(); + +protected: +#ifdef DEBUG_LAYOUT + nsresult SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug); +#endif + NS_HIDDEN_(nsresult) Notify(nsITimer* aTimer); + + bool mIsMenu; // Whether or not we can even have children or not. + bool mChecked; // are we checked? + bool mIgnoreAccelTextChange; // temporarily set while determining the accelerator key + nsMenuType mType; + + nsMenuParent* mMenuParent; // Our parent menu. + + // Reference to the mediator which wraps this frame. + nsRefPtr<nsMenuTimerMediator> mTimerMediator; + + nsCOMPtr<nsITimer> mOpenTimer; + nsCOMPtr<nsITimer> mBlinkTimer; + + uint8_t mBlinkState; // 0: not blinking, 1: off, 2: on + nsRefPtr<nsXULMenuCommandEvent> mDelayedMenuCommandEvent; + + nsString mGroupName; + +}; // class nsMenuFrame + +#endif diff --git a/layout/xul/base/src/nsMenuParent.h b/layout/xul/base/src/nsMenuParent.h new file mode 100644 index 000000000..cc96d82a5 --- /dev/null +++ b/layout/xul/base/src/nsMenuParent.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMenuParent_h___ +#define nsMenuParent_h___ + +class nsMenuFrame; + +/* + * nsMenuParent is an interface implemented by nsMenuBarFrame and nsMenuPopupFrame + * as both serve as parent frames to nsMenuFrame. + * + * Don't implement this interface on other classes unless you also fix up references, + * as this interface is directly cast to and from nsMenuBarFrame and nsMenuPopupFrame. + */ + +class nsMenuParent { + +public: + // returns the menu frame of the currently active item within the menu + virtual nsMenuFrame *GetCurrentMenuItem() = 0; + // sets the currently active menu frame. + NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) = 0; + // indicate that the current menu frame is being destroyed, so clear the + // current menu item + virtual void CurrentMenuIsBeingDestroyed() = 0; + // deselects the current item and closes its popup if any, then selects the + // new item aMenuItem. For a menubar, if another menu is already open, the + // new menu aMenuItem is opened. In this case, if aSelectFirstItem is true, + // select the first item in it. For menupopups, the menu is not opened and + // the aSelectFirstItem argument is not used. + NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, bool aSelectFirstItem) = 0; + + // returns true if the menupopup is open. For menubars, returns false. + virtual bool IsOpen() = 0; + // returns true if the menubar is currently active. For menupopups, returns false. + virtual bool IsActive() = 0; + // returns true if this is a menubar. If false, it is a popup + virtual bool IsMenuBar() = 0; + // returns true if this is a menu, which has a tag of menupopup or popup. + // Otherwise, this returns false + virtual bool IsMenu() = 0; + // returns true if this is a context menu + virtual bool IsContextMenu() = 0; + + // indicate that the menubar should become active or inactive + NS_IMETHOD SetActive(bool aActiveFlag) = 0; + + // notify that the menu has been closed and any active state should be + // cleared. This should return true if the menu should be deselected + // by the caller. + virtual bool MenuClosed() = 0; + + // Lock this menu and its parents until they're closed or unlocked. + // A menu being "locked" means that all events inside it that would change the + // selected menu item should be ignored. + // This is used when closing the popup is delayed because of a blink or fade + // animation. + virtual void LockMenuUntilClosed(bool aLock) = 0; + virtual bool IsMenuLocked() = 0; +}; + +#endif + diff --git a/layout/xul/base/src/nsMenuPopupFrame.cpp b/layout/xul/base/src/nsMenuPopupFrame.cpp new file mode 100644 index 000000000..52bf2eab7 --- /dev/null +++ b/layout/xul/base/src/nsMenuPopupFrame.cpp @@ -0,0 +1,2062 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMenuPopupFrame.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsIAtom.h" +#include "nsPresContext.h" +#include "nsStyleContext.h" +#include "nsCSSRendering.h" +#include "nsINameSpaceManager.h" +#include "nsViewManager.h" +#include "nsWidgetsCID.h" +#include "nsMenuFrame.h" +#include "nsMenuBarFrame.h" +#include "nsPopupSetFrame.h" +#include "nsEventDispatcher.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMScreen.h" +#include "nsIPresShell.h" +#include "nsFrameManager.h" +#include "nsIDocument.h" +#include "nsRect.h" +#include "nsIComponentManager.h" +#include "nsBoxLayoutState.h" +#include "nsIScrollableFrame.h" +#include "nsGUIEvent.h" +#include "nsIRootBox.h" +#include "nsIDocShellTreeItem.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsLayoutUtils.h" +#include "nsContentUtils.h" +#include "nsCSSFrameConstructor.h" +#include "nsEventStateManager.h" +#include "nsIPopupBoxObject.h" +#include "nsPIWindowRoot.h" +#include "nsIReflowCallback.h" +#include "nsBindingManager.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIBaseWindow.h" +#include "nsISound.h" +#include "nsIScreenManager.h" +#include "nsIServiceManager.h" +#include "nsThemeConstants.h" +#include "nsDisplayList.h" +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include <algorithm> + +using namespace mozilla; + +int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1; + +// NS_NewMenuPopupFrame +// +// Wrapper for creating a new menu popup container +// +nsIFrame* +NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsMenuPopupFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame) + +NS_QUERYFRAME_HEAD(nsMenuPopupFrame) + NS_QUERYFRAME_ENTRY(nsMenuPopupFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +// +// nsMenuPopupFrame ctor +// +nsMenuPopupFrame::nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext) + :nsBoxFrame(aShell, aContext), + mCurrentMenu(nullptr), + mPrefSize(-1, -1), + mLastClientOffset(0, 0), + mPopupType(ePopupTypePanel), + mPopupState(ePopupClosed), + mPopupAlignment(POPUPALIGNMENT_NONE), + mPopupAnchor(POPUPALIGNMENT_NONE), + mPosition(POPUPPOSITION_UNKNOWN), + mConsumeRollupEvent(nsIPopupBoxObject::ROLLUP_DEFAULT), + mFlipBoth(false), + mIsOpenChanged(false), + mIsContextMenu(false), + mAdjustOffsetForContextMenu(false), + mGeneratedChildren(false), + mMenuCanOverlapOSBar(false), + mShouldAutoPosition(true), + mInContentShell(true), + mIsMenuLocked(false), + mIsDragPopup(false), + mHFlip(false), + mVFlip(false) +{ + // the preference name is backwards here. True means that the 'top' level is + // the default, and false means that the 'parent' level is the default. + if (sDefaultLevelIsTop >= 0) + return; + sDefaultLevelIsTop = + Preferences::GetBool("ui.panel.default_level_parent", false); +} // ctor + + +void +nsMenuPopupFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the + // look&feel object + mMenuCanOverlapOSBar = + LookAndFeel::GetInt(LookAndFeel::eIntID_MenusCanOverlapOSBar) != 0; + + CreatePopupView(); + + // XXX Hack. The popup's view should float above all other views, + // so we use the nsView::SetFloating() to tell the view manager + // about that constraint. + nsView* ourView = GetView(); + nsViewManager* viewManager = ourView->GetViewManager(); + viewManager->SetViewFloating(ourView, true); + + mPopupType = ePopupTypePanel; + nsIDocument* doc = aContent->OwnerDoc(); + int32_t namespaceID; + nsCOMPtr<nsIAtom> tag = doc->BindingManager()->ResolveTag(aContent, &namespaceID); + if (namespaceID == kNameSpaceID_XUL) { + if (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup) + mPopupType = ePopupTypeMenu; + else if (tag == nsGkAtoms::tooltip) + mPopupType = ePopupTypeTooltip; + } + + if (mPopupType == ePopupTypePanel && + aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::drag, eIgnoreCase)) { + mIsDragPopup = true; + } + + nsCOMPtr<nsISupports> cont = PresContext()->GetContainer(); + nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont); + int32_t type = -1; + if (dsti && NS_SUCCEEDED(dsti->GetItemType(&type)) && + type == nsIDocShellTreeItem::typeChrome) + mInContentShell = false; + + // To improve performance, create the widget for the popup only if it is not + // a leaf. Leaf popups such as menus will create their widgets later when + // the popup opens. + if (!IsLeaf() && !ourView->HasWidget()) { + CreateWidgetForView(ourView); + } + + if (aContent->NodeInfo()->Equals(nsGkAtoms::tooltip, kNameSpaceID_XUL) && + aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_default, + nsGkAtoms::_true, eIgnoreCase)) { + nsIRootBox* rootBox = + nsIRootBox::GetRootBox(PresContext()->GetPresShell()); + if (rootBox) { + rootBox->SetDefaultTooltip(aContent); + } + } + + AddStateBits(NS_FRAME_IN_POPUP); +} + +bool +nsMenuPopupFrame::IsNoAutoHide() const +{ + // Panels with noautohide="true" don't hide when the mouse is clicked + // outside of them, or when another application is made active. Non-autohide + // panels cannot be used in content windows. + return (!mInContentShell && mPopupType == ePopupTypePanel && + mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautohide, + nsGkAtoms::_true, eIgnoreCase)); +} + +nsPopupLevel +nsMenuPopupFrame::PopupLevel(bool aIsNoAutoHide) const +{ + // The popup level is determined as follows, in this order: + // 1. non-panels (menus and tooltips) are always topmost + // 2. any specified level attribute + // 3. if a titlebar attribute is set, use the 'floating' level + // 4. if this is a noautohide panel, use the 'parent' level + // 5. use the platform-specific default level + + // If this is not a panel, this is always a top-most popup. + if (mPopupType != ePopupTypePanel) + return ePopupLevelTop; + + // If the level attribute has been set, use that. + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::top, &nsGkAtoms::parent, &nsGkAtoms::floating, nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::level, + strings, eCaseMatters)) { + case 0: + return ePopupLevelTop; + case 1: + return ePopupLevelParent; + case 2: + return ePopupLevelFloating; + } + + // Panels with titlebars most likely want to be floating popups. + if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::titlebar)) + return ePopupLevelFloating; + + // If this panel is a noautohide panel, the default is the parent level. + if (aIsNoAutoHide) + return ePopupLevelParent; + + // Otherwise, the result depends on the platform. + return sDefaultLevelIsTop ? ePopupLevelTop : ePopupLevelParent; +} + +void +nsMenuPopupFrame::EnsureWidget() +{ + nsView* ourView = GetView(); + if (!ourView->HasWidget()) { + NS_ASSERTION(!mGeneratedChildren && !GetFirstPrincipalChild(), + "Creating widget for MenuPopupFrame with children"); + CreateWidgetForView(ourView); + } +} + +nsresult +nsMenuPopupFrame::CreateWidgetForView(nsView* aView) +{ + // Create a widget for ourselves. + nsWidgetInitData widgetData; + widgetData.mWindowType = eWindowType_popup; + widgetData.mBorderStyle = eBorderStyle_default; + widgetData.clipSiblings = true; + widgetData.mPopupHint = mPopupType; + widgetData.mNoAutoHide = IsNoAutoHide(); + widgetData.mIsDragPopup = mIsDragPopup; + + nsAutoString title; + if (mContent && widgetData.mNoAutoHide) { + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::titlebar, + nsGkAtoms::normal, eCaseMatters)) { + widgetData.mBorderStyle = eBorderStyle_title; + + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title); + + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::close, + nsGkAtoms::_true, eCaseMatters)) { + widgetData.mBorderStyle = + static_cast<enum nsBorderStyle>(widgetData.mBorderStyle | eBorderStyle_close); + } + } + } + + nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(this, this); + bool viewHasTransparentContent = !mInContentShell && + (eTransparencyTransparent == + mode); + nsIContent* parentContent = GetContent()->GetParent(); + nsIAtom *tag = nullptr; + if (parentContent) + tag = parentContent->Tag(); + widgetData.mSupportTranslucency = mode == eTransparencyTransparent; + widgetData.mDropShadow = !(viewHasTransparentContent || tag == nsGkAtoms::menulist); + widgetData.mPopupLevel = PopupLevel(widgetData.mNoAutoHide); + + // panels which have a parent level need a parent widget. This allows them to + // always appear in front of the parent window but behind other windows that + // should be in front of it. + nsCOMPtr<nsIWidget> parentWidget; + if (widgetData.mPopupLevel != ePopupLevelTop) { + nsCOMPtr<nsISupports> cont = PresContext()->GetContainer(); + nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont); + if (!dsti) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + dsti->GetTreeOwner(getter_AddRefs(treeOwner)); + if (!treeOwner) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(treeOwner)); + if (baseWindow) + baseWindow->GetMainWidget(getter_AddRefs(parentWidget)); + } + + nsresult rv = aView->CreateWidgetForPopup(&widgetData, parentWidget, + true, true); + if (NS_FAILED(rv)) { + return rv; + } + + nsIWidget* widget = aView->GetWidget(); + widget->SetTransparencyMode(mode); + widget->SetWindowShadowStyle(GetShadowStyle()); + + // most popups don't have a title so avoid setting the title if there isn't one + if (!title.IsEmpty()) { + widget->SetTitle(title); + } + + return NS_OK; +} + +uint8_t +nsMenuPopupFrame::GetShadowStyle() +{ + uint8_t shadow = StyleUIReset()->mWindowShadow; + if (shadow != NS_STYLE_WINDOW_SHADOW_DEFAULT) + return shadow; + + switch (StyleDisplay()->mAppearance) { + 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 popupshown events asynchronously. +class nsXULPopupShownEvent : public nsRunnable +{ +public: + nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext) + : mPopup(aPopup), mPresContext(aPresContext) + { + } + + NS_IMETHOD Run() + { + nsMouseEvent event(true, NS_XUL_POPUP_SHOWN, nullptr, nsMouseEvent::eReal); + return nsEventDispatcher::Dispatch(mPopup, mPresContext, &event); + } + +private: + nsCOMPtr<nsIContent> mPopup; + nsRefPtr<nsPresContext> mPresContext; +}; + +NS_IMETHODIMP +nsMenuPopupFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + // unless the list is empty, indicate that children have been generated. + if (aChildList.NotEmpty()) + mGeneratedChildren = true; + return nsBoxFrame::SetInitialChildList(aListID, aChildList); +} + +bool +nsMenuPopupFrame::IsLeaf() const +{ + if (mGeneratedChildren) + return false; + + if (mPopupType != ePopupTypeMenu) { + // any panel with a type attribute, such as the autocomplete popup, + // is always generated right away. + return !mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::type); + } + + // menu popups generate their child frames lazily only when opened, so + // behave like a leaf frame. However, generate child frames normally if + // the parent menu has a sizetopopup attribute. In this case the size of + // the parent menu is dependent on the size of the popup, so the frames + // need to exist in order to calculate this size. + nsIContent* parentContent = mContent->GetParent(); + return (parentContent && + !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup)); +} + +void +nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool aSizedToPopup) +{ + if (!mGeneratedChildren) + return; + + SchedulePaint(); + + bool shouldPosition = true; + bool isOpen = IsOpen(); + if (!isOpen) { + // if the popup is not open, only do layout while showing or if the menu + // is sized to the popup + shouldPosition = (mPopupState == ePopupShowing); + if (!shouldPosition && !aSizedToPopup) + return; + } + + // if the popup has just been opened, make sure the scrolled window is at 0,0 + if (mIsOpenChanged) { + nsIScrollableFrame *scrollframe = do_QueryFrame(GetChildBox()); + if (scrollframe) { + nsWeakFrame weakFrame(this); + scrollframe->ScrollTo(nsPoint(0,0), nsIScrollableFrame::INSTANT); + if (!weakFrame.IsAlive()) { + return; + } + } + } + + // get the preferred, minimum and maximum size. If the menu is sized to the + // popup, then the popup's width is the menu's width. + nsSize prefSize = GetPrefSize(aState); + nsSize minSize = GetMinSize(aState); + nsSize maxSize = GetMaxSize(aState); + + if (aSizedToPopup) { + prefSize.width = aParentMenu->GetRect().width; + } + prefSize = BoundsCheck(minSize, prefSize, maxSize); + + // if the size changed then set the bounds to be the preferred size + bool sizeChanged = (mPrefSize != prefSize); + if (sizeChanged) { + SetBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false); + mPrefSize = prefSize; + } + + if (shouldPosition) { + SetPopupPosition(aParentMenu, false); + } + + nsRect bounds(GetRect()); + Layout(aState); + + // if the width or height changed, readjust the popup position. This is a + // special case for tooltips where the preferred height doesn't include the + // real height for its inline element, but does once it is laid out. + // This is bug 228673 which doesn't have a simple fix. + if (!aParentMenu) { + nsSize newsize = GetSize(); + if (newsize.width > bounds.width || newsize.height > bounds.height) { + // the size after layout was larger than the preferred size, + // so set the preferred size accordingly + mPrefSize = newsize; + if (isOpen) { + SetPopupPosition(nullptr, false); + } + } + } + + nsPresContext* pc = PresContext(); + nsView* view = GetView(); + + if (sizeChanged) { + // If the size of the popup changed, apply any size constraints. + nsIWidget* widget = view->GetWidget(); + if (widget) { + SetSizeConstraints(pc, widget, minSize, maxSize); + } + } + + if (isOpen) { + nsViewManager* viewManager = view->GetViewManager(); + nsRect rect = GetRect(); + rect.x = rect.y = 0; + viewManager->ResizeView(view, rect); + + viewManager->SetViewVisibility(view, nsViewVisibility_kShow); + mPopupState = ePopupOpenAndVisible; + nsContainerFrame::SyncFrameViewProperties(pc, this, nullptr, view, 0); + } + + // finally, if the popup just opened, send a popupshown event + if (mIsOpenChanged) { + mIsOpenChanged = false; + nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), pc); + NS_DispatchToCurrentThread(event); + } +} + +nsIContent* +nsMenuPopupFrame::GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame) +{ + while (aMenuPopupFrame) { + if (aMenuPopupFrame->mTriggerContent) + return aMenuPopupFrame->mTriggerContent; + + // check up the menu hierarchy until a popup with a trigger node is found + nsMenuFrame* menuFrame = do_QueryFrame(aMenuPopupFrame->GetParent()); + if (!menuFrame) + break; + + nsMenuParent* parentPopup = menuFrame->GetMenuParent(); + if (!parentPopup || !parentPopup->IsMenu()) + break; + + aMenuPopupFrame = static_cast<nsMenuPopupFrame *>(parentPopup); + } + + return nullptr; +} + +void +nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor, + const nsAString& aAlign) +{ + mTriggerContent = nullptr; + + 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; + else if (aAnchor.EqualsLiteral("leftcenter")) + mPopupAnchor = POPUPALIGNMENT_LEFTCENTER; + else if (aAnchor.EqualsLiteral("rightcenter")) + mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER; + else if (aAnchor.EqualsLiteral("topcenter")) + mPopupAnchor = POPUPALIGNMENT_TOPCENTER; + else if (aAnchor.EqualsLiteral("bottomcenter")) + mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER; + else + mPopupAnchor = POPUPALIGNMENT_NONE; + + if (aAlign.EqualsLiteral("topleft")) + mPopupAlignment = POPUPALIGNMENT_TOPLEFT; + else if (aAlign.EqualsLiteral("topright")) + mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; + else if (aAlign.EqualsLiteral("bottomleft")) + mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; + else if (aAlign.EqualsLiteral("bottomright")) + mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; + else + mPopupAlignment = POPUPALIGNMENT_NONE; + + mPosition = POPUPPOSITION_UNKNOWN; +} + +void +nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent, + nsIContent* aTriggerContent, + const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + bool aAttributesOverride) +{ + EnsureWidget(); + + mPopupState = ePopupShowing; + mAnchorContent = aAnchorContent; + mTriggerContent = aTriggerContent; + mXPos = aXPos; + mYPos = aYPos; + mAdjustOffsetForContextMenu = false; + mVFlip = false; + mHFlip = false; + mAlignmentOffset = 0; + + // 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) { + nsAutoString anchor, align, position, flip; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor); + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align); + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::position, position); + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::flip, flip); + + if (aAttributesOverride) { + // if the attributes are set, clear the offset position. Otherwise, + // the offset is used to adjust the position from the anchor point + if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty()) + position.Assign(aPosition); + else + mXPos = mYPos = 0; + } + else if (!aPosition.IsEmpty()) { + position.Assign(aPosition); + } + + mFlipBoth = flip.EqualsLiteral("both"); + mSlide = flip.EqualsLiteral("slide"); + + position.CompressWhitespace(); + int32_t spaceIdx = position.FindChar(' '); + // if there is a space in the position, assume it is the anchor and + // alignment as two separate tokens. + if (spaceIdx >= 0) { + InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1)); + } + else if (position.EqualsLiteral("before_start")) { + mPopupAnchor = POPUPALIGNMENT_TOPLEFT; + mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; + mPosition = POPUPPOSITION_BEFORESTART; + } + else if (position.EqualsLiteral("before_end")) { + mPopupAnchor = POPUPALIGNMENT_TOPRIGHT; + mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; + mPosition = POPUPPOSITION_BEFOREEND; + } + else if (position.EqualsLiteral("after_start")) { + mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; + mPopupAlignment = POPUPALIGNMENT_TOPLEFT; + mPosition = POPUPPOSITION_AFTERSTART; + } + else if (position.EqualsLiteral("after_end")) { + mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT; + mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; + mPosition = POPUPPOSITION_AFTEREND; + } + else if (position.EqualsLiteral("start_before")) { + mPopupAnchor = POPUPALIGNMENT_TOPLEFT; + mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; + mPosition = POPUPPOSITION_STARTBEFORE; + } + else if (position.EqualsLiteral("start_after")) { + mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; + mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; + mPosition = POPUPPOSITION_STARTAFTER; + } + else if (position.EqualsLiteral("end_before")) { + mPopupAnchor = POPUPALIGNMENT_TOPRIGHT; + mPopupAlignment = POPUPALIGNMENT_TOPLEFT; + mPosition = POPUPPOSITION_ENDBEFORE; + } + else if (position.EqualsLiteral("end_after")) { + mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT; + mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; + mPosition = POPUPPOSITION_ENDAFTER; + } + else if (position.EqualsLiteral("overlap")) { + mPopupAnchor = POPUPALIGNMENT_TOPLEFT; + mPopupAlignment = POPUPALIGNMENT_TOPLEFT; + mPosition = POPUPPOSITION_OVERLAP; + } + else if (position.EqualsLiteral("after_pointer")) { + mPopupAnchor = POPUPALIGNMENT_TOPLEFT; + mPopupAlignment = POPUPALIGNMENT_TOPLEFT; + mPosition = POPUPPOSITION_AFTERPOINTER; + // XXXndeakin this is supposed to anchor vertically after, but with the + // horizontal position as the mouse pointer. + mYPos += 21; + } + else { + InitPositionFromAnchorAlign(anchor, align); + } + } + + mScreenXPos = -1; + mScreenYPos = -1; + + if (aAttributesOverride) { + // Use |left| and |top| dimension attributes to position the popup if + // present, as they may have been persisted. + nsAutoString left, top; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left); + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top); + + nsresult err; + if (!left.IsEmpty()) { + int32_t x = left.ToInteger(&err); + if (NS_SUCCEEDED(err)) + mScreenXPos = x; + } + if (!top.IsEmpty()) { + int32_t y = top.ToInteger(&err); + if (NS_SUCCEEDED(err)) + mScreenYPos = y; + } + } +} + +void +nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu) +{ + EnsureWidget(); + + mPopupState = ePopupShowing; + mAnchorContent = nullptr; + mTriggerContent = aTriggerContent; + mScreenXPos = aXPos; + mScreenYPos = aYPos; + mFlipBoth = false; + mSlide = false; + mPopupAnchor = POPUPALIGNMENT_NONE; + mPopupAlignment = POPUPALIGNMENT_NONE; + mIsContextMenu = aIsContextMenu; + mAdjustOffsetForContextMenu = aIsContextMenu; +} + +void +nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent, + nsAString& aAnchor, + nsAString& aAlign, + int32_t aXPos, int32_t aYPos) +{ + EnsureWidget(); + + mPopupState = ePopupShowing; + mAdjustOffsetForContextMenu = false; + mFlipBoth = false; + mSlide = false; + + // this popup opening function is provided for backwards compatibility + // only. It accepts either coordinates or an anchor and alignment value + // but doesn't use both together. + if (aXPos == -1 && aYPos == -1) { + mAnchorContent = aAnchorContent; + mScreenXPos = -1; + mScreenYPos = -1; + mXPos = 0; + mYPos = 0; + InitPositionFromAnchorAlign(aAnchor, aAlign); + } + else { + mAnchorContent = nullptr; + mPopupAnchor = POPUPALIGNMENT_NONE; + mPopupAlignment = POPUPALIGNMENT_NONE; + mScreenXPos = aXPos; + mScreenYPos = aYPos; + mXPos = aXPos; + mYPos = aYPos; + } +} + +void +nsMenuPopupFrame::ShowPopup(bool aIsContextMenu, bool aSelectFirstItem) +{ + mIsContextMenu = aIsContextMenu; + + InvalidateFrameSubtree(); + + if (mPopupState == ePopupShowing) { + mPopupState = ePopupOpen; + mIsOpenChanged = true; + + nsMenuFrame* menuFrame = do_QueryFrame(GetParent()); + if (menuFrame) { + nsWeakFrame weakFrame(this); + menuFrame->PopupOpened(); + if (!weakFrame.IsAlive()) + return; + } + + // do we need an actual reflow here? + // is SetPopupPosition all that is needed? + PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + + if (mPopupType == ePopupTypeMenu) { + nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1")); + if (sound) + sound->PlayEventSound(nsISound::EVENT_MENU_POPUP); + } + } + + mShouldAutoPosition = true; +} + +void +nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState) +{ + 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(nullptr); + } + } + } + } + mTriggerContent = nullptr; + mAnchorContent = nullptr; + } + + // 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; + } + + mPopupState = aNewState; + + if (IsMenu()) + SetCurrentMenuItem(nullptr); + + mIncrementalString.Truncate(); + + LockMenuUntilClosed(false); + + mIsOpenChanged = false; + mCurrentMenu = nullptr; // make sure no current menu is set + mHFlip = mVFlip = false; + + nsView* view = GetView(); + nsViewManager* viewManager = view->GetViewManager(); + viewManager->SetViewVisibility(view, nsViewVisibility_kHide); + + FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent); + + // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no + // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually. + // This code may not the best solution, but we can leave it here until we find the better approach. + NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?"); + nsEventStates state = mContent->AsElement()->State(); + + if (state.HasState(NS_EVENT_STATE_HOVER)) { + nsEventStateManager *esm = PresContext()->EventStateManager(); + esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER); + } + + nsMenuFrame* menuFrame = do_QueryFrame(GetParent()); + if (menuFrame) { + menuFrame->PopupClosed(aDeselectMenu); + } +} + +void +nsMenuPopupFrame::GetLayoutFlags(uint32_t& aFlags) +{ + aFlags = NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_VISIBILITY; +} + +/////////////////////////////////////////////////////////////////////////////// +// GetRootViewForPopup +// Retrieves the view for the popup widget that contains the given frame. +// If the given frame is not contained by a popup widget, return the +// root view of the root viewmanager. +nsView* +nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame) +{ + nsView* view = aStartFrame->GetClosestView(); + NS_ASSERTION(view, "frame must have a closest view!"); + while (view) { + // Walk up the view hierarchy looking for a view whose widget has a + // window type of eWindowType_popup - in other words a popup window + // widget. If we find one, this is the view we want. + nsIWidget* widget = view->GetWidget(); + if (widget) { + nsWindowType wtype; + widget->GetWindowType(wtype); + if (wtype == eWindowType_popup) { + return view; + } + } + + nsView* temp = view->GetParent(); + if (!temp) { + // Otherwise, we've walked all the way up to the root view and not + // found a view for a popup window widget. Just return the root view. + return view; + } + view = temp; + } + + return nullptr; +} + +nsPoint +nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect& anchorRect, + FlipStyle& aHFlip, FlipStyle& aVFlip) +{ + // flip the anchor and alignment for right-to-left + int8_t popupAnchor(mPopupAnchor); + int8_t popupAlign(mPopupAlignment); + if (IsDirectionRTL()) { + // no need to flip the centered anchor types vertically + if (popupAnchor <= POPUPALIGNMENT_LEFTCENTER) { + popupAnchor = -popupAnchor; + } + popupAlign = -popupAlign; + } + + // first, determine at which corner of the anchor the popup should appear + nsPoint pnt; + switch (popupAnchor) { + case POPUPALIGNMENT_LEFTCENTER: + pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2); + anchorRect.y = pnt.y; + anchorRect.height = 0; + break; + case POPUPALIGNMENT_RIGHTCENTER: + pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2); + anchorRect.y = pnt.y; + anchorRect.height = 0; + break; + case POPUPALIGNMENT_TOPCENTER: + pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y); + anchorRect.x = pnt.x; + anchorRect.width = 0; + break; + case POPUPALIGNMENT_BOTTOMCENTER: + pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost()); + anchorRect.x = pnt.x; + anchorRect.width = 0; + break; + case POPUPALIGNMENT_TOPRIGHT: + pnt = anchorRect.TopRight(); + break; + case POPUPALIGNMENT_BOTTOMLEFT: + pnt = anchorRect.BottomLeft(); + break; + case POPUPALIGNMENT_BOTTOMRIGHT: + pnt = anchorRect.BottomRight(); + break; + case POPUPALIGNMENT_TOPLEFT: + default: + pnt = anchorRect.TopLeft(); + break; + } + + // If the alignment is on the right edge of the popup, move the popup left + // by the width. Similarly, if the alignment is on the bottom edge of the + // popup, move the popup up by the height. In addition, account for the + // margins of the popup on the edge on which it is aligned. + nsMargin margin(0, 0, 0, 0); + StyleMargin()->GetMargin(margin); + switch (popupAlign) { + case POPUPALIGNMENT_TOPRIGHT: + pnt.MoveBy(-mRect.width - margin.right, margin.top); + break; + case POPUPALIGNMENT_BOTTOMLEFT: + pnt.MoveBy(margin.left, -mRect.height - margin.bottom); + break; + case POPUPALIGNMENT_BOTTOMRIGHT: + pnt.MoveBy(-mRect.width - margin.right, -mRect.height - margin.bottom); + break; + case POPUPALIGNMENT_TOPLEFT: + default: + pnt.MoveBy(margin.left, margin.top); + break; + } + + // Flipping horizontally is allowed as long as the popup is above or below + // the anchor. This will happen if both the anchor and alignment are top or + // both are bottom, but different values. Similarly, flipping vertically is + // allowed if the popup is to the left or right of the anchor. In this case, + // the values of the constants are such that both must be positive or both + // must be negative. A special case, used for overlap, allows flipping + // vertically as well. + // If we are flipping in both directions, we want to set a flip style both + // horizontally and vertically. However, we want to flip on the inside edge + // of the anchor. Consider the example of a typical dropdown menu. + // Vertically, we flip the popup on the outside edges of the anchor menu, + // however horizontally, we want to to use the inside edges so the popup + // still appears underneath the anchor menu instead of floating off the + // side of the menu. + switch (popupAnchor) { + case POPUPALIGNMENT_LEFTCENTER: + case POPUPALIGNMENT_RIGHTCENTER: + aHFlip = FlipStyle_Outside; + aVFlip = FlipStyle_Inside; + break; + case POPUPALIGNMENT_TOPCENTER: + case POPUPALIGNMENT_BOTTOMCENTER: + aHFlip = FlipStyle_Inside; + aVFlip = FlipStyle_Outside; + break; + default: + { + FlipStyle anchorEdge = mFlipBoth ? FlipStyle_Inside : FlipStyle_None; + aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge; + if (((popupAnchor > 0) == (popupAlign > 0)) || + (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT)) + aVFlip = FlipStyle_Outside; + else + aVFlip = anchorEdge; + break; + } + } + + return pnt; +} + +nscoord +nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize, + nscoord aScreenBegin, nscoord aScreenEnd, + nscoord *aOffset) +{ + // The popup may be positioned such that either the left/top or bottom/right + // is outside the screen - but never both. + if (aScreenPoint < aScreenBegin) { + *aOffset = aScreenBegin - aScreenPoint; + aScreenPoint = aScreenBegin; + } else if (aScreenPoint + aSize > aScreenEnd) { + *aOffset = aScreenPoint + aSize - aScreenEnd; + aScreenPoint = std::max(aScreenEnd - aSize, 0); + } + return std::min(aSize, aScreenEnd - aScreenPoint); +} + +nscoord +nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize, + nscoord aScreenBegin, nscoord aScreenEnd, + nscoord aAnchorBegin, nscoord aAnchorEnd, + nscoord aMarginBegin, nscoord aMarginEnd, + nscoord aOffsetForContextMenu, FlipStyle aFlip, + bool* aFlipSide) +{ + // all of the coordinates used here are in app units relative to the screen + nscoord popupSize = aSize; + if (aScreenPoint < aScreenBegin) { + // at its current position, the popup would extend past the left or top + // edge of the screen, so it will have to be moved or resized. + if (aFlip) { + // for inside flips, we flip on the opposite side of the anchor + nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd; + nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin; + + // check whether there is more room to the left and right (or top and + // bottom) of the anchor and put the popup on the side with more room. + if (startpos - aScreenBegin >= aScreenEnd - endpos) { + aScreenPoint = aScreenBegin; + popupSize = startpos - aScreenPoint - aMarginEnd; + } + else { + // If the newly calculated position is different than the existing + // position, flip such that the popup is to the right or bottom of the + // anchor point instead . However, when flipping use the same margin + // size. + nscoord newScreenPoint = endpos + aMarginEnd; + if (newScreenPoint != aScreenPoint) { + *aFlipSide = true; + aScreenPoint = newScreenPoint; + // check if the new position is still off the right or bottom edge of + // the screen. If so, resize the popup. + if (aScreenPoint + aSize > aScreenEnd) { + popupSize = aScreenEnd - aScreenPoint; + } + } + } + } + else { + aScreenPoint = aScreenBegin; + } + } + else if (aScreenPoint + aSize > aScreenEnd) { + // at its current position, the popup would extend past the right or + // bottom edge of the screen, so it will have to be moved or resized. + if (aFlip) { + // for inside flips, we flip on the opposite side of the anchor + nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd; + nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin; + + // check whether there is more room to the left and right (or top and + // bottom) of the anchor and put the popup on the side with more room. + if (aScreenEnd - endpos >= startpos - aScreenBegin) { + if (mIsContextMenu) { + aScreenPoint = aScreenEnd - aSize; + } + else { + aScreenPoint = endpos + aMarginBegin; + popupSize = aScreenEnd - aScreenPoint; + } + } + else { + // if the newly calculated position is different than the existing + // position, we flip such that the popup is to the left or top of the + // anchor point instead. + nscoord newScreenPoint = startpos - aSize - aMarginBegin - aOffsetForContextMenu; + if (newScreenPoint != aScreenPoint) { + *aFlipSide = true; + aScreenPoint = newScreenPoint; + + // check if the new position is still off the left or top edge of the + // screen. If so, resize the popup. + if (aScreenPoint < aScreenBegin) { + aScreenPoint = aScreenBegin; + if (!mIsContextMenu) { + popupSize = startpos - aScreenPoint - aMarginBegin; + } + } + } + } + } + else { + aScreenPoint = aScreenEnd - aSize; + } + } + + // Make sure that the point is within the screen boundaries and that the + // size isn't off the edge of the screen. This can happen when a large + // positive or negative margin is used. + if (aScreenPoint < aScreenBegin) { + aScreenPoint = aScreenBegin; + } + if (aScreenPoint > aScreenEnd) { + aScreenPoint = aScreenEnd - aSize; + } + + // If popupSize ended up being negative, or the original size was actually + // smaller than the calculated popup size, just use the original size instead. + if (popupSize <= 0 || aSize < popupSize) { + popupSize = aSize; + } + return std::min(popupSize, aScreenEnd - aScreenPoint); +} + +nsresult +nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove) +{ + if (!mShouldAutoPosition) + return NS_OK; + + // If this is due to a move, return early if the popup hasn't been laid out + // yet. On Windows, this can happen when using a drag popup before it opens. + if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) { + return NS_OK; + } + + nsPresContext* presContext = PresContext(); + nsIFrame* rootFrame = presContext->PresShell()->FrameManager()->GetRootFrame(); + NS_ASSERTION(rootFrame->GetView() && GetView() && + rootFrame->GetView() == GetView()->GetParent(), + "rootFrame's view is not our view's parent???"); + + // if the frame is not specified, use the anchor node passed to OpenPopup. If + // that wasn't specified either, use the root frame. Note that mAnchorContent + // might be a different document so its presshell must be used. + if (!aAnchorFrame) { + if (mAnchorContent) { + aAnchorFrame = mAnchorContent->GetPrimaryFrame(); + } + + if (!aAnchorFrame) { + aAnchorFrame = rootFrame; + if (!aAnchorFrame) + return NS_OK; + } + } + + bool sizedToPopup = false; + if (aAnchorFrame->GetContent()) { + // the popup should be the same size as the anchor menu, for example, a menulist. + sizedToPopup = nsMenuFrame::IsSizedToPopup(aAnchorFrame->GetContent(), false); + } + + // the dimensions of the anchor in its app units + nsRect parentRect = aAnchorFrame->GetScreenRectInAppUnits(); + + // the anchor may be in a different document with a different scale, + // so adjust the size so that it is in the app units of the popup instead + // of the anchor. + parentRect = parentRect.ConvertAppUnitsRoundOut( + aAnchorFrame->PresContext()->AppUnitsPerDevPixel(), + presContext->AppUnitsPerDevPixel()); + + // Set the popup's size to the preferred size. Below, this size will be + // adjusted to fit on the screen or within the content area. If the anchor + // is sized to the popup, use the anchor's width instead of the preferred + // width. The preferred size should already be set by the parent frame. + NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0, + "preferred size of popup not set"); + mRect.width = sizedToPopup ? parentRect.width : mPrefSize.width; + mRect.height = mPrefSize.height; + + // the screen position in app units where the popup should appear + nsPoint screenPoint; + + // For anchored popups, the anchor rectangle. For non-anchored popups, the + // size will be 0. + nsRect anchorRect = parentRect; + + // indicators of whether the popup should be flipped or resized. + FlipStyle hFlip = FlipStyle_None, vFlip = FlipStyle_None; + + nsMargin margin(0, 0, 0, 0); + StyleMargin()->GetMargin(margin); + + // the screen rectangle of the root frame, in dev pixels. + nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits(); + + nsDeviceContext* devContext = presContext->DeviceContext(); + nscoord offsetForContextMenu = 0; + + if (IsAnchored()) { + // if we are anchored, there are certain things we don't want to do when + // repositioning the popup to fit on the screen, such as end up positioned + // over the anchor, for instance a popup appearing over the menu label. + // When doing this reposition, we want to move the popup to the side with + // the most room. The combination of anchor and alignment dictate if we + // readjust above/below or to the left/right. + if (mAnchorContent) { + // move the popup according to the anchor and alignment. This will also + // tell us which axis the popup is flush against in case we have to move + // it around later. The AdjustPositionForAnchorAlign method accounts for + // the popup's margin. + screenPoint = AdjustPositionForAnchorAlign(anchorRect, hFlip, vFlip); + } + else { + // with no anchor, the popup is positioned relative to the root frame + anchorRect = rootScreenRect; + screenPoint = anchorRect.TopLeft() + nsPoint(margin.left, margin.top); + } + + // mXPos and mYPos specify an additonal offset passed to OpenPopup that + // should be added to the position. We also add the offset to the anchor + // pos so a later flip/resize takes the offset into account. + nscoord anchorXOffset = presContext->CSSPixelsToAppUnits(mXPos); + if (IsDirectionRTL()) { + screenPoint.x -= anchorXOffset; + anchorRect.x -= anchorXOffset; + } else { + screenPoint.x += anchorXOffset; + anchorRect.x += anchorXOffset; + } + nscoord anchorYOffset = presContext->CSSPixelsToAppUnits(mYPos); + screenPoint.y += anchorYOffset; + anchorRect.y += anchorYOffset; + + // If this is a noautohide popup, set the screen coordinates of the popup. + // This way, the popup stays at the location where it was opened even when + // the window is moved. Popups at the parent level follow the parent + // window as it is moved and remained anchored, so we want to maintain the + // anchoring instead. + if (IsNoAutoHide() && PopupLevel(true) != ePopupLevelParent) { + // Account for the margin that will end up being added to the screen coordinate + // the next time SetPopupPosition is called. + mScreenXPos = presContext->AppUnitsToIntCSSPixels(screenPoint.x - margin.left); + mScreenYPos = presContext->AppUnitsToIntCSSPixels(screenPoint.y - margin.top); + } + } + else { + // the popup is positioned at a screen coordinate. + // first convert the screen position in mScreenXPos and mScreenYPos from + // CSS pixels into device pixels, ignoring any scaling as mScreenXPos and + // mScreenYPos are unscaled screen coordinates. + int32_t factor = devContext->UnscaledAppUnitsPerDevPixel(); + + // context menus should be offset by two pixels so that they don't appear + // directly where the cursor is. Otherwise, it is too easy to have the + // context menu close up again. + if (mAdjustOffsetForContextMenu) { + int32_t offsetForContextMenuDev = + nsPresContext::CSSPixelsToAppUnits(CONTEXT_MENU_OFFSET_PIXELS) / factor; + offsetForContextMenu = presContext->DevPixelsToAppUnits(offsetForContextMenuDev); + } + + // next, convert into app units accounting for the scaling + screenPoint.x = presContext->DevPixelsToAppUnits( + nsPresContext::CSSPixelsToAppUnits(mScreenXPos) / factor); + screenPoint.y = presContext->DevPixelsToAppUnits( + nsPresContext::CSSPixelsToAppUnits(mScreenYPos) / factor); + anchorRect = nsRect(screenPoint, nsSize(0, 0)); + + // add the margins on the popup + screenPoint.MoveBy(margin.left + offsetForContextMenu, + margin.top + offsetForContextMenu); + + // screen positioned popups can be flipped vertically but never horizontally + vFlip = FlipStyle_Outside; + } + + // If a panel is being moved, don't constrain or flip it. But always do this for + // content shells, so that the popup doesn't extend outside the containing frame. + if (mInContentShell || !aIsMove || mPopupType != ePopupTypePanel) { + nsRect screenRect = GetConstraintRect(anchorRect, rootScreenRect); + + // ensure that anchorRect is on screen + if (!anchorRect.IntersectRect(anchorRect, screenRect)) { + anchorRect.width = anchorRect.height = 0; + // if the anchor isn't within the screen, move it to the edge of the screen. + if (anchorRect.x < screenRect.x) + anchorRect.x = screenRect.x; + if (anchorRect.XMost() > screenRect.XMost()) + anchorRect.x = screenRect.XMost(); + if (anchorRect.y < screenRect.y) + anchorRect.y = screenRect.y; + if (anchorRect.YMost() > screenRect.YMost()) + anchorRect.y = screenRect.YMost(); + } + + // shrink the the popup down if it is larger than the screen size + if (mRect.width > screenRect.width) + mRect.width = screenRect.width; + if (mRect.height > screenRect.height) + mRect.height = screenRect.height; + + // at this point the anchor (anchorRect) is within the available screen + // area (screenRect) and the popup is known to be no larger than the screen. + + // We might want to "slide" an arrow if the panel is of the correct type - + // but we can only slide on one axis - the other axis must be "flipped or + // resized" as normal. + bool slideHorizontal = mSlide && mPosition <= POPUPPOSITION_AFTEREND; + bool slideVertical = mSlide && mPosition >= POPUPPOSITION_STARTBEFORE; + + // Next, check if there is enough space to show the popup at full size when + // positioned at screenPoint. If not, flip the popups to the opposite side + // of their anchor point, or resize them as necessary. + if (slideHorizontal) { + mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x, + screenRect.XMost(), &mAlignmentOffset); + } else { + mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x, + screenRect.XMost(), anchorRect.x, anchorRect.XMost(), + margin.left, margin.right, offsetForContextMenu, hFlip, + &mHFlip); + } + if (slideVertical) { + mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y, + screenRect.YMost(), &mAlignmentOffset); + } else { + mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y, + screenRect.YMost(), anchorRect.y, anchorRect.YMost(), + margin.top, margin.bottom, offsetForContextMenu, vFlip, + &mVFlip); + } + + NS_ASSERTION(screenPoint.x >= screenRect.x && screenPoint.y >= screenRect.y && + screenPoint.x + mRect.width <= screenRect.XMost() && + screenPoint.y + mRect.height <= screenRect.YMost(), + "Popup is offscreen"); + } + + // determine the x and y position of the view by subtracting the desired + // screen position from the screen position of the root frame. + nsPoint viewPoint = screenPoint - rootScreenRect.TopLeft(); + + // snap the view's position to device pixels, see bug 622507 + viewPoint.x = presContext->RoundAppUnitsToNearestDevPixels(viewPoint.x); + viewPoint.y = presContext->RoundAppUnitsToNearestDevPixels(viewPoint.y); + + nsView* view = GetView(); + NS_ASSERTION(view, "popup with no view"); + + // Offset the position by the width and height of the borders and titlebar. + // Even though GetClientOffset should return (0, 0) when there is no + // titlebar or borders, we skip these calculations anyway for non-panels + // to save time since they will never have a titlebar. + nsIWidget* widget = view->GetWidget(); + if (mPopupType == ePopupTypePanel && widget) { + mLastClientOffset = widget->GetClientOffset(); + viewPoint.x += presContext->DevPixelsToAppUnits(mLastClientOffset.x); + viewPoint.y += presContext->DevPixelsToAppUnits(mLastClientOffset.y); + } + + presContext->GetPresShell()->GetViewManager()-> + MoveViewTo(view, viewPoint.x, viewPoint.y); + + // Now that we've positioned the view, sync up the frame's origin. + nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame)); + + if (sizedToPopup) { + nsBoxLayoutState state(PresContext()); + // XXXndeakin can parentSize.width still extend outside? + SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height)); + } + + return NS_OK; +} + +/* virtual */ nsMenuFrame* +nsMenuPopupFrame::GetCurrentMenuItem() +{ + return mCurrentMenu; +} + +nsRect +nsMenuPopupFrame::GetConstraintRect(const nsRect& aAnchorRect, + const nsRect& aRootScreenRect) +{ + nsIntRect screenRectPixels; + nsPresContext* presContext = PresContext(); + + // determine the available screen space. It will be reduced by the OS chrome + // such as menubars. It addition, for content shells, it will be the area of + // the content rather than the screen. + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (sm) { + // for content shells, get the screen where the root frame is located. + // This is because we need to constrain the content to this content area, + // so we should use the same screen. Otherwise, use the screen where the + // anchor is located. + nsRect rect = mInContentShell ? aRootScreenRect : aAnchorRect; + // nsIScreenManager::ScreenForRect wants the coordinates in CSS pixels + int32_t width = std::max(1, nsPresContext::AppUnitsToIntCSSPixels(rect.width)); + int32_t height = std::max(1, nsPresContext::AppUnitsToIntCSSPixels(rect.height)); + sm->ScreenForRect(nsPresContext::AppUnitsToIntCSSPixels(rect.x), + nsPresContext::AppUnitsToIntCSSPixels(rect.y), + width, height, getter_AddRefs(screen)); + if (screen) { + // get the total screen area if the popup is allowed to overlap it. + if (mMenuCanOverlapOSBar && !mInContentShell) + screen->GetRect(&screenRectPixels.x, &screenRectPixels.y, + &screenRectPixels.width, &screenRectPixels.height); + else + screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y, + &screenRectPixels.width, &screenRectPixels.height); + } + } + + // keep a 3 pixel margin to the right and bottom of the screen for the WinXP dropshadow + screenRectPixels.SizeTo(screenRectPixels.width - 3, screenRectPixels.height - 3); + + nsRect screenRect = screenRectPixels.ToAppUnits(presContext->AppUnitsPerDevPixel()); + if (mInContentShell) { + // for content shells, clip to the client area rather than the screen area + screenRect.IntersectRect(screenRect, aRootScreenRect); + } + + return screenRect; +} + +void nsMenuPopupFrame::CanAdjustEdges(int8_t aHorizontalSide, int8_t aVerticalSide, nsIntPoint& aChange) +{ + int8_t popupAlign(mPopupAlignment); + if (IsDirectionRTL()) { + popupAlign = -popupAlign; + } + + if (aHorizontalSide == (mHFlip ? NS_SIDE_RIGHT : NS_SIDE_LEFT)) { + if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_BOTTOMLEFT) { + aChange.x = 0; + } + } + else if (aHorizontalSide == (mHFlip ? NS_SIDE_LEFT : NS_SIDE_RIGHT)) { + if (popupAlign == POPUPALIGNMENT_TOPRIGHT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) { + aChange.x = 0; + } + } + + if (aVerticalSide == (mVFlip ? NS_SIDE_BOTTOM : NS_SIDE_TOP)) { + if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_TOPRIGHT) { + aChange.y = 0; + } + } + else if (aVerticalSide == (mVFlip ? NS_SIDE_TOP : NS_SIDE_BOTTOM)) { + if (popupAlign == POPUPALIGNMENT_BOTTOMLEFT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) { + aChange.y = 0; + } + } +} + +bool nsMenuPopupFrame::ConsumeOutsideClicks() +{ + // If the popup has explicitly set a consume mode, honor that. + if (mConsumeRollupEvent != nsIPopupBoxObject::ROLLUP_DEFAULT) + return (mConsumeRollupEvent == nsIPopupBoxObject::ROLLUP_CONSUME); + + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks, + nsGkAtoms::_true, eCaseMatters)) + return true; + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks, + nsGkAtoms::_false, eCaseMatters)) + return false; + + nsCOMPtr<nsIContent> parentContent = mContent->GetParent(); + if (parentContent) { + nsINodeInfo *ni = parentContent->NodeInfo(); + if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL)) + return true; // Consume outside clicks for combo boxes on all platforms +#if defined(XP_WIN) || defined(XP_OS2) + // Don't consume outside clicks for menus in Windows + if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) || + ni->Equals(nsGkAtoms::splitmenu, kNameSpaceID_XUL) || + ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL) || + ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL) || + ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL)) && + (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::menu, eCaseMatters) || + parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::menuButton, eCaseMatters)))) { + return false; + } +#endif + if (ni->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL)) { + // Don't consume outside clicks for autocomplete widget + if (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::autocomplete, eCaseMatters)) + return false; + } + } + + return true; +} + +// XXXroc this is megalame. Fossicking around for a frame of the right +// type is a recipe for disaster in the long term. +nsIScrollableFrame* nsMenuPopupFrame::GetScrollFrame(nsIFrame* aStart) +{ + if (!aStart) + return nullptr; + + // try start frame and siblings + nsIFrame* currFrame = aStart; + do { + nsIScrollableFrame* sf = do_QueryFrame(currFrame); + if (sf) + return sf; + currFrame = currFrame->GetNextSibling(); + } while (currFrame); + + // try children + currFrame = aStart; + do { + nsIFrame* childFrame = currFrame->GetFirstPrincipalChild(); + nsIScrollableFrame* sf = GetScrollFrame(childFrame); + if (sf) + return sf; + currFrame = currFrame->GetNextSibling(); + } while (currFrame); + + return nullptr; +} + +void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem) +{ + if (aMenuItem) { + aMenuItem->PresContext()->PresShell()->ScrollFrameRectIntoView( + aMenuItem, + nsRect(nsPoint(0,0), aMenuItem->GetRect().Size()), + nsIPresShell::ScrollAxis(), + nsIPresShell::ScrollAxis(), + nsIPresShell::SCROLL_OVERFLOW_HIDDEN | + nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY); + } +} + +NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) +{ + if (mCurrentMenu == aMenuItem) + return NS_OK; + + if (mCurrentMenu) { + mCurrentMenu->SelectMenu(false); + } + + if (aMenuItem) { + EnsureMenuItemIsVisible(aMenuItem); + aMenuItem->SelectMenu(true); + } + + mCurrentMenu = aMenuItem; + + return NS_OK; +} + +void +nsMenuPopupFrame::CurrentMenuIsBeingDestroyed() +{ + mCurrentMenu = nullptr; +} + +NS_IMETHODIMP +nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, + bool aSelectFirstItem) +{ + if (mCurrentMenu == aMenuItem) + return NS_OK; + + // When a context menu is open, the current menu is locked, and no change + // to the menu is allowed. + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!mIsContextMenu && pm && pm->HasContextMenu(this)) + return NS_OK; + + // Unset the current child. + if (mCurrentMenu) { + mCurrentMenu->SelectMenu(false); + nsMenuPopupFrame* popup = mCurrentMenu->GetPopup(); + if (popup) { + if (mCurrentMenu->IsOpen()) { + if (pm) + pm->HidePopupAfterDelay(popup); + } + } + } + + // Set the new child. + if (aMenuItem) { + EnsureMenuItemIsVisible(aMenuItem); + aMenuItem->SelectMenu(true); + } + + mCurrentMenu = aMenuItem; + + return NS_OK; +} + +nsMenuFrame* +nsMenuPopupFrame::Enter(nsGUIEvent* aEvent) +{ + mIncrementalString.Truncate(); + + // Give it to the child. + if (mCurrentMenu) + return mCurrentMenu->Enter(aEvent); + + return nullptr; +} + +nsMenuFrame* +nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction) +{ + uint32_t charCode, keyCode; + aKeyEvent->GetCharCode(&charCode); + aKeyEvent->GetKeyCode(&keyCode); + + doAction = false; + + // Enumerate over our list of frames. + nsIFrame* immediateParent = nullptr; + PresContext()->PresShell()-> + FrameConstructor()->GetInsertionPoint(this, nullptr, &immediateParent); + if (!immediateParent) + immediateParent = this; + + uint32_t matchCount = 0, matchShortcutCount = 0; + bool foundActive = false; + bool isShortcut; + nsMenuFrame* frameBefore = nullptr; + nsMenuFrame* frameAfter = nullptr; + nsMenuFrame* frameShortcut = nullptr; + + nsIContent* parentContent = mContent->GetParent(); + + bool isMenu = parentContent && + !parentContent->NodeInfo()->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL); + + static DOMTimeStamp lastKeyTime = 0; + DOMTimeStamp keyTime; + aKeyEvent->GetTimeStamp(&keyTime); + + if (charCode == 0) { + if (keyCode == NS_VK_BACK) { + if (!isMenu && !mIncrementalString.IsEmpty()) { + mIncrementalString.SetLength(mIncrementalString.Length() - 1); + return nullptr; + } + else { +#ifdef XP_WIN + nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1"); + if (soundInterface) + soundInterface->Beep(); +#endif // #ifdef XP_WIN + } + } + return nullptr; + } + else { + PRUnichar uniChar = ToLowerCase(static_cast<PRUnichar>(charCode)); + if (isMenu || // Menu supports only first-letter navigation + keyTime - lastKeyTime > INC_TYP_INTERVAL) // Interval too long, treat as new typing + mIncrementalString = uniChar; + else { + mIncrementalString.Append(uniChar); + } + } + + // See bug 188199 & 192346, if all letters in incremental string are same, just try to match the first one + nsAutoString incrementalString(mIncrementalString); + uint32_t charIndex = 1, stringLength = incrementalString.Length(); + while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) { + charIndex++; + } + if (charIndex == stringLength) { + incrementalString.Truncate(1); + stringLength = 1; + } + + lastKeyTime = keyTime; + + nsIFrame* currFrame; + // NOTE: If you crashed here due to a bogus |immediateParent| it is + // possible that the menu whose shortcut is being looked up has + // been destroyed already. One strategy would be to + // setTimeout(<func>,0) as detailed in: + // <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32> + currFrame = immediateParent->GetFirstPrincipalChild(); + + int32_t menuAccessKey = -1; + nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); + + // We start searching from first child. This process is divided into two parts + // -- before current and after current -- by the current item + while (currFrame) { + nsIContent* current = currFrame->GetContent(); + + // See if it's a menu item. + if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, true)) { + nsAutoString textKey; + if (menuAccessKey >= 0) { + // Get the shortcut attribute. + current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, textKey); + } + if (textKey.IsEmpty()) { // No shortcut, try first letter + isShortcut = false; + current->GetAttr(kNameSpaceID_None, nsGkAtoms::label, textKey); + if (textKey.IsEmpty()) // No label, try another attribute (value) + current->GetAttr(kNameSpaceID_None, nsGkAtoms::value, textKey); + } + else + isShortcut = true; + + if (StringBeginsWith(textKey, incrementalString, + nsCaseInsensitiveStringComparator())) { + // mIncrementalString is a prefix of textKey + nsMenuFrame* menu = do_QueryFrame(currFrame); + if (menu) { + // There is one match + matchCount++; + if (isShortcut) { + // There is one shortcut-key match + matchShortcutCount++; + // Record the matched item. If there is only one matched shortcut item, do it + frameShortcut = menu; + } + if (!foundActive) { + // It's a first candidate item located before/on the current item + if (!frameBefore) + frameBefore = menu; + } + else { + // It's a first candidate item located after the current item + if (!frameAfter) + frameAfter = menu; + } + } + else + return nullptr; + } + + // Get the active status + if (current->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, + nsGkAtoms::_true, eCaseMatters)) { + foundActive = true; + if (stringLength > 1) { + // If there is more than one char typed, the current item has highest priority, + // otherwise the item next to current has highest priority + if (currFrame == frameBefore) + return frameBefore; + } + } + } + currFrame = currFrame->GetNextSibling(); + } + + doAction = (isMenu && (matchCount == 1 || matchShortcutCount == 1)); + + if (matchShortcutCount == 1) // We have one matched shortcut item + return frameShortcut; + if (frameAfter) // If we have matched item after the current, use it + return frameAfter; + else if (frameBefore) // If we haven't, use the item before the current + return frameBefore; + + // If we don't match anything, rollback the last typing + mIncrementalString.SetLength(mIncrementalString.Length() - 1); + + // didn't find a matching menu item +#ifdef XP_WIN + // behavior on Windows - this item is in a menu popup off of the + // menu bar, so beep and do nothing else + if (isMenu) { + nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1"); + if (soundInterface) + soundInterface->Beep(); + } +#endif // #ifdef XP_WIN + + return nullptr; +} + +void +nsMenuPopupFrame::LockMenuUntilClosed(bool aLock) +{ + mIsMenuLocked = aLock; + + // Lock / unlock the parent, too. + nsMenuFrame* menu = do_QueryFrame(GetParent()); + if (menu) { + nsMenuParent* parentParent = menu->GetMenuParent(); + if (parentParent) { + parentParent->LockMenuUntilClosed(aLock); + } + } +} + +nsIWidget* +nsMenuPopupFrame::GetWidget() +{ + nsView * view = GetRootViewForPopup(this); + if (!view) + return nullptr; + + return view->GetWidget(); +} + +void +nsMenuPopupFrame::AttachedDismissalListener() +{ + mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT; +} + +void +nsMenuPopupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // don't pass events to drag popups + if (aBuilder->IsForEventDelivery() && mIsDragPopup) { + return; + } + + nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); +} + +// helpers ///////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) + +{ + nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + + if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top) + MoveToAttributePosition(); + + if (aAttribute == nsGkAtoms::label) { + // set the label for the titlebar + nsView* view = GetView(); + if (view) { + nsIWidget* widget = view->GetWidget(); + if (widget) { + nsAutoString title; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title); + if (!title.IsEmpty()) { + widget->SetTitle(title); + } + } + } + } + + return rv; +} + +void +nsMenuPopupFrame::MoveToAttributePosition() +{ + // Move the widget around when the user sets the |left| and |top| attributes. + // Note that this is not the best way to move the widget, as it results in lots + // of FE notifications and is likely to be slow as molasses. Use |moveTo| on + // nsIPopupBoxObject if possible. + nsAutoString left, top; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left); + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top); + nsresult err1, err2; + int32_t xpos = left.ToInteger(&err1); + int32_t ypos = top.ToInteger(&err2); + + if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2)) + MoveTo(xpos, ypos, false); +} + +void +nsMenuPopupFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + nsMenuFrame* menu = do_QueryFrame(GetParent()); + if (menu) { + // clear the open attribute on the parent menu + nsContentUtils::AddScriptRunner( + new nsUnsetAttrRunnable(menu->GetContent(), nsGkAtoms::open)); + } + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + pm->PopupDestroyed(this); + + nsIRootBox* rootBox = + nsIRootBox::GetRootBox(PresContext()->GetPresShell()); + if (rootBox && rootBox->GetDefaultTooltip() == mContent) { + rootBox->SetDefaultTooltip(nullptr); + } + + nsBoxFrame::DestroyFrom(aDestructRoot); +} + + +void +nsMenuPopupFrame::MoveTo(int32_t aLeft, int32_t aTop, bool aUpdateAttrs) +{ + nsIWidget* widget = GetWidget(); + if ((mScreenXPos == aLeft && mScreenYPos == aTop) && + (!widget || widget->GetClientOffset() == mLastClientOffset)) { + return; + } + + // reposition the popup at the specified coordinates. Don't clear the anchor + // and position, because the popup can be reset to its anchor position by + // using (-1, -1) as coordinates. Subtract off the margin as it will be + // added to the position when SetPopupPosition is called. + nsMargin margin(0, 0, 0, 0); + StyleMargin()->GetMargin(margin); + + // Workaround for bug 788189. See also bug 708278 comment #25 and following. + if (mAdjustOffsetForContextMenu) { + nscoord offsetForContextMenu = + nsPresContext::CSSPixelsToAppUnits(CONTEXT_MENU_OFFSET_PIXELS); + margin.left += offsetForContextMenu; + margin.top += offsetForContextMenu; + } + + nsPresContext* presContext = PresContext(); + mScreenXPos = aLeft - presContext->AppUnitsToIntCSSPixels(margin.left); + mScreenYPos = aTop - presContext->AppUnitsToIntCSSPixels(margin.top); + + SetPopupPosition(nullptr, true); + + nsCOMPtr<nsIContent> popup = mContent; + if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) || + popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top))) + { + nsAutoString left, top; + left.AppendInt(aLeft); + top.AppendInt(aTop); + popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false); + popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false); + } +} + +void +nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent, + const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + bool aAttributesOverride) +{ + NS_ASSERTION(mPopupState == ePopupOpenAndVisible, "popup must be open to move it"); + + InitializePopup(aAnchorContent, mTriggerContent, aPosition, + aXPos, aYPos, aAttributesOverride); + // InitializePopup changed the state so reset it. + mPopupState = ePopupOpenAndVisible; + + // Pass false here so that flipping and adjusting to fit on the screen happen. + SetPopupPosition(nullptr, false); +} + +bool +nsMenuPopupFrame::GetAutoPosition() +{ + return mShouldAutoPosition; +} + +void +nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition) +{ + mShouldAutoPosition = aShouldAutoPosition; +} + +void +nsMenuPopupFrame::SetConsumeRollupEvent(uint32_t aConsumeMode) +{ + mConsumeRollupEvent = aConsumeMode; +} + +int8_t +nsMenuPopupFrame::GetAlignmentPosition() const +{ + // The code below handles most cases of alignment, anchor and position values. Those that are + // not handled just return POPUPPOSITION_UNKNOWN. + + if (mPosition == POPUPPOSITION_OVERLAP || mPosition == POPUPPOSITION_AFTERPOINTER) + return mPosition; + + int8_t position = mPosition; + + if (position == POPUPPOSITION_UNKNOWN) { + switch (mPopupAnchor) { + case POPUPALIGNMENT_BOTTOMCENTER: + position = mPopupAlignment == POPUPALIGNMENT_TOPRIGHT ? + POPUPPOSITION_AFTEREND : POPUPPOSITION_AFTERSTART; + break; + case POPUPALIGNMENT_TOPCENTER: + position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ? + POPUPPOSITION_BEFOREEND : POPUPPOSITION_BEFORESTART; + break; + case POPUPALIGNMENT_LEFTCENTER: + position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ? + POPUPPOSITION_STARTAFTER : POPUPPOSITION_STARTBEFORE; + break; + case POPUPALIGNMENT_RIGHTCENTER: + position = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ? + POPUPPOSITION_ENDAFTER : POPUPPOSITION_ENDBEFORE; + break; + default: + break; + } + } + + if (mHFlip) { + position = POPUPPOSITION_HFLIP(position); + } + + if (mVFlip) { + position = POPUPPOSITION_VFLIP(position); + } + + return position; +} + +/** + * KEEP THIS IN SYNC WITH nsContainerFrame::CreateViewForFrame + * as much as possible. Until we get rid of views finally... + */ +void +nsMenuPopupFrame::CreatePopupView() +{ + if (HasView()) { + return; + } + + nsViewManager* viewManager = PresContext()->GetPresShell()->GetViewManager(); + NS_ASSERTION(nullptr != viewManager, "null view manager"); + + // Create a view + nsView* parentView = viewManager->GetRootView(); + nsViewVisibility visibility = nsViewVisibility_kHide; + int32_t zIndex = INT32_MAX; + bool autoZIndex = false; + + NS_ASSERTION(parentView, "no parent view"); + + // Create a view + nsView *view = viewManager->CreateView(GetRect(), parentView, visibility); + viewManager->SetViewZIndex(view, autoZIndex, zIndex); + // XXX put view last in document order until we can do better + viewManager->InsertChild(parentView, view, nullptr, true); + + // Remember our view + SetView(view); + + NS_FRAME_LOG(NS_FRAME_TRACE_CALLS, + ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view)); +} diff --git a/layout/xul/base/src/nsMenuPopupFrame.h b/layout/xul/base/src/nsMenuPopupFrame.h new file mode 100644 index 000000000..22e49d8b3 --- /dev/null +++ b/layout/xul/base/src/nsMenuPopupFrame.h @@ -0,0 +1,484 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// nsMenuPopupFrame +// + +#ifndef nsMenuPopupFrame_h__ +#define nsMenuPopupFrame_h__ + +#include "mozilla/Attributes.h" +#include "nsIAtom.h" +#include "nsGkAtoms.h" +#include "nsCOMPtr.h" +#include "nsMenuFrame.h" + +#include "nsBoxFrame.h" +#include "nsMenuParent.h" + +#include "nsITimer.h" + +class nsIWidget; + +// XUL popups can be in several different states. When opening a popup, the +// state changes as follows: +// ePopupClosed - initial state +// ePopupShowing - during the period when the popupshowing event fires +// ePopupOpen - between the popupshowing event and being visible. Creation +// of the child frames, layout and reflow occurs in this state. +// ePopupOpenAndVisible - layout is done and the popup's view and widget are +// made visible. The popupshown event fires. +// When closing a popup: +// ePopupHidden - during the period when the popuphiding event fires and +// the popup is removed. +// ePopupClosed - the popup's widget is made invisible. +enum nsPopupState { + // state when a popup is not open + ePopupClosed, + // state from when a popup is requested to be shown to after the + // popupshowing event has been fired. + ePopupShowing, + // state while a popup is open but the widget is not yet visible + ePopupOpen, + // state while a popup is open and visible on screen + ePopupOpenAndVisible, + // state from when a popup is requested to be hidden to when it is closed. + ePopupHiding, + // state which indicates that the popup was hidden without firing the + // popuphiding or popuphidden events. It is used when executing a menu + // command because the menu needs to be hidden before the command event + // fires, yet the popuphiding and popuphidden events are fired after. This + // state can also occur when the popup is removed because the document is + // unloaded. + ePopupInvisible +}; + +// How a popup may be flipped. Flipping to the outside edge is like how +// a submenu would work. The entire popup is flipped to the opposite side +// of the anchor. +enum FlipStyle { + FlipStyle_None = 0, + FlipStyle_Outside = 1, + FlipStyle_Inside = 2 +}; + +// values are selected so that the direction can be flipped just by +// changing the sign +#define POPUPALIGNMENT_NONE 0 +#define POPUPALIGNMENT_TOPLEFT 1 +#define POPUPALIGNMENT_TOPRIGHT -1 +#define POPUPALIGNMENT_BOTTOMLEFT 2 +#define POPUPALIGNMENT_BOTTOMRIGHT -2 + +#define POPUPALIGNMENT_LEFTCENTER 16 +#define POPUPALIGNMENT_RIGHTCENTER -16 +#define POPUPALIGNMENT_TOPCENTER 17 +#define POPUPALIGNMENT_BOTTOMCENTER 18 + +// The constants here are selected so that horizontally and vertically flipping +// can be easily handled using the two flip macros below. +#define POPUPPOSITION_UNKNOWN -1 +#define POPUPPOSITION_BEFORESTART 0 +#define POPUPPOSITION_BEFOREEND 1 +#define POPUPPOSITION_AFTERSTART 2 +#define POPUPPOSITION_AFTEREND 3 +#define POPUPPOSITION_STARTBEFORE 4 +#define POPUPPOSITION_ENDBEFORE 5 +#define POPUPPOSITION_STARTAFTER 6 +#define POPUPPOSITION_ENDAFTER 7 +#define POPUPPOSITION_OVERLAP 8 +#define POPUPPOSITION_AFTERPOINTER 9 + +#define POPUPPOSITION_HFLIP(v) (v ^ 1) +#define POPUPPOSITION_VFLIP(v) (v ^ 2) + +#define INC_TYP_INTERVAL 1000 // 1s. If the interval between two keypresses is shorter than this, + // treat as a continue typing +// XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose: +// nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml +// need to find a good place to put them together. +// if someone changes one, please also change the other. + +#define CONTEXT_MENU_OFFSET_PIXELS 2 + +nsIFrame* NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsViewManager; +class nsView; +class nsMenuPopupFrame; + +class nsMenuPopupFrame : public nsBoxFrame, public nsMenuParent +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsMenuPopupFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext); + + // nsMenuParent interface + virtual nsMenuFrame* GetCurrentMenuItem() MOZ_OVERRIDE; + NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) MOZ_OVERRIDE; + virtual void CurrentMenuIsBeingDestroyed() MOZ_OVERRIDE; + NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, bool aSelectFirstItem) MOZ_OVERRIDE; + + // as popups are opened asynchronously, the popup pending state is used to + // prevent multiple requests from attempting to open the same popup twice + nsPopupState PopupState() { return mPopupState; } + void SetPopupState(nsPopupState aPopupState) { mPopupState = aPopupState; } + + NS_IMETHOD SetActive(bool aActiveFlag) MOZ_OVERRIDE { return NS_OK; } // We don't care. + virtual bool IsActive() MOZ_OVERRIDE { return false; } + virtual bool IsMenuBar() MOZ_OVERRIDE { return false; } + + /* + * When this popup is open, should clicks outside of it be consumed? + * Return true if the popup should rollup on an outside click, + * but consume that click so it can't be used for anything else. + * Return false to allow clicks outside the popup to activate content + * even when the popup is open. + * --------------------------------------------------------------------- + * + * Should clicks outside of a popup be eaten? + * + * Menus Autocomplete Comboboxes + * Mac Eat No Eat + * Win No No Eat + * Unix Eat No Eat + * + */ + bool ConsumeOutsideClicks(); + + virtual bool IsContextMenu() MOZ_OVERRIDE { return mIsContextMenu; } + + virtual bool MenuClosed() MOZ_OVERRIDE { return true; } + + virtual void LockMenuUntilClosed(bool aLock) MOZ_OVERRIDE; + virtual bool IsMenuLocked() MOZ_OVERRIDE { return mIsMenuLocked; } + + nsIWidget* GetWidget(); + + // The dismissal listener gets created and attached to the window. + void AttachedDismissalListener(); + + // Overridden methods + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + // returns true if the popup is a panel with the noautohide attribute set to + // true. These panels do not roll up automatically. + bool IsNoAutoHide() const; + + nsPopupLevel PopupLevel() const + { + return PopupLevel(IsNoAutoHide()); + } + + void EnsureWidget(); + + nsresult CreateWidgetForView(nsView* aView); + uint8_t GetShadowStyle(); + + NS_IMETHOD SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) MOZ_OVERRIDE; + + virtual bool IsLeaf() const MOZ_OVERRIDE; + + // layout, position and display the popup as needed + void LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool aSizedToPopup); + + nsView* GetRootViewForPopup(nsIFrame* aStartFrame); + + // set the position of the popup either relative to the anchor aAnchorFrame + // (or the frame for mAnchorContent if aAnchorFrame is null) or at a specific + // point if a screen position (mScreenXPos and mScreenYPos) are set. The popup + // will be adjusted so that it is on screen. If aIsMove is true, then the popup + // is being moved, and should not be flipped. + nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove); + + bool HasGeneratedChildren() { return mGeneratedChildren; } + void SetGeneratedChildren() { mGeneratedChildren = true; } + + // called when the Enter key is pressed while the popup is open. This will + // just pass the call down to the current menu, if any. If a current menu + // should be opened as a result, this method should return the frame for + // that menu, or null if no menu should be opened. Also, calling Enter will + // reset the current incremental search string, calculated in + // FindMenuWithShortcut. + nsMenuFrame* Enter(nsGUIEvent* aEvent); + + nsPopupType PopupType() const { return mPopupType; } + bool IsMenu() MOZ_OVERRIDE { return mPopupType == ePopupTypeMenu; } + bool IsOpen() MOZ_OVERRIDE { return mPopupState == ePopupOpen || mPopupState == ePopupOpenAndVisible; } + + bool IsDragPopup() { return mIsDragPopup; } + + static nsIContent* GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame); + void ClearTriggerContent() { mTriggerContent = nullptr; } + + // returns true if the popup is in a content shell, or false for a popup in + // a chrome shell + bool 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, + int32_t aXPos, int32_t aYPos, + bool 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(nsIContent* aTriggerContent, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu); + + void InitializePopupWithAnchorAlign(nsIContent* aAnchorContent, + nsAString& aAnchor, + nsAString& aAlign, + int32_t aXPos, int32_t aYPos); + + // indicate that the popup should be opened + void ShowPopup(bool aIsContextMenu, bool aSelectFirstItem); + // indicate that the popup should be hidden. The new state should either be + // ePopupClosed or ePopupInvisible. + void HidePopup(bool aDeselectMenu, nsPopupState aNewState); + + // locate and return the menu frame that should be activated for the + // supplied key event. If doAction is set to true by this method, + // then the menu's action should be carried out, as if the user had pressed + // the Enter key. If doAction is false, the menu should just be highlighted. + // This method also handles incremental searching in menus so the user can + // type the first few letters of an item/s name to select it. + nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction); + + void ClearIncrementalString() { mIncrementalString.Truncate(); } + + virtual nsIAtom* GetType() const MOZ_OVERRIDE { return nsGkAtoms::menuPopupFrame; } + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE + { + return MakeFrameName(NS_LITERAL_STRING("MenuPopup"), aResult); + } +#endif + + void EnsureMenuItemIsVisible(nsMenuFrame* aMenuFrame); + + // Move the popup to the screen coordinate (aLeft, aTop) in CSS pixels. + // If aUpdateAttrs is true, and the popup already has left or top attributes, + // then those attributes are updated to the new location. + // The frame may be destroyed by this method. + void MoveTo(int32_t aLeft, int32_t aTop, bool aUpdateAttrs); + + void MoveToAnchor(nsIContent* aAnchorContent, + const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + bool aAttributesOverride); + + bool GetAutoPosition(); + void SetAutoPosition(bool aShouldAutoPosition); + void SetConsumeRollupEvent(uint32_t aConsumeMode); + + nsIScrollableFrame* GetScrollFrame(nsIFrame* aStart); + + // For a popup that should appear anchored at the given rect, determine + // the screen area that it is constrained by. This will be the available + // area of the screen the popup should be displayed on. Content popups, + // however, will also be constrained by the content area, given by + // aRootScreenRect. All coordinates are in app units. + nsRect GetConstraintRect(const nsRect& aAnchorRect, const nsRect& aRootScreenRect); + + // Determines whether the given edges of the popup may be moved, where + // aHorizontalSide and aVerticalSide are one of the NS_SIDE_* constants, or + // 0 for no movement in that direction. aChange is the distance to move on + // those sides. If will be reset to 0 if the side cannot be adjusted at all + // in that direction. For example, a popup cannot be moved if it is anchored + // on a particular side. + // + // Later, when bug 357725 is implemented, we can make this adjust aChange by + // the amount that the side can be resized, so that minimums and maximums + // can be taken into account. + void CanAdjustEdges(int8_t aHorizontalSide, int8_t aVerticalSide, nsIntPoint& aChange); + + // Return true if the popup is positioned relative to an anchor. + bool IsAnchored() const { return mScreenXPos == -1 && mScreenYPos == -1; } + + // Return the anchor if there is one. + nsIContent* GetAnchor() const { return mAnchorContent; } + + // Return the screen coordinates of the popup, or (-1, -1) if anchored. + // This position is in CSS pixels. + nsIntPoint ScreenPosition() const { return nsIntPoint(mScreenXPos, mScreenYPos); } + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + nsIntPoint GetLastClientOffset() const { return mLastClientOffset; } + + // Return the alignment of the popup + int8_t GetAlignmentPosition() const; + + // Return the offset applied to the alignment of the popup + nscoord GetAlignmentOffset() const { return mAlignmentOffset; } +protected: + + // returns the popup's level. + nsPopupLevel PopupLevel(bool aIsNoAutoHide) const; + + // redefine to tell the box system not to move the views. + virtual void GetLayoutFlags(uint32_t& aFlags) MOZ_OVERRIDE; + + void InitPositionFromAnchorAlign(const nsAString& aAnchor, + const nsAString& aAlign); + + // return the position where the popup should be, when it should be + // anchored at anchorRect. aHFlip and aVFlip will be set if the popup may be + // flipped in that direction if there is not enough space available. + nsPoint AdjustPositionForAnchorAlign(nsRect& anchorRect, + FlipStyle& aHFlip, FlipStyle& aVFlip); + + // check if the popup will fit into the available space and resize it. This + // method handles only one axis at a time so is called twice, once for + // horizontal and once for vertical. All arguments are specified for this + // one axis. All coordinates are in app units relative to the screen. + // aScreenPoint - the point where the popup should appear + // aSize - the size of the popup + // aScreenBegin - the left or top edge of the screen + // aScreenEnd - the right or bottom edge of the screen + // aAnchorBegin - the left or top edge of the anchor rectangle + // aAnchorEnd - the right or bottom edge of the anchor rectangle + // aMarginBegin - the left or top margin of the popup + // aMarginEnd - the right or bottom margin of the popup + // aOffsetForContextMenu - the additional offset to add for context menus + // aFlip - how to flip or resize the popup when there isn't space + // aFlipSide - pointer to where current flip mode is stored + nscoord FlipOrResize(nscoord& aScreenPoint, nscoord aSize, + nscoord aScreenBegin, nscoord aScreenEnd, + nscoord aAnchorBegin, nscoord aAnchorEnd, + nscoord aMarginBegin, nscoord aMarginEnd, + nscoord aOffsetForContextMenu, FlipStyle aFlip, + bool* aFlipSide); + + // check if the popup can fit into the available space by "sliding" (i.e., + // by having the anchor arrow slide along one axis and only resizing if that + // can't provide the requested size). Only one axis can be slid - the other + // axis is "flipped" as normal. This method can handle either axis, but is + // only called for the sliding axis. All coordinates are in app units + // relative to the screen. + // aScreenPoint - the point where the popup should appear + // aSize - the size of the popup + // aScreenBegin - the left or top edge of the screen + // aScreenEnd - the right or bottom edge of the screen + // aOffset - the amount by which the arrow must be slid such that it is + // still aligned with the anchor. + // Result is the new size of the popup, which will typically be the same + // as aSize, unless aSize is greater than the screen width/height. + nscoord SlideOrResize(nscoord& aScreenPoint, nscoord aSize, + nscoord aScreenBegin, nscoord aScreenEnd, + nscoord *aOffset); + + // Move the popup to the position specified in its |left| and |top| attributes. + void MoveToAttributePosition(); + + /** + * Return whether the popup direction should be RTL. + * If the popup has an anchor, its direction is the anchor direction. + * Otherwise, its the general direction of the UI. + * + * Return whether the popup direction should be RTL. + */ + bool IsDirectionRTL() const { + return mAnchorContent && mAnchorContent->GetPrimaryFrame() + ? mAnchorContent->GetPrimaryFrame()->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL + : StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + } + + // Create a popup view for this frame. The view is added a child of the root + // view, and is initially hidden. + void CreatePopupView(); + + 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; + + // The position of the popup, in CSS pixels. + // The screen coordinates, if set to values other than -1, + // override mXPos and mYPos. + int32_t mXPos; + int32_t mYPos; + int32_t mScreenXPos; + int32_t mScreenYPos; + + // If the panel prefers to "slide" rather than resize, then the arrow gets + // positioned at this offset (along either the x or y axis, depending on + // mPosition) + nscoord mAlignmentOffset; + + // The value of the client offset of our widget the last time we positioned + // ourselves. We store this so that we can detect when it changes but the + // position of our widget didn't change. + nsIntPoint mLastClientOffset; + + nsPopupType mPopupType; // type of popup + nsPopupState mPopupState; // open state of the popup + + // popup alignment relative to the anchor node + int8_t mPopupAlignment; + int8_t mPopupAnchor; + int8_t mPosition; + + // One of nsIPopupBoxObject::ROLLUP_DEFAULT/ROLLUP_CONSUME/ROLLUP_NO_CONSUME + int8_t mConsumeRollupEvent; + bool mFlipBoth; // flip in both directions + bool mSlide; // allow the arrow to "slide" instead of resizing + + bool mIsOpenChanged; // true if the open state changed since the last layout + bool mIsContextMenu; // true for context menus + // true if we need to offset the popup to ensure it's not under the mouse + bool mAdjustOffsetForContextMenu; + bool mGeneratedChildren; // true if the contents have been created + + bool mMenuCanOverlapOSBar; // can we appear over the taskbar/menubar? + bool mShouldAutoPosition; // Should SetPopupPosition be allowed to auto position popup? + bool mInContentShell; // True if the popup is in a content shell + bool mIsMenuLocked; // Should events inside this menu be ignored? + bool mIsDragPopup; // True if this is a popup used for drag feedback + + // the flip modes that were used when the popup was opened + bool mHFlip; + bool mVFlip; + + static int8_t sDefaultLevelIsTop; +}; // class nsMenuPopupFrame + +#endif diff --git a/layout/xul/base/src/nsPIListBoxObject.h b/layout/xul/base/src/nsPIListBoxObject.h new file mode 100644 index 000000000..c7b9928f0 --- /dev/null +++ b/layout/xul/base/src/nsPIListBoxObject.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPIListBoxObject_h__ +#define nsPIListBoxObject_h__ + +class nsListBoxBodyFrame; + +// fa9549f7-ee09-48fc-89f7-30cceee21c15 +#define NS_PILISTBOXOBJECT_IID \ +{ 0xfa9549f7, 0xee09, 0x48fc, \ + { 0x89, 0xf7, 0x30, 0xcc, 0xee, 0xe2, 0x1c, 0x15 } } + +#include "nsIListBoxObject.h" + +class nsPIListBoxObject : public nsIListBoxObject { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_PILISTBOXOBJECT_IID) + /** + * Get the list box body. This will search for it as needed. + * If aFlush is false we don't Flush_Frames though. + */ + virtual nsListBoxBodyFrame* GetListBoxBody(bool aFlush) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsPIListBoxObject, NS_PILISTBOXOBJECT_IID) + +#endif // nsPIListBoxObject_h__ diff --git a/layout/xul/base/src/nsPopupBoxObject.cpp b/layout/xul/base/src/nsPopupBoxObject.cpp new file mode 100644 index 000000000..93233752d --- /dev/null +++ b/layout/xul/base/src/nsPopupBoxObject.cpp @@ -0,0 +1,386 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsIPopupBoxObject.h" +#include "nsIRootBox.h" +#include "nsBoxObject.h" +#include "nsIPresShell.h" +#include "nsFrameManager.h" +#include "nsIContent.h" +#include "nsIDOMElement.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsMenuPopupFrame.h" +#include "nsClientRect.h" +#include "nsView.h" + +class nsPopupBoxObject : public nsBoxObject, + public nsIPopupBoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPOPUPBOXOBJECT + + nsPopupBoxObject() {} +protected: + virtual ~nsPopupBoxObject() {} + + nsPopupSetFrame* GetPopupSetFrame(); +}; + +NS_IMPL_ISUPPORTS_INHERITED1(nsPopupBoxObject, nsBoxObject, nsIPopupBoxObject) + +nsPopupSetFrame* +nsPopupBoxObject::GetPopupSetFrame() +{ + nsIRootBox* rootBox = nsIRootBox::GetRootBox(GetPresShell(false)); + if (!rootBox) + return nullptr; + + return rootBox->GetPopupSetFrame(); +} + +NS_IMETHODIMP +nsPopupBoxObject::HidePopup() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && mContent) + pm->HidePopup(mContent, false, true, false); + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::ShowPopup(nsIDOMElement* aAnchorElement, + nsIDOMElement* aPopupElement, + int32_t aXPos, int32_t aYPos, + const PRUnichar *aPopupType, + const PRUnichar *aAnchorAlignment, + const PRUnichar *aPopupAlignment) +{ + NS_ENSURE_TRUE(aPopupElement, NS_ERROR_INVALID_ARG); + // srcContent can be null. + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && mContent) { + nsCOMPtr<nsIContent> anchorContent(do_QueryInterface(aAnchorElement)); + nsAutoString popupType(aPopupType); + nsAutoString anchor(aAnchorAlignment); + nsAutoString align(aPopupAlignment); + pm->ShowPopupWithAnchorAlign(mContent, anchorContent, anchor, align, + aXPos, aYPos, popupType.EqualsLiteral("context")); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::OpenPopup(nsIDOMElement* aAnchorElement, + const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + bool 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, false, aTriggerEvent); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::OpenPopupAtScreen(int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + nsIDOMEvent* aTriggerEvent) +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && mContent) + pm->ShowPopupAtScreen(mContent, aXPos, aYPos, aIsContextMenu, aTriggerEvent); + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::MoveTo(int32_t aLeft, int32_t aTop) +{ + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (menuPopupFrame) { + menuPopupFrame->MoveTo(aLeft, aTop, true); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::MoveToAnchor(nsIDOMElement* aAnchorElement, + const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + bool aAttributesOverride) +{ + if (mContent) { + nsCOMPtr<nsIContent> anchorContent(do_QueryInterface(aAnchorElement)); + + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (menuPopupFrame && menuPopupFrame->PopupState() == ePopupOpenAndVisible) { + menuPopupFrame->MoveToAnchor(anchorContent, aPosition, aXPos, aYPos, aAttributesOverride); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::SizeTo(int32_t aWidth, int32_t aHeight) +{ + if (!mContent) + return NS_OK; + + nsAutoString width, height; + width.AppendInt(aWidth); + height.AppendInt(aHeight); + + nsCOMPtr<nsIContent> content = mContent; + + // We only want to pass aNotify=true to SetAttr once, but must make sure + // we pass it when a value is being changed. Thus, we check if the height + // is the same and if so, pass true when setting the width. + bool heightSame = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::height, height, eCaseMatters); + + content->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, heightSame); + content->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true); + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::GetAutoPosition(bool* aShouldAutoPosition) +{ + *aShouldAutoPosition = true; + + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (menuPopupFrame) { + *aShouldAutoPosition = menuPopupFrame->GetAutoPosition(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::SetAutoPosition(bool aShouldAutoPosition) +{ + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (menuPopupFrame) { + menuPopupFrame->SetAutoPosition(aShouldAutoPosition); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::EnableRollup(bool aShouldRollup) +{ + // this does nothing now + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::SetConsumeRollupEvent(uint32_t aConsume) +{ + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false)); + if (menuPopupFrame) { + menuPopupFrame->SetConsumeRollupEvent(aConsume); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::EnableKeyboardNavigator(bool aEnableKeyboardNavigator) +{ + if (!mContent) + return NS_OK; + + // Use ignorekeys="true" on the popup instead of using this function. + if (aEnableKeyboardNavigator) + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, true); + else + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, + NS_LITERAL_STRING("true"), true); + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::GetPopupState(nsAString& aState) +{ + // set this here in case there's no frame for the popup + aState.AssignLiteral("closed"); + + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (menuPopupFrame) { + switch (menuPopupFrame->PopupState()) { + case ePopupShowing: + case ePopupOpen: + aState.AssignLiteral("showing"); + break; + case ePopupOpenAndVisible: + aState.AssignLiteral("open"); + break; + case ePopupHiding: + case ePopupInvisible: + aState.AssignLiteral("hiding"); + break; + case ePopupClosed: + break; + default: + NS_NOTREACHED("Bad popup state"); + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::GetTriggerNode(nsIDOMNode** aTriggerNode) +{ + *aTriggerNode = nullptr; + + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + nsIContent* triggerContent = nsMenuPopupFrame::GetTriggerContent(menuPopupFrame); + if (triggerContent) + CallQueryInterface(triggerContent, aTriggerNode); + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::GetAnchorNode(nsIDOMElement** aAnchor) +{ + *aAnchor = nullptr; + + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (!menuPopupFrame) + return NS_OK; + + nsIContent* anchor = menuPopupFrame->GetAnchor(); + if (anchor) + CallQueryInterface(anchor, aAnchor); + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::GetOuterScreenRect(nsIDOMClientRect** aRect) +{ + nsClientRect* rect = new nsClientRect(mContent); + + NS_ADDREF(*aRect = rect); + + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false)); + if (!menuPopupFrame) + return NS_OK; + + // Return an empty rectangle if the popup is not open. + nsPopupState state = menuPopupFrame->PopupState(); + if (state != ePopupOpen && state != ePopupOpenAndVisible) + return NS_OK; + + nsView* view = menuPopupFrame->GetView(); + if (view) { + nsIWidget* widget = view->GetWidget(); + if (widget) { + nsIntRect screenRect; + widget->GetScreenBounds(screenRect); + + int32_t pp = menuPopupFrame->PresContext()->AppUnitsPerDevPixel(); + rect->SetLayoutRect(screenRect.ToAppUnits(pp)); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::GetAlignmentPosition(nsAString& positionStr) +{ + positionStr.Truncate(); + + // This needs to flush layout. + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(true)); + if (!menuPopupFrame) + return NS_OK; + + int8_t position = menuPopupFrame->GetAlignmentPosition(); + switch (position) { + case POPUPPOSITION_AFTERSTART: + positionStr.AssignLiteral("after_start"); + break; + case POPUPPOSITION_AFTEREND: + positionStr.AssignLiteral("after_end"); + break; + case POPUPPOSITION_BEFORESTART: + positionStr.AssignLiteral("before_start"); + break; + case POPUPPOSITION_BEFOREEND: + positionStr.AssignLiteral("before_end"); + break; + case POPUPPOSITION_STARTBEFORE: + positionStr.AssignLiteral("start_before"); + break; + case POPUPPOSITION_ENDBEFORE: + positionStr.AssignLiteral("end_before"); + break; + case POPUPPOSITION_STARTAFTER: + positionStr.AssignLiteral("start_after"); + break; + case POPUPPOSITION_ENDAFTER: + positionStr.AssignLiteral("end_after"); + break; + case POPUPPOSITION_OVERLAP: + positionStr.AssignLiteral("overlap"); + break; + case POPUPPOSITION_AFTERPOINTER: + positionStr.AssignLiteral("after_pointer"); + break; + default: + // Leave as an empty string. + break; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupBoxObject::GetAlignmentOffset(int32_t *aAlignmentOffset) +{ + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(true)); + if (!menuPopupFrame) + return NS_OK; + + int32_t pp = nsDeviceContext::AppUnitsPerCSSPixel(); + // Note that the offset might be along either the X or Y axis, but for the + // sake of simplicity we use a point with only the X axis set so we can + // use ToNearestPixels(). + nsPoint appOffset(menuPopupFrame->GetAlignmentOffset(), 0); + nsIntPoint popupOffset = appOffset.ToNearestPixels(pp); + *aAlignmentOffset = popupOffset.x; + return NS_OK; +} + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewPopupBoxObject(nsIBoxObject** aResult) +{ + *aResult = new nsPopupBoxObject; + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/layout/xul/base/src/nsPopupSetFrame.cpp b/layout/xul/base/src/nsPopupSetFrame.cpp new file mode 100644 index 000000000..f20d1e6d5 --- /dev/null +++ b/layout/xul/base/src/nsPopupSetFrame.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPopupSetFrame.h" +#include "nsGkAtoms.h" +#include "nsCOMPtr.h" +#include "nsIContent.h" +#include "nsPresContext.h" +#include "nsStyleContext.h" +#include "nsBoxLayoutState.h" +#include "nsIScrollableFrame.h" +#include "nsIRootBox.h" +#include "nsMenuPopupFrame.h" + +nsIFrame* +NS_NewPopupSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsPopupSetFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsPopupSetFrame) + +void +nsPopupSetFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + // Normally the root box is our grandparent, but in case of wrapping + // it can be our great-grandparent. + nsIRootBox *rootBox = nsIRootBox::GetRootBox(PresContext()->GetPresShell()); + if (rootBox) { + rootBox->SetPopupSetFrame(this); + } +} + +nsIAtom* +nsPopupSetFrame::GetType() const +{ + return nsGkAtoms::popupSetFrame; +} + +NS_IMETHODIMP +nsPopupSetFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + if (aListID == kPopupList) { + AddPopupFrameList(aFrameList); + return NS_OK; + } + return nsBoxFrame::AppendFrames(aListID, aFrameList); +} + +NS_IMETHODIMP +nsPopupSetFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + if (aListID == kPopupList) { + RemovePopupFrame(aOldFrame); + return NS_OK; + } + return nsBoxFrame::RemoveFrame(aListID, aOldFrame); +} + +NS_IMETHODIMP +nsPopupSetFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + if (aListID == kPopupList) { + AddPopupFrameList(aFrameList); + return NS_OK; + } + return nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); +} + +NS_IMETHODIMP +nsPopupSetFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + if (aListID == kPopupList) { + // XXXmats this asserts because we don't implement + // GetChildList(kPopupList) so nsCSSFrameConstructor + // believes it's empty and calls us multiple times. + //NS_ASSERTION(mPopupList.IsEmpty(), + // "SetInitialChildList on non-empty child list"); + AddPopupFrameList(aChildList); + return NS_OK; + } + return nsBoxFrame::SetInitialChildList(aListID, aChildList); +} + +void +nsPopupSetFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + mPopupList.DestroyFramesFrom(aDestructRoot); + + // Normally the root box is our grandparent, but in case of wrapping + // it can be our great-grandparent. + nsIRootBox *rootBox = nsIRootBox::GetRootBox(PresContext()->GetPresShell()); + if (rootBox) { + rootBox->SetPopupSetFrame(nullptr); + } + + nsBoxFrame::DestroyFrom(aDestructRoot); +} + +NS_IMETHODIMP +nsPopupSetFrame::DoLayout(nsBoxLayoutState& aState) +{ + // lay us out + nsresult rv = nsBoxFrame::DoLayout(aState); + + // lay out all of our currently open popups. + for (nsFrameList::Enumerator e(mPopupList); !e.AtEnd(); e.Next()) { + nsMenuPopupFrame* popupChild = static_cast<nsMenuPopupFrame*>(e.get()); + popupChild->LayoutPopup(aState, nullptr, false); + } + + return rv; +} + +void +nsPopupSetFrame::RemovePopupFrame(nsIFrame* aPopup) +{ + NS_PRECONDITION((aPopup->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && + aPopup->GetType() == nsGkAtoms::menuPopupFrame, + "removing wrong type of frame in popupset's ::popupList"); + + mPopupList.DestroyFrame(aPopup); +} + +void +nsPopupSetFrame::AddPopupFrameList(nsFrameList& aPopupFrameList) +{ +#ifdef DEBUG + for (nsFrameList::Enumerator e(aPopupFrameList); !e.AtEnd(); e.Next()) { + NS_ASSERTION((e.get()->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && + e.get()->GetType() == nsGkAtoms::menuPopupFrame, + "adding wrong type of frame in popupset's ::popupList"); + } +#endif + mPopupList.InsertFrames(nullptr, nullptr, aPopupFrameList); +} + +#ifdef DEBUG +void +nsPopupSetFrame::List(FILE* out, int32_t aIndent, uint32_t aFlags) const +{ + ListGeneric(out, aIndent, aFlags); + + // Output the children + bool outputOneList = false; + ChildListIterator lists(this); + for (; !lists.IsDone(); lists.Next()) { + if (outputOneList) { + IndentBy(out, aIndent); + } + outputOneList = true; + fprintf(out, "%s<\n", mozilla::layout::ChildListName(lists.CurrentID())); + nsFrameList::Enumerator childFrames(lists.CurrentList()); + for (; !childFrames.AtEnd(); childFrames.Next()) { + nsIFrame* kid = childFrames.get(); + // Verify the child frame's parent frame pointer is correct + NS_ASSERTION(kid->GetParent() == this, "bad parent frame pointer"); + + // Have the child frame list + kid->List(out, aIndent + 1, aFlags); + } + IndentBy(out, aIndent); + fputs(">\n", out); + } + + // XXXmats the above is copy-pasted from nsContainerFrame::List which is lame, + // clean this up after bug 399111 is implemented. + + if (!mPopupList.IsEmpty()) { + fputs("<\n", out); + ++aIndent; + IndentBy(out, aIndent); + fputs(mozilla::layout::ChildListName(kPopupList), out); + fputs(" for ", out); + ListTag(out); + fputs(" <\n", out); + ++aIndent; + for (nsFrameList::Enumerator e(mPopupList); !e.AtEnd(); e.Next()) { + e.get()->List(out, aIndent, aFlags); + } + --aIndent; + IndentBy(out, aIndent); + fputs(">\n", out); + --aIndent; + IndentBy(out, aIndent); + fputs(">\n", out); + outputOneList = true; + } + + if (!outputOneList) { + fputs("<>\n", out); + } +} +#endif diff --git a/layout/xul/base/src/nsPopupSetFrame.h b/layout/xul/base/src/nsPopupSetFrame.h new file mode 100644 index 000000000..96d373643 --- /dev/null +++ b/layout/xul/base/src/nsPopupSetFrame.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPopupSetFrame_h__ +#define nsPopupSetFrame_h__ + +#include "mozilla/Attributes.h" +#include "nsIAtom.h" +#include "nsBoxFrame.h" + +nsIFrame* NS_NewPopupSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsPopupSetFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + nsPopupSetFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsBoxFrame(aShell, aContext) {} + + ~nsPopupSetFrame() {} + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + NS_IMETHOD AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) MOZ_OVERRIDE; + NS_IMETHOD RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) MOZ_OVERRIDE; + NS_IMETHOD InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) MOZ_OVERRIDE; + NS_IMETHOD SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) MOZ_OVERRIDE; + + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + // Used to destroy our popup frames. + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + virtual nsIAtom* GetType() const MOZ_OVERRIDE; + +#ifdef DEBUG + void List(FILE* out, int32_t aIndent, uint32_t aFlags = 0) const MOZ_OVERRIDE; + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE + { + return MakeFrameName(NS_LITERAL_STRING("PopupSet"), aResult); + } +#endif + +protected: + void AddPopupFrameList(nsFrameList& aPopupFrameList); + void RemovePopupFrame(nsIFrame* aPopup); + + nsFrameList mPopupList; +}; + +#endif diff --git a/layout/xul/base/src/nsProgressMeterFrame.cpp b/layout/xul/base/src/nsProgressMeterFrame.cpp new file mode 100644 index 000000000..c235a1165 --- /dev/null +++ b/layout/xul/base/src/nsProgressMeterFrame.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// David Hyatt & Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsProgressMeterFrame.h" +#include "nsCSSRendering.h" +#include "nsIContent.h" +#include "nsPresContext.h" +#include "nsGkAtoms.h" +#include "nsINameSpaceManager.h" +#include "nsCOMPtr.h" +#include "nsBoxLayoutState.h" +#include "nsIReflowCallback.h" +#include "nsContentUtils.h" +#include "mozilla/Attributes.h" +// +// NS_NewToolbarFrame +// +// Creates a new Toolbar frame and returns it +// +nsIFrame* +NS_NewProgressMeterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsProgressMeterFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsProgressMeterFrame) + +// +// nsProgressMeterFrame dstr +// +// Cleanup, if necessary +// +nsProgressMeterFrame :: ~nsProgressMeterFrame ( ) +{ +} + +class nsAsyncProgressMeterInit MOZ_FINAL : public nsIReflowCallback +{ +public: + nsAsyncProgressMeterInit(nsIFrame* aFrame) : mWeakFrame(aFrame) {} + + virtual bool ReflowFinished() + { + bool shouldFlush = false; + nsIFrame* frame = mWeakFrame.GetFrame(); + if (frame) { + nsAutoScriptBlocker scriptBlocker; + frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::mode, 0); + shouldFlush = true; + } + delete this; + return shouldFlush; + } + + virtual void ReflowCallbackCanceled() + { + delete this; + } + + nsWeakFrame mWeakFrame; +}; + +NS_IMETHODIMP +nsProgressMeterFrame::DoLayout(nsBoxLayoutState& aState) +{ + if (mNeedsReflowCallback) { + nsIReflowCallback* cb = new nsAsyncProgressMeterInit(this); + if (cb) { + PresContext()->PresShell()->PostReflowCallback(cb); + } + mNeedsReflowCallback = false; + } + return nsBoxFrame::DoLayout(aState); +} + +NS_IMETHODIMP +nsProgressMeterFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), + "Scripts not blocked in nsProgressMeterFrame::AttributeChanged!"); + nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + if (NS_OK != rv) { + return rv; + } + + // did the progress change? + bool undetermined = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mode, + nsGkAtoms::undetermined, eCaseMatters); + if (nsGkAtoms::mode == aAttribute || + (!undetermined && + (nsGkAtoms::value == aAttribute || nsGkAtoms::max == aAttribute))) { + nsIFrame* barChild = GetFirstPrincipalChild(); + if (!barChild) return NS_OK; + nsIFrame* remainderChild = barChild->GetNextSibling(); + if (!remainderChild) return NS_OK; + nsCOMPtr<nsIContent> remainderContent = remainderChild->GetContent(); + if (!remainderContent) return NS_OK; + + int32_t flex = 1, maxFlex = 1; + if (!undetermined) { + nsAutoString value, maxValue; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value); + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxValue); + + nsresult error; + flex = value.ToInteger(&error); + maxFlex = maxValue.ToInteger(&error); + if (NS_FAILED(error) || maxValue.IsEmpty()) { + maxFlex = 100; + } + if (maxFlex < 1) { + maxFlex = 1; + } + if (flex < 0) { + flex = 0; + } + if (flex > maxFlex) { + flex = maxFlex; + } + } + + nsContentUtils::AddScriptRunner(new nsSetAttrRunnable( + barChild->GetContent(), nsGkAtoms::flex, flex)); + nsContentUtils::AddScriptRunner(new nsSetAttrRunnable( + remainderContent, nsGkAtoms::flex, maxFlex - flex)); + nsContentUtils::AddScriptRunner(new nsReflowFrameRunnable( + this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY)); + } + return NS_OK; +} + +#ifdef DEBUG +NS_IMETHODIMP +nsProgressMeterFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("ProgressMeter"), aResult); +} +#endif diff --git a/layout/xul/base/src/nsProgressMeterFrame.h b/layout/xul/base/src/nsProgressMeterFrame.h new file mode 100644 index 000000000..e3ac168c3 --- /dev/null +++ b/layout/xul/base/src/nsProgressMeterFrame.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + David Hyatt & Eric D Vaughan. + + An XBL-based progress meter. + + Attributes: + + value: A number between 0% and 100% + align: horizontal or vertical + mode: determined, undetermined (one shows progress other shows animated candy cane) + +**/ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +class nsProgressMeterFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewProgressMeterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE; +#endif + +protected: + nsProgressMeterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) : + nsBoxFrame(aPresShell, aContext), mNeedsReflowCallback(true) {} + virtual ~nsProgressMeterFrame(); + + bool mNeedsReflowCallback; +}; // class nsProgressMeterFrame diff --git a/layout/xul/base/src/nsRepeatService.cpp b/layout/xul/base/src/nsRepeatService.cpp new file mode 100644 index 000000000..e54ead684 --- /dev/null +++ b/layout/xul/base/src/nsRepeatService.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsRepeatService.h" +#include "nsIServiceManager.h" + +nsRepeatService* nsRepeatService::gInstance = nullptr; + +nsRepeatService::nsRepeatService() +: mCallback(nullptr), mCallbackData(nullptr) +{ +} + +nsRepeatService::~nsRepeatService() +{ + NS_ASSERTION(!mCallback && !mCallbackData, "Callback was not removed before shutdown"); +} + +nsRepeatService* +nsRepeatService::GetInstance() +{ + if (!gInstance) { + gInstance = new nsRepeatService(); + NS_IF_ADDREF(gInstance); + } + return gInstance; +} + +/*static*/ void +nsRepeatService::Shutdown() +{ + NS_IF_RELEASE(gInstance); +} + +void nsRepeatService::Start(Callback aCallback, void* aCallbackData, + uint32_t aInitialDelay) +{ + NS_PRECONDITION(aCallback != nullptr, "null ptr"); + + mCallback = aCallback; + mCallbackData = aCallbackData; + nsresult rv; + mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + + if (NS_SUCCEEDED(rv)) { + mRepeatTimer->InitWithCallback(this, aInitialDelay, nsITimer::TYPE_ONE_SHOT); + } +} + +void nsRepeatService::Stop(Callback aCallback, void* aCallbackData) +{ + if (mCallback != aCallback || mCallbackData != aCallbackData) + return; + + //printf("Stopping repeat timer\n"); + if (mRepeatTimer) { + mRepeatTimer->Cancel(); + mRepeatTimer = nullptr; + } + mCallback = nullptr; + mCallbackData = nullptr; +} + +NS_IMETHODIMP nsRepeatService::Notify(nsITimer *timer) +{ + // do callback + if (mCallback) + mCallback(mCallbackData); + + // start timer again. + if (mRepeatTimer) { + mRepeatTimer->InitWithCallback(this, REPEAT_DELAY, nsITimer::TYPE_ONE_SHOT); + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS1(nsRepeatService, nsITimerCallback) diff --git a/layout/xul/base/src/nsRepeatService.h b/layout/xul/base/src/nsRepeatService.h new file mode 100644 index 000000000..cda383e20 --- /dev/null +++ b/layout/xul/base/src/nsRepeatService.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// nsRepeatService +// +#ifndef nsRepeatService_h__ +#define nsRepeatService_h__ + +#include "nsCOMPtr.h" +#include "nsITimer.h" + +#define INITAL_REPEAT_DELAY 250 + +#ifdef XP_MACOSX +#define REPEAT_DELAY 25 +#else +#define REPEAT_DELAY 50 +#endif + +class nsITimer; + +class nsRepeatService : public nsITimerCallback +{ +public: + + typedef void (* Callback)(void* aData); + + NS_DECL_NSITIMERCALLBACK + + // Start dispatching timer events to the callback. There is no memory + // management of aData here; it is the caller's responsibility to call + // Stop() before aData's memory is released. + void Start(Callback aCallback, void* aData, + uint32_t aInitialDelay = INITAL_REPEAT_DELAY); + // Stop dispatching timer events to the callback. If the repeat service + // is not currently configured with the given callback and data, this + // is just ignored. + void Stop(Callback aCallback, void* aData); + + static nsRepeatService* GetInstance(); + static void Shutdown(); + + NS_DECL_ISUPPORTS + virtual ~nsRepeatService(); + +protected: + nsRepeatService(); + +private: + Callback mCallback; + void* mCallbackData; + nsCOMPtr<nsITimer> mRepeatTimer; + static nsRepeatService* gInstance; + +}; // class nsRepeatService + +#endif diff --git a/layout/xul/base/src/nsResizerFrame.cpp b/layout/xul/base/src/nsResizerFrame.cpp new file mode 100644 index 000000000..4c3d7fe4b --- /dev/null +++ b/layout/xul/base/src/nsResizerFrame.cpp @@ -0,0 +1,548 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsIServiceManager.h" +#include "nsResizerFrame.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMNodeList.h" +#include "nsGkAtoms.h" +#include "nsINameSpaceManager.h" +#include "nsIDOMElementCSSInlineStyle.h" +#include "nsIDOMCSSStyleDeclaration.h" + +#include "nsPresContext.h" +#include "nsFrameManager.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIBaseWindow.h" +#include "nsPIDOMWindow.h" +#include "nsGUIEvent.h" +#include "nsEventDispatcher.h" +#include "nsContentUtils.h" +#include "nsMenuPopupFrame.h" +#include "nsIScreenManager.h" +#include "mozilla/dom/Element.h" +#include "nsError.h" +#include <algorithm> + +using namespace mozilla; + +// +// NS_NewResizerFrame +// +// Creates a new Resizer frame and returns it +// +nsIFrame* +NS_NewResizerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsResizerFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsResizerFrame) + +nsResizerFrame::nsResizerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +:nsTitleBarFrame(aPresShell, aContext) +{ +} + +NS_IMETHODIMP +nsResizerFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + nsWeakFrame weakFrame(this); + bool doDefault = true; + + switch (aEvent->message) { + case NS_TOUCH_START: + case NS_MOUSE_BUTTON_DOWN: { + if (aEvent->eventStructType == NS_TOUCH_EVENT || + (aEvent->eventStructType == NS_MOUSE_EVENT && + static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eLeftButton)) + { + nsCOMPtr<nsIBaseWindow> window; + nsIPresShell* presShell = aPresContext->GetPresShell(); + nsIContent* contentToResize = + GetContentToResize(presShell, getter_AddRefs(window)); + if (contentToResize) { + nsIFrame* frameToResize = contentToResize->GetPrimaryFrame(); + if (!frameToResize) + break; + + // cache the content rectangle for the frame to resize + // GetScreenRectInAppUnits returns the border box rectangle, so + // adjust to get the desired content rectangle. + nsRect rect = frameToResize->GetScreenRectInAppUnits(); + switch (frameToResize->StylePosition()->mBoxSizing) { + case NS_STYLE_BOX_SIZING_CONTENT: + rect.Deflate(frameToResize->GetUsedPadding()); + case NS_STYLE_BOX_SIZING_PADDING: + rect.Deflate(frameToResize->GetUsedBorder()); + default: + break; + } + + mMouseDownRect = rect.ToNearestPixels(aPresContext->AppUnitsPerDevPixel()); + doDefault = false; + } + else { + // If there is no window, then resizing isn't allowed. + if (!window) + break; + + doDefault = false; + + // ask the widget implementation to begin a resize drag if it can + Direction direction = GetDirection(); + nsresult rv = aEvent->widget->BeginResizeDrag(aEvent, + direction.mHorizontal, direction.mVertical); + // for native drags, don't set the fields below + if (rv != NS_ERROR_NOT_IMPLEMENTED) + break; + + // if there's no native resize support, we need to do window + // resizing ourselves + window->GetPositionAndSize(&mMouseDownRect.x, &mMouseDownRect.y, + &mMouseDownRect.width, &mMouseDownRect.height); + } + + // remember current mouse coordinates + nsIntPoint refPoint; + if (!GetEventPoint(aEvent, refPoint)) + return NS_OK; + mMouseDownPoint = refPoint + aEvent->widget->WidgetToScreenOffset(); + + // we're tracking + mTrackingMouseMove = true; + + nsIPresShell::SetCapturingContent(GetContent(), CAPTURE_IGNOREALLOWED); + } + } + break; + + case NS_TOUCH_END: + case NS_MOUSE_BUTTON_UP: { + + if (aEvent->eventStructType == NS_TOUCH_EVENT || + (aEvent->eventStructType == NS_MOUSE_EVENT && + static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eLeftButton)) + { + // we're done tracking. + mTrackingMouseMove = false; + + nsIPresShell::SetCapturingContent(nullptr, 0); + + doDefault = false; + } + } + break; + + case NS_TOUCH_MOVE: + case NS_MOUSE_MOVE: { + if (mTrackingMouseMove) + { + nsCOMPtr<nsIBaseWindow> window; + nsIPresShell* presShell = aPresContext->GetPresShell(); + nsCOMPtr<nsIContent> contentToResize = + GetContentToResize(presShell, getter_AddRefs(window)); + + // check if the returned content really is a menupopup + nsMenuPopupFrame* menuPopupFrame = nullptr; + if (contentToResize) { + menuPopupFrame = do_QueryFrame(contentToResize->GetPrimaryFrame()); + } + + // both MouseMove and direction are negative when pointing to the + // top and left, and positive when pointing to the bottom and right + + // retrieve the offset of the mousemove event relative to the mousedown. + // The difference is how much the resize needs to be + nsIntPoint refPoint; + if (!GetEventPoint(aEvent, refPoint)) + return NS_OK; + nsIntPoint screenPoint(refPoint + aEvent->widget->WidgetToScreenOffset()); + nsIntPoint mouseMove(screenPoint - mMouseDownPoint); + + // Determine which direction to resize by checking the dir attribute. + // For windows and menus, ensure that it can be resized in that direction. + Direction direction = GetDirection(); + if (window || menuPopupFrame) { + if (menuPopupFrame) { + menuPopupFrame->CanAdjustEdges( + (direction.mHorizontal == -1) ? NS_SIDE_LEFT : NS_SIDE_RIGHT, + (direction.mVertical == -1) ? NS_SIDE_TOP : NS_SIDE_BOTTOM, mouseMove); + } + } + else if (!contentToResize) { + break; // don't do anything if there's nothing to resize + } + + nsIntRect rect = mMouseDownRect; + + // Check if there are any size constraints on this window. + widget::SizeConstraints sizeConstraints; + if (window) { + nsCOMPtr<nsIWidget> widget; + window->GetMainWidget(getter_AddRefs(widget)); + sizeConstraints = widget->GetSizeConstraints(); + } + + AdjustDimensions(&rect.x, &rect.width, sizeConstraints.mMinSize.width, + sizeConstraints.mMaxSize.width, mouseMove.x, direction.mHorizontal); + AdjustDimensions(&rect.y, &rect.height, sizeConstraints.mMinSize.height, + sizeConstraints.mMaxSize.height, mouseMove.y, direction.mVertical); + + // Don't allow resizing a window or a popup past the edge of the screen, + // so adjust the rectangle to fit within the available screen area. + if (window) { + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (sm) { + nsIntRect frameRect = GetScreenRect(); + // ScreenForRect requires display pixels, so scale from device pix + double scale; + window->GetUnscaledDevicePixelsPerCSSPixel(&scale); + sm->ScreenForRect(NSToIntRound(frameRect.x / scale), + NSToIntRound(frameRect.y / scale), 1, 1, + getter_AddRefs(screen)); + if (screen) { + nsIntRect screenRect; + screen->GetRect(&screenRect.x, &screenRect.y, + &screenRect.width, &screenRect.height); + rect.IntersectRect(rect, screenRect); + } + } + } + else if (menuPopupFrame) { + nsRect frameRect = menuPopupFrame->GetScreenRectInAppUnits(); + nsIFrame* rootFrame = aPresContext->PresShell()->FrameManager()->GetRootFrame(); + nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits(); + + nsRect screenRect = menuPopupFrame->GetConstraintRect(frameRect, rootScreenRect); + // round using ToInsidePixels as it's better to be a pixel too small + // than be too large. If the popup is too large it could get flipped + // to the opposite side of the anchor point while resizing. + nsIntRect screenRectPixels = screenRect.ToInsidePixels(aPresContext->AppUnitsPerDevPixel()); + rect.IntersectRect(rect, screenRectPixels); + } + + if (contentToResize) { + // convert the rectangle into css pixels. When changing the size in a + // direction, don't allow the new size to be less that the resizer's + // size. This ensures that content isn't resized too small as to make + // the resizer invisible. + nsRect appUnitsRect = rect.ToAppUnits(aPresContext->AppUnitsPerDevPixel()); + if (appUnitsRect.width < mRect.width && mouseMove.x) + appUnitsRect.width = mRect.width; + if (appUnitsRect.height < mRect.height && mouseMove.y) + appUnitsRect.height = mRect.height; + nsIntRect cssRect = appUnitsRect.ToInsidePixels(nsPresContext::AppUnitsPerCSSPixel()); + + nsIntRect oldRect; + nsWeakFrame weakFrame(menuPopupFrame); + if (menuPopupFrame) { + nsCOMPtr<nsIWidget> widget = menuPopupFrame->GetWidget(); + if (widget) + widget->GetScreenBounds(oldRect); + + // convert the new rectangle into outer window coordinates + nsIntPoint clientOffset = widget->GetClientOffset(); + rect.x -= clientOffset.x; + rect.y -= clientOffset.y; + } + + SizeInfo sizeInfo, originalSizeInfo; + sizeInfo.width.AppendInt(cssRect.width); + sizeInfo.height.AppendInt(cssRect.height); + ResizeContent(contentToResize, direction, sizeInfo, &originalSizeInfo); + MaybePersistOriginalSize(contentToResize, originalSizeInfo); + + // Move the popup to the new location unless it is anchored, since + // the position shouldn't change. nsMenuPopupFrame::SetPopupPosition + // will instead ensure that the popup's position is anchored at the + // right place. + if (weakFrame.IsAlive() && + (oldRect.x != rect.x || oldRect.y != rect.y) && + (!menuPopupFrame->IsAnchored() || + menuPopupFrame->PopupLevel() != ePopupLevelParent)) { + menuPopupFrame->MoveTo(rect.x, rect.y, true); + } + } + else { + window->SetPositionAndSize(rect.x, rect.y, rect.width, rect.height, true); // do the repaint. + } + + doDefault = false; + } + } + break; + + case NS_MOUSE_CLICK: + if (NS_IS_MOUSE_LEFT_CLICK(aEvent)) + { + MouseClicked(aPresContext, aEvent); + } + break; + + case NS_MOUSE_DOUBLECLICK: + if (aEvent->eventStructType == NS_MOUSE_EVENT && + static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eLeftButton) + { + nsCOMPtr<nsIBaseWindow> window; + nsIPresShell* presShell = aPresContext->GetPresShell(); + nsIContent* contentToResize = + GetContentToResize(presShell, getter_AddRefs(window)); + if (contentToResize) { + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(contentToResize->GetPrimaryFrame()); + if (menuPopupFrame) + break; // Don't restore original sizing for menupopup frames until + // we handle screen constraints here. (Bug 357725) + + RestoreOriginalSize(contentToResize); + } + } + break; + } + + if (!doDefault) + *aEventStatus = nsEventStatus_eConsumeNoDefault; + + if (doDefault && weakFrame.IsAlive()) + return nsTitleBarFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + + return NS_OK; +} + +nsIContent* +nsResizerFrame::GetContentToResize(nsIPresShell* aPresShell, nsIBaseWindow** aWindow) +{ + *aWindow = nullptr; + + nsAutoString elementid; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::element, elementid); + if (elementid.IsEmpty()) { + // If the resizer is in a popup, resize the popup's widget, otherwise + // resize the widget associated with the window. + nsIFrame* popup = GetParent(); + while (popup) { + nsMenuPopupFrame* popupFrame = do_QueryFrame(popup); + if (popupFrame) { + return popupFrame->GetContent(); + } + popup = popup->GetParent(); + } + + // don't allow resizing windows in content shells + bool isChromeShell = false; + nsCOMPtr<nsISupports> cont = aPresShell->GetPresContext()->GetContainer(); + nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont); + if (dsti) { + int32_t type = -1; + isChromeShell = (NS_SUCCEEDED(dsti->GetItemType(&type)) && + type == nsIDocShellTreeItem::typeChrome); + } + + if (!isChromeShell) { + // don't allow resizers in content shells, except for the viewport + // scrollbar which doesn't have a parent + nsIContent* nonNativeAnon = mContent->FindFirstNonChromeOnlyAccessContent(); + if (!nonNativeAnon || nonNativeAnon->GetParent()) { + return nullptr; + } + } + + // get the document and the window - should this be cached? + nsPIDOMWindow *domWindow = aPresShell->GetDocument()->GetWindow(); + if (domWindow) { + nsCOMPtr<nsIDocShell> docShell = domWindow->GetDocShell(); + if (docShell) { + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShell->GetTreeOwner(getter_AddRefs(treeOwner)); + if (treeOwner) { + CallQueryInterface(treeOwner, aWindow); + } + } + } + + return nullptr; + } + + if (elementid.EqualsLiteral("_parent")) { + // return the parent, but skip over native anonymous content + nsIContent* parent = mContent->GetParent(); + return parent ? parent->FindFirstNonChromeOnlyAccessContent() : nullptr; + } + + return aPresShell->GetDocument()->GetElementById(elementid); +} + +void +nsResizerFrame::AdjustDimensions(int32_t* aPos, int32_t* aSize, + int32_t aMinSize, int32_t aMaxSize, + int32_t aMovement, int8_t aResizerDirection) +{ + int32_t oldSize = *aSize; + + *aSize += aResizerDirection * aMovement; + // use one as a minimum size or the element could disappear + if (*aSize < 1) + *aSize = 1; + + // Constrain the size within the minimum and maximum size. + *aSize = std::max(aMinSize, std::min(aMaxSize, *aSize)); + + // For left and top resizers, the window must be moved left by the same + // amount that the window was resized. + if (aResizerDirection == -1) + *aPos += oldSize - *aSize; +} + +/* static */ void +nsResizerFrame::ResizeContent(nsIContent* aContent, const Direction& aDirection, + const SizeInfo& aSizeInfo, SizeInfo* aOriginalSizeInfo) +{ + // for XUL elements, just set the width and height attributes. For + // other elements, set style.width and style.height + if (aContent->IsXUL()) { + if (aOriginalSizeInfo) { + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::width, + aOriginalSizeInfo->width); + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::height, + aOriginalSizeInfo->height); + } + // only set the property if the element could have changed in that direction + if (aDirection.mHorizontal) { + aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::width, aSizeInfo.width, true); + } + if (aDirection.mVertical) { + aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::height, aSizeInfo.height, true); + } + } + else { + nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyleContent = + do_QueryInterface(aContent); + if (inlineStyleContent) { + nsCOMPtr<nsIDOMCSSStyleDeclaration> decl; + inlineStyleContent->GetStyle(getter_AddRefs(decl)); + + if (aOriginalSizeInfo) { + decl->GetPropertyValue(NS_LITERAL_STRING("width"), + aOriginalSizeInfo->width); + decl->GetPropertyValue(NS_LITERAL_STRING("height"), + aOriginalSizeInfo->height); + } + + // only set the property if the element could have changed in that direction + if (aDirection.mHorizontal) { + nsAutoString widthstr(aSizeInfo.width); + if (!widthstr.IsEmpty() && + !Substring(widthstr, widthstr.Length() - 2, 2).EqualsLiteral("px")) + widthstr.AppendLiteral("px"); + decl->SetProperty(NS_LITERAL_STRING("width"), widthstr, EmptyString()); + } + if (aDirection.mVertical) { + nsAutoString heightstr(aSizeInfo.height); + if (!heightstr.IsEmpty() && + !Substring(heightstr, heightstr.Length() - 2, 2).EqualsLiteral("px")) + heightstr.AppendLiteral("px"); + decl->SetProperty(NS_LITERAL_STRING("height"), heightstr, EmptyString()); + } + } + } +} + +/* static */ void +nsResizerFrame::SizeInfoDtorFunc(void *aObject, nsIAtom *aPropertyName, + void *aPropertyValue, void *aData) +{ + nsResizerFrame::SizeInfo *propertyValue = + static_cast<nsResizerFrame::SizeInfo*>(aPropertyValue); + delete propertyValue; +} + +/* static */ void +nsResizerFrame::MaybePersistOriginalSize(nsIContent* aContent, + const SizeInfo& aSizeInfo) +{ + nsresult rv; + + aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv); + if (rv != NS_PROPTABLE_PROP_NOT_THERE) + return; + + nsAutoPtr<SizeInfo> sizeInfo(new SizeInfo(aSizeInfo)); + rv = aContent->SetProperty(nsGkAtoms::_moz_original_size, sizeInfo.get(), + &SizeInfoDtorFunc); + if (NS_SUCCEEDED(rv)) + sizeInfo.forget(); +} + +/* static */ void +nsResizerFrame::RestoreOriginalSize(nsIContent* aContent) +{ + nsresult rv; + SizeInfo* sizeInfo = + static_cast<SizeInfo*>(aContent->GetProperty(nsGkAtoms::_moz_original_size, + &rv)); + if (NS_FAILED(rv)) + return; + + NS_ASSERTION(sizeInfo, "We set a null sizeInfo!?"); + Direction direction = {1, 1}; + ResizeContent(aContent, direction, *sizeInfo, nullptr); + aContent->DeleteProperty(nsGkAtoms::_moz_original_size); +} + +/* returns a Direction struct containing the horizontal and vertical direction + */ +nsResizerFrame::Direction +nsResizerFrame::GetDirection() +{ + static const nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::topleft, &nsGkAtoms::top, &nsGkAtoms::topright, + &nsGkAtoms::left, &nsGkAtoms::right, + &nsGkAtoms::bottomleft, &nsGkAtoms::bottom, &nsGkAtoms::bottomright, + &nsGkAtoms::bottomstart, &nsGkAtoms::bottomend, + nullptr}; + + static const Direction directions[] = + {{-1, -1}, {0, -1}, {1, -1}, + {-1, 0}, {1, 0}, + {-1, 1}, {0, 1}, {1, 1}, + {-1, 1}, {1, 1} + }; + + if (!GetContent()) + return directions[0]; // default: topleft + + int32_t index = GetContent()->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::dir, + strings, eCaseMatters); + if(index < 0) + return directions[0]; // default: topleft + else if (index >= 8 && StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { + // Directions 8 and higher are RTL-aware directions and should reverse the + // horizontal component if RTL. + Direction direction = directions[index]; + direction.mHorizontal *= -1; + return direction; + } + return directions[index]; +} + +void +nsResizerFrame::MouseClicked(nsPresContext* aPresContext, nsGUIEvent *aEvent) +{ + // Execute the oncommand event handler. + nsContentUtils::DispatchXULCommand(mContent, + aEvent && aEvent->mFlags.mIsTrusted); +} diff --git a/layout/xul/base/src/nsResizerFrame.h b/layout/xul/base/src/nsResizerFrame.h new file mode 100644 index 000000000..4efbbc01c --- /dev/null +++ b/layout/xul/base/src/nsResizerFrame.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsResizerFrame_h___ +#define nsResizerFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsTitleBarFrame.h" + +class nsIBaseWindow; +class nsMenuPopupFrame; + +class nsResizerFrame : public nsTitleBarFrame +{ +protected: + struct Direction { + int8_t mHorizontal; + int8_t mVertical; + }; + +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewResizerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + nsResizerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + virtual void MouseClicked(nsPresContext* aPresContext, nsGUIEvent *aEvent) MOZ_OVERRIDE; + +protected: + nsIContent* GetContentToResize(nsIPresShell* aPresShell, nsIBaseWindow** aWindow); + + Direction GetDirection(); + + /** + * Adjust the window position and size in a direction according to the mouse + * movement and the resizer direction. The minimum and maximum size is used + * to constrain the size. + * + * @param aPos left or top position + * @param aSize width or height + * @param aMinSize minimum width or height + * @param aMacSize maximum width or height + * @param aMovement the amount the mouse was moved + * @param aResizerDirection resizer direction returned by GetDirection + */ + static void AdjustDimensions(int32_t* aPos, int32_t* aSize, + int32_t aMinSize, int32_t aMaxSize, + int32_t aMovement, int8_t aResizerDirection); + + struct SizeInfo { + nsString width, height; + }; + static void SizeInfoDtorFunc(void *aObject, nsIAtom *aPropertyName, + void *aPropertyValue, void *aData); + static void ResizeContent(nsIContent* aContent, const Direction& aDirection, + const SizeInfo& aSizeInfo, SizeInfo* aOriginalSizeInfo); + static void MaybePersistOriginalSize(nsIContent* aContent, const SizeInfo& aSizeInfo); + static void RestoreOriginalSize(nsIContent* aContent); + +protected: + nsIntRect mMouseDownRect; + nsIntPoint mMouseDownPoint; +}; // class nsResizerFrame + +#endif /* nsResizerFrame_h___ */ diff --git a/layout/xul/base/src/nsRootBoxFrame.cpp b/layout/xul/base/src/nsRootBoxFrame.cpp new file mode 100644 index 000000000..85a30586f --- /dev/null +++ b/layout/xul/base/src/nsRootBoxFrame.cpp @@ -0,0 +1,308 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsHTMLParts.h" +#include "nsGUIEvent.h" +#include "nsStyleConsts.h" +#include "nsGkAtoms.h" +#include "nsIPresShell.h" +#include "nsBoxFrame.h" +#include "nsStackLayout.h" +#include "nsIRootBox.h" +#include "nsIContent.h" +#include "nsXULTooltipListener.h" +#include "nsFrameManager.h" + +// Interface IDs + +//#define DEBUG_REFLOW + +// static +nsIRootBox* +nsIRootBox::GetRootBox(nsIPresShell* aShell) +{ + if (!aShell) { + return nullptr; + } + nsIFrame* rootFrame = aShell->FrameManager()->GetRootFrame(); + if (!rootFrame) { + return nullptr; + } + + if (rootFrame) { + rootFrame = rootFrame->GetFirstPrincipalChild(); + } + + nsIRootBox* rootBox = do_QueryFrame(rootFrame); + return rootBox; +} + +class nsRootBoxFrame : public nsBoxFrame, public nsIRootBox { +public: + + friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + nsRootBoxFrame(nsIPresShell* aShell, nsStyleContext *aContext); + + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + virtual nsPopupSetFrame* GetPopupSetFrame(); + virtual void SetPopupSetFrame(nsPopupSetFrame* aPopupSet); + virtual nsIContent* GetDefaultTooltip(); + virtual void SetDefaultTooltip(nsIContent* aTooltip); + virtual nsresult AddTooltipSupport(nsIContent* aNode); + virtual nsresult RemoveTooltipSupport(nsIContent* aNode); + + NS_IMETHOD AppendFrames(ChildListID aListID, + nsFrameList& aFrameList); + NS_IMETHOD InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList); + NS_IMETHOD RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame); + + NS_IMETHOD Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus); + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus); + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::rootFrame + */ + virtual nsIAtom* GetType() const; + + virtual bool IsFrameOfType(uint32_t aFlags) const + { + // Override bogus IsFrameOfType in nsBoxFrame. + if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced)) + return false; + return nsBoxFrame::IsFrameOfType(aFlags); + } + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const; +#endif + + nsPopupSetFrame* mPopupSetFrame; + +protected: + nsIContent* mDefaultTooltip; +}; + +//---------------------------------------------------------------------- + +nsIFrame* +NS_NewRootBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsRootBoxFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsRootBoxFrame) + +nsRootBoxFrame::nsRootBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsBoxFrame(aShell, aContext, true) +{ + mPopupSetFrame = nullptr; + + nsCOMPtr<nsBoxLayout> layout; + NS_NewStackLayout(aShell, layout); + SetLayoutManager(layout); +} + +NS_IMETHODIMP +nsRootBoxFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + nsresult rv; + + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list ID"); + NS_PRECONDITION(mFrames.IsEmpty(), "already have a child frame"); + if (aListID != kPrincipalList) { + // We only support the principal child list. + rv = NS_ERROR_INVALID_ARG; + } else if (!mFrames.IsEmpty()) { + // We only allow a single child frame. + rv = NS_ERROR_FAILURE; + } else { + rv = nsBoxFrame::AppendFrames(aListID, aFrameList); + } + + return rv; +} + +NS_IMETHODIMP +nsRootBoxFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + nsresult rv; + + // Because we only support a single child frame inserting is the same + // as appending. + NS_PRECONDITION(!aPrevFrame, "unexpected previous sibling frame"); + if (aPrevFrame) { + rv = NS_ERROR_UNEXPECTED; + } else { + rv = AppendFrames(aListID, aFrameList); + } + + return rv; +} + +NS_IMETHODIMP +nsRootBoxFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + nsresult rv; + + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list ID"); + if (aListID != kPrincipalList) { + // We only support the principal child list. + rv = NS_ERROR_INVALID_ARG; + } else if (aOldFrame == mFrames.FirstChild()) { + rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame); + } else { + rv = NS_ERROR_FAILURE; + } + + return rv; +} + +#ifdef DEBUG_REFLOW +int32_t gReflows = 0; +#endif + +NS_IMETHODIMP +nsRootBoxFrame::Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + DO_GLOBAL_REFLOW_COUNT("nsRootBoxFrame"); + +#ifdef DEBUG_REFLOW + gReflows++; + printf("----Reflow %d----\n", gReflows); +#endif + return nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus); +} + +void +nsRootBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // root boxes don't need a debug border/outline or a selection overlay... + // They *may* have a background propagated to them, so force creation + // of a background display list element. + DisplayBorderBackgroundOutline(aBuilder, aLists, true); + + BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); +} + +NS_IMETHODIMP +nsRootBoxFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + if (aEvent->message == NS_MOUSE_BUTTON_UP) { + nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + } + + return NS_OK; +} + +// REVIEW: The override here was doing nothing since nsBoxFrame is our +// parent class +nsIAtom* +nsRootBoxFrame::GetType() const +{ + return nsGkAtoms::rootFrame; +} + +nsPopupSetFrame* +nsRootBoxFrame::GetPopupSetFrame() +{ + return mPopupSetFrame; +} + +void +nsRootBoxFrame::SetPopupSetFrame(nsPopupSetFrame* aPopupSet) +{ + // Under normal conditions this should only be called once. However, + // if something triggers ReconstructDocElementHierarchy, we will + // destroy this frame's child (the nsDocElementBoxFrame), but not this + // frame. This will cause the popupset to remove itself by calling + // |SetPopupSetFrame(nullptr)|, and then we'll be able to accept a new + // popupset. Since the anonymous content is associated with the + // nsDocElementBoxFrame, we'll get a new popupset when the new doc + // element box frame is created. + if (!mPopupSetFrame || !aPopupSet) { + mPopupSetFrame = aPopupSet; + } else { + NS_NOTREACHED("Popup set is already defined! Only 1 allowed."); + } +} + +nsIContent* +nsRootBoxFrame::GetDefaultTooltip() +{ + return mDefaultTooltip; +} + +void +nsRootBoxFrame::SetDefaultTooltip(nsIContent* aTooltip) +{ + mDefaultTooltip = aTooltip; +} + +nsresult +nsRootBoxFrame::AddTooltipSupport(nsIContent* aNode) +{ + NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); + + nsXULTooltipListener *listener = nsXULTooltipListener::GetInstance(); + if (!listener) + return NS_ERROR_OUT_OF_MEMORY; + + return listener->AddTooltipSupport(aNode); +} + +nsresult +nsRootBoxFrame::RemoveTooltipSupport(nsIContent* aNode) +{ + // XXjh yuck, I'll have to implement a way to get at + // the tooltip listener for a given node to make + // this work. Not crucial, we aren't removing + // tooltips from any nodes in the app just yet. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_QUERYFRAME_HEAD(nsRootBoxFrame) + NS_QUERYFRAME_ENTRY(nsIRootBox) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +#ifdef DEBUG +NS_IMETHODIMP +nsRootBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("RootBox"), aResult); +} +#endif diff --git a/layout/xul/base/src/nsScrollBoxFrame.cpp b/layout/xul/base/src/nsScrollBoxFrame.cpp new file mode 100644 index 000000000..83857afb1 --- /dev/null +++ b/layout/xul/base/src/nsScrollBoxFrame.cpp @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsPresContext.h" +#include "nsGkAtoms.h" +#include "nsGUIEvent.h" +#include "nsButtonBoxFrame.h" +#include "nsITimer.h" +#include "nsRepeatService.h" + +class nsAutoRepeatBoxFrame : public nsButtonBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewAutoRepeatBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + virtual void DestroyFrom(nsIFrame* aDestructRoot); + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType); + + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus); + + NS_IMETHOD HandlePress(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus); + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus); + +protected: + nsAutoRepeatBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext): + nsButtonBoxFrame(aPresShell, aContext) {} + + void StartRepeat() { + if (IsActivatedOnHover()) { + // No initial delay on hover. + nsRepeatService::GetInstance()->Start(Notify, this, 0); + } else { + nsRepeatService::GetInstance()->Start(Notify, this); + } + } + void StopRepeat() { + nsRepeatService::GetInstance()->Stop(Notify, this); + } + void Notify(); + static void Notify(void* aData) { + static_cast<nsAutoRepeatBoxFrame*>(aData)->Notify(); + } + + bool mTrustedEvent; + + bool IsActivatedOnHover(); +}; + +nsIFrame* +NS_NewAutoRepeatBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsAutoRepeatBoxFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsAutoRepeatBoxFrame) + +NS_IMETHODIMP +nsAutoRepeatBoxFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + switch(aEvent->message) + { + // repeat mode may be "hover" for repeating while the mouse is hovering + // over the element, otherwise repetition is done while the element is + // active (pressed). + case NS_MOUSE_ENTER: + case NS_MOUSE_ENTER_SYNTH: + if (IsActivatedOnHover()) { + StartRepeat(); + mTrustedEvent = aEvent->mFlags.mIsTrusted; + } + break; + + case NS_MOUSE_EXIT: + case NS_MOUSE_EXIT_SYNTH: + // always stop on mouse exit + StopRepeat(); + // Not really necessary but do this to be safe + mTrustedEvent = false; + break; + + case NS_MOUSE_CLICK: + if (NS_IS_MOUSE_LEFT_CLICK(aEvent)) { + // skip button frame handling to prevent click handling + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + } + break; + } + + return nsButtonBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +NS_IMETHODIMP +nsAutoRepeatBoxFrame::HandlePress(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + if (!IsActivatedOnHover()) { + StartRepeat(); + mTrustedEvent = aEvent->mFlags.mIsTrusted; + DoMouseClick(aEvent, mTrustedEvent); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAutoRepeatBoxFrame::HandleRelease(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + if (!IsActivatedOnHover()) { + StopRepeat(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsAutoRepeatBoxFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + if (aAttribute == nsGkAtoms::type) { + StopRepeat(); + } + return NS_OK; +} + +void +nsAutoRepeatBoxFrame::Notify() +{ + DoMouseClick(nullptr, mTrustedEvent); +} + +void +nsAutoRepeatBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // Ensure our repeat service isn't going... it's possible that a scrollbar can disappear out + // from under you while you're in the process of scrolling. + StopRepeat(); + nsButtonBoxFrame::DestroyFrom(aDestructRoot); +} + +bool +nsAutoRepeatBoxFrame::IsActivatedOnHover() +{ + return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::repeat, + nsGkAtoms::hover, eCaseMatters); +} diff --git a/layout/xul/base/src/nsScrollBoxObject.cpp b/layout/xul/base/src/nsScrollBoxObject.cpp new file mode 100644 index 000000000..41a1b3975 --- /dev/null +++ b/layout/xul/base/src/nsScrollBoxObject.cpp @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsIScrollBoxObject.h" +#include "nsBoxObject.h" +#include "nsIPresShell.h" +#include "nsIContent.h" +#include "nsIDOMElement.h" +#include "nsPresContext.h" +#include "nsIFrame.h" +#include "nsIScrollableFrame.h" + +class nsScrollBoxObject : public nsIScrollBoxObject, public nsBoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISCROLLBOXOBJECT + + nsScrollBoxObject(); + virtual ~nsScrollBoxObject(); + + virtual nsIScrollableFrame* GetScrollFrame() { + return do_QueryFrame(GetFrame(false)); + } + + /* additional members */ +}; + +/* Implementation file */ + +NS_INTERFACE_MAP_BEGIN(nsScrollBoxObject) + NS_INTERFACE_MAP_ENTRY(nsIScrollBoxObject) +NS_INTERFACE_MAP_END_INHERITING(nsBoxObject) + +NS_IMPL_ADDREF_INHERITED(nsScrollBoxObject, nsBoxObject) +NS_IMPL_RELEASE_INHERITED(nsScrollBoxObject, nsBoxObject) + +nsScrollBoxObject::nsScrollBoxObject() +{ + /* member initializers and constructor code */ +} + +nsScrollBoxObject::~nsScrollBoxObject() +{ + /* destructor code */ +} + +/* void scrollTo (in long x, in long y); */ +NS_IMETHODIMP nsScrollBoxObject::ScrollTo(int32_t x, int32_t y) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) + return NS_ERROR_FAILURE; + sf->ScrollToCSSPixels(nsIntPoint(x, y)); + return NS_OK; +} + +/* void scrollBy (in long dx, in long dy); */ +NS_IMETHODIMP nsScrollBoxObject::ScrollBy(int32_t dx, int32_t dy) +{ + int32_t x, y; + nsresult rv = GetPosition(&x, &y); + if (NS_FAILED(rv)) + return rv; + + return ScrollTo(x + dx, y + dy); +} + +/* void scrollByLine (in long dlines); */ +NS_IMETHODIMP nsScrollBoxObject::ScrollByLine(int32_t dlines) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) + return NS_ERROR_FAILURE; + + sf->ScrollBy(nsIntPoint(0, dlines), nsIScrollableFrame::LINES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +// XUL <scrollbox> elements have a single box child element. +// Get a pointer to that box. +// Note that now that the <scrollbox> is just a regular box +// with 'overflow:hidden', the boxobject's frame is an nsXULScrollFrame, +// the <scrollbox>'s box frame is the scrollframe's "scrolled frame", and +// the <scrollbox>'s child box is a child of that. +static nsIFrame* GetScrolledBox(nsBoxObject* aScrollBox) { + nsIFrame* frame = aScrollBox->GetFrame(false); + if (!frame) + return nullptr; + nsIScrollableFrame* scrollFrame = do_QueryFrame(frame); + if (!scrollFrame) { + NS_WARNING("nsIScrollBoxObject attached to something that's not a scroll frame!"); + return nullptr; + } + nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); + if (!scrolledFrame) + return nullptr; + return scrolledFrame->GetChildBox(); +} + +/* void scrollByIndex (in long dindexes); */ +NS_IMETHODIMP nsScrollBoxObject::ScrollByIndex(int32_t dindexes) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) + return NS_ERROR_FAILURE; + nsIFrame* scrolledBox = GetScrolledBox(this); + if (!scrolledBox) + return NS_ERROR_FAILURE; + + nsRect rect; + + // now get the scrolled boxes first child. + nsIFrame* child = scrolledBox->GetChildBox(); + + bool horiz = scrolledBox->IsHorizontal(); + nsPoint cp = sf->GetScrollPosition(); + nscoord diff = 0; + int32_t curIndex = 0; + bool isLTR = scrolledBox->IsNormalDirection(); + + int32_t frameWidth = 0; + if (!isLTR && horiz) { + GetWidth(&frameWidth); + nsCOMPtr<nsIPresShell> shell = GetPresShell(false); + if (!shell) { + return NS_ERROR_UNEXPECTED; + } + frameWidth = nsPresContext::CSSPixelsToAppUnits(frameWidth); + } + + // first find out what index we are currently at + while(child) { + rect = child->GetRect(); + if (horiz) { + // In the left-to-right case we break from the loop when the center of + // the current child rect is greater than the scrolled position of + // the left edge of the scrollbox + // In the right-to-left case we break when the center of the current + // child rect is less than the scrolled position of the right edge of + // the scrollbox. + diff = rect.x + rect.width/2; // use the center, to avoid rounding errors + if ((isLTR && diff > cp.x) || + (!isLTR && diff < cp.x + frameWidth)) { + break; + } + } else { + diff = rect.y + rect.height/2;// use the center, to avoid rounding errors + if (diff > cp.y) { + break; + } + } + child = child->GetNextBox(); + curIndex++; + } + + int32_t count = 0; + + if (dindexes == 0) + return NS_OK; + + if (dindexes > 0) { + while(child) { + child = child->GetNextBox(); + if (child) + rect = child->GetRect(); + count++; + if (count >= dindexes) + break; + } + + } else if (dindexes < 0) { + child = scrolledBox->GetChildBox(); + while(child) { + rect = child->GetRect(); + if (count >= curIndex + dindexes) + break; + + count++; + child = child->GetNextBox(); + + } + } + + nscoord csspixel = nsPresContext::CSSPixelsToAppUnits(1); + if (horiz) { + // In the left-to-right case we scroll so that the left edge of the + // selected child is scrolled to the left edge of the scrollbox. + // In the right-to-left case we scroll so that the right edge of the + // selected child is scrolled to the right edge of the scrollbox. + + nsPoint pt(isLTR ? rect.x : rect.x + rect.width - frameWidth, + cp.y); + + // Use a destination range that ensures the left edge (or right edge, + // for RTL) will indeed be visible. Also ensure that the top edge + // is visible. + nsRect range(pt.x, pt.y, csspixel, 0); + if (isLTR) { + range.x -= csspixel; + } + sf->ScrollTo(pt, nsIScrollableFrame::INSTANT, &range); + } else { + // Use a destination range that ensures the top edge will be visible. + nsRect range(cp.x, rect.y - csspixel, 0, csspixel); + sf->ScrollTo(nsPoint(cp.x, rect.y), nsIScrollableFrame::INSTANT, &range); + } + + return NS_OK; +} + +/* void scrollToLine (in long line); */ +NS_IMETHODIMP nsScrollBoxObject::ScrollToLine(int32_t line) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) + return NS_ERROR_FAILURE; + + nscoord y = sf->GetLineScrollAmount().height * line; + nsRect range(0, y - nsPresContext::CSSPixelsToAppUnits(1), + 0, nsPresContext::CSSPixelsToAppUnits(1)); + sf->ScrollTo(nsPoint(0, y), nsIScrollableFrame::INSTANT, &range); + return NS_OK; +} + +/* void scrollToElement (in nsIDOMElement child); */ +NS_IMETHODIMP nsScrollBoxObject::ScrollToElement(nsIDOMElement *child) +{ + NS_ENSURE_ARG_POINTER(child); + + nsCOMPtr<nsIPresShell> shell = GetPresShell(false); + if (!shell) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(child); + shell->ScrollContentIntoView(content, + nsIPresShell::ScrollAxis( + nsIPresShell::SCROLL_TOP, + nsIPresShell::SCROLL_ALWAYS), + nsIPresShell::ScrollAxis( + nsIPresShell::SCROLL_LEFT, + nsIPresShell::SCROLL_ALWAYS), + nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY | + nsIPresShell::SCROLL_OVERFLOW_HIDDEN); + return NS_OK; +} + +/* void scrollToIndex (in long index); */ +NS_IMETHODIMP nsScrollBoxObject::ScrollToIndex(int32_t index) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void getPosition (out long x, out long y); */ +NS_IMETHODIMP nsScrollBoxObject::GetPosition(int32_t *x, int32_t *y) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) + return NS_ERROR_FAILURE; + + nsIntPoint pt = sf->GetScrollPositionCSSPixels(); + *x = pt.x; + *y = pt.y; + + return NS_OK; +} + +/* void getScrolledSize (out long width, out long height); */ +NS_IMETHODIMP nsScrollBoxObject::GetScrolledSize(int32_t *width, int32_t *height) +{ + nsIFrame* scrolledBox = GetScrolledBox(this); + if (!scrolledBox) + return NS_ERROR_FAILURE; + + nsRect scrollRect = scrolledBox->GetRect(); + + *width = nsPresContext::AppUnitsToIntCSSPixels(scrollRect.width); + *height = nsPresContext::AppUnitsToIntCSSPixels(scrollRect.height); + + return NS_OK; +} + +/* void ensureElementIsVisible (in nsIDOMElement child); */ +NS_IMETHODIMP nsScrollBoxObject::EnsureElementIsVisible(nsIDOMElement *child) +{ + NS_ENSURE_ARG_POINTER(child); + + nsCOMPtr<nsIPresShell> shell = GetPresShell(false); + if (!shell) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(child); + shell->ScrollContentIntoView(content, + nsIPresShell::ScrollAxis(), + nsIPresShell::ScrollAxis(), + nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY | + nsIPresShell::SCROLL_OVERFLOW_HIDDEN); + return NS_OK; +} + +/* void ensureIndexIsVisible (in long index); */ +NS_IMETHODIMP nsScrollBoxObject::EnsureIndexIsVisible(int32_t index) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void ensureLineIsVisible (in long line); */ +NS_IMETHODIMP nsScrollBoxObject::EnsureLineIsVisible(int32_t line) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +NS_NewScrollBoxObject(nsIBoxObject** aResult) +{ + *aResult = new nsScrollBoxObject; + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} + diff --git a/layout/xul/base/src/nsScrollbarButtonFrame.cpp b/layout/xul/base/src/nsScrollbarButtonFrame.cpp new file mode 100644 index 000000000..7e57a65ab --- /dev/null +++ b/layout/xul/base/src/nsScrollbarButtonFrame.cpp @@ -0,0 +1,315 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsScrollbarButtonFrame.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsCOMPtr.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsSliderFrame.h" +#include "nsScrollbarFrame.h" +#include "nsIScrollbarMediator.h" +#include "nsRepeatService.h" +#include "nsGUIEvent.h" +#include "mozilla/LookAndFeel.h" + +using namespace mozilla; + +// +// NS_NewToolbarFrame +// +// Creates a new Toolbar frame and returns it +// +nsIFrame* +NS_NewScrollbarButtonFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsScrollbarButtonFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarButtonFrame) + +NS_IMETHODIMP +nsScrollbarButtonFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + + // If a web page calls event.preventDefault() we still want to + // scroll when scroll arrow is clicked. See bug 511075. + if (!mContent->IsInNativeAnonymousSubtree() && + nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + switch (aEvent->message) { + case NS_MOUSE_BUTTON_DOWN: + mCursorOnThis = true; + // if we didn't handle the press ourselves, pass it on to the superclass + if (HandleButtonPress(aPresContext, aEvent, aEventStatus)) { + return NS_OK; + } + break; + case NS_MOUSE_BUTTON_UP: + HandleRelease(aPresContext, aEvent, aEventStatus); + break; + case NS_MOUSE_EXIT_SYNTH: + mCursorOnThis = false; + break; + case NS_MOUSE_MOVE: { + nsPoint cursor = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this); + nsRect frameRect(nsPoint(0, 0), GetSize()); + mCursorOnThis = frameRect.Contains(cursor); + break; + } + } + + return nsButtonBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + + +bool +nsScrollbarButtonFrame::HandleButtonPress(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + // Get the desired action for the scrollbar button. + LookAndFeel::IntID tmpAction; + uint16_t button = static_cast<nsMouseEvent*>(aEvent)->button; + if (button == nsMouseEvent::eLeftButton) { + tmpAction = LookAndFeel::eIntID_ScrollButtonLeftMouseButtonAction; + } else if (button == nsMouseEvent::eMiddleButton) { + tmpAction = LookAndFeel::eIntID_ScrollButtonMiddleMouseButtonAction; + } else if (button == nsMouseEvent::eRightButton) { + tmpAction = LookAndFeel::eIntID_ScrollButtonRightMouseButtonAction; + } else { + return false; + } + + // Get the button action metric from the pres. shell. + int32_t pressedButtonAction; + if (NS_FAILED(LookAndFeel::GetInt(tmpAction, &pressedButtonAction))) { + return false; + } + + // get the scrollbar control + nsIFrame* scrollbar; + GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar); + + if (scrollbar == nullptr) + return false; + + // get the scrollbars content node + nsIContent* content = scrollbar->GetContent(); + + static nsIContent::AttrValuesArray strings[] = { &nsGkAtoms::increment, + &nsGkAtoms::decrement, + nullptr }; + int32_t index = mContent->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::type, + strings, eCaseMatters); + int32_t direction; + if (index == 0) + direction = 1; + else if (index == 1) + direction = -1; + else + return false; + + // Whether or not to repeat the click action. + bool repeat = true; + // Use smooth scrolling by default. + bool smoothScroll = true; + switch (pressedButtonAction) { + case 0: + mIncrement = direction * nsSliderFrame::GetIncrement(content); + break; + case 1: + mIncrement = direction * nsSliderFrame::GetPageIncrement(content); + break; + case 2: + if (direction == -1) + mIncrement = -nsSliderFrame::GetCurrentPosition(content); + else + mIncrement = nsSliderFrame::GetMaxPosition(content) - + nsSliderFrame::GetCurrentPosition(content); + // Don't repeat or use smooth scrolling if scrolling to beginning or end + // of a page. + repeat = smoothScroll = false; + break; + case 3: + default: + // We were told to ignore this click, or someone assigned a non-standard + // value to the button's action. + return false; + } + // set this attribute so we can style it later + nsWeakFrame weakFrame(this); + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true); + + nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED); + + if (weakFrame.IsAlive()) { + DoButtonAction(smoothScroll); + } + if (repeat) + StartRepeat(); + return true; +} + +NS_IMETHODIMP +nsScrollbarButtonFrame::HandleRelease(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + nsIPresShell::SetCapturingContent(nullptr, 0); + // we're not active anymore + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true); + StopRepeat(); + return NS_OK; +} + +void nsScrollbarButtonFrame::Notify() +{ + // Since this is only going to get called if we're scrolling a page length + // or a line increment, we will always use smooth scrolling. + if (mCursorOnThis || + LookAndFeel::GetInt( + LookAndFeel::eIntID_ScrollbarButtonAutoRepeatBehavior, 0)) { + DoButtonAction(true); + } +} + +void +nsScrollbarButtonFrame::MouseClicked(nsPresContext* aPresContext, nsGUIEvent* aEvent) +{ + nsButtonBoxFrame::MouseClicked(aPresContext, aEvent); + //MouseClicked(); +} + +void +nsScrollbarButtonFrame::DoButtonAction(bool aSmoothScroll) +{ + // get the scrollbar control + nsIFrame* scrollbar; + GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar); + + if (scrollbar == nullptr) + return; + + // get the scrollbars content node + nsCOMPtr<nsIContent> content = scrollbar->GetContent(); + + // get the current pos + int32_t curpos = nsSliderFrame::GetCurrentPosition(content); + int32_t oldpos = curpos; + + // get the max pos + int32_t maxpos = nsSliderFrame::GetMaxPosition(content); + + // increment the given amount + if (mIncrement) + curpos += mIncrement; + + // make sure the current position is between the current and max positions + if (curpos < 0) + curpos = 0; + else if (curpos > maxpos) + curpos = maxpos; + + nsScrollbarFrame* sb = do_QueryFrame(scrollbar); + if (sb) { + nsIScrollbarMediator* m = sb->GetScrollbarMediator(); + if (m) { + m->ScrollbarButtonPressed(sb, oldpos, curpos); + return; + } + } + + // set the current position of the slider. + nsAutoString curposStr; + curposStr.AppendInt(curpos); + + if (aSmoothScroll) + content->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false); + content->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curposStr, true); + if (aSmoothScroll) + content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false); +} + +nsresult +nsScrollbarButtonFrame::GetChildWithTag(nsPresContext* aPresContext, + nsIAtom* atom, nsIFrame* start, + nsIFrame*& result) +{ + // recursively search our children + nsIFrame* childFrame = start->GetFirstPrincipalChild(); + while (nullptr != childFrame) + { + // get the content node + nsIContent* child = childFrame->GetContent(); + + if (child) { + // see if it is the child + if (child->Tag() == atom) + { + result = childFrame; + + return NS_OK; + } + } + + // recursive search the child + GetChildWithTag(aPresContext, atom, childFrame, result); + if (result != nullptr) + return NS_OK; + + childFrame = childFrame->GetNextSibling(); + } + + result = nullptr; + return NS_OK; +} + +nsresult +nsScrollbarButtonFrame::GetParentWithTag(nsIAtom* toFind, nsIFrame* start, + nsIFrame*& result) +{ + while (start) + { + start = start->GetParent(); + + if (start) { + // get the content node + nsIContent* child = start->GetContent(); + + if (child && child->Tag() == toFind) { + result = start; + return NS_OK; + } + } + } + + result = nullptr; + return NS_OK; +} + +void +nsScrollbarButtonFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // Ensure our repeat service isn't going... it's possible that a scrollbar can disappear out + // from under you while you're in the process of scrolling. + StopRepeat(); + nsButtonBoxFrame::DestroyFrom(aDestructRoot); +} diff --git a/layout/xul/base/src/nsScrollbarButtonFrame.h b/layout/xul/base/src/nsScrollbarButtonFrame.h new file mode 100644 index 000000000..6f73bb628 --- /dev/null +++ b/layout/xul/base/src/nsScrollbarButtonFrame.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Eric D Vaughan + This class lays out its children either vertically or horizontally + +**/ + +#ifndef nsScrollbarButtonFrame_h___ +#define nsScrollbarButtonFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsButtonBoxFrame.h" +#include "nsITimer.h" +#include "nsRepeatService.h" + +class nsSliderFrame; + +class nsScrollbarButtonFrame : public nsButtonBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + nsScrollbarButtonFrame(nsIPresShell* aPresShell, nsStyleContext* aContext): + nsButtonBoxFrame(aPresShell, aContext), mCursorOnThis(false) {} + + // Overrides + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + friend nsIFrame* NS_NewScrollbarButtonFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + static nsresult GetChildWithTag(nsPresContext* aPresContext, + nsIAtom* atom, nsIFrame* start, nsIFrame*& result); + static nsresult GetParentWithTag(nsIAtom* atom, nsIFrame* start, nsIFrame*& result); + + bool HandleButtonPress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus); + + NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) MOZ_OVERRIDE { return NS_OK; } + + NS_IMETHOD HandleDrag(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE { return NS_OK; } + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + +protected: + virtual void MouseClicked(nsPresContext* aPresContext, nsGUIEvent* aEvent) MOZ_OVERRIDE; + void DoButtonAction(bool aSmoothScroll); + + void StartRepeat() { + nsRepeatService::GetInstance()->Start(Notify, this); + } + void StopRepeat() { + nsRepeatService::GetInstance()->Stop(Notify, this); + } + void Notify(); + static void Notify(void* aData) { + static_cast<nsScrollbarButtonFrame*>(aData)->Notify(); + } + + int32_t mIncrement; + bool mCursorOnThis; +}; + +#endif diff --git a/layout/xul/base/src/nsScrollbarFrame.cpp b/layout/xul/base/src/nsScrollbarFrame.cpp new file mode 100644 index 000000000..ae8c52875 --- /dev/null +++ b/layout/xul/base/src/nsScrollbarFrame.cpp @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsScrollbarFrame.h" +#include "nsScrollbarButtonFrame.h" +#include "nsGkAtoms.h" +#include "nsIScrollableFrame.h" +#include "nsIScrollbarMediator.h" +#include "mozilla/LookAndFeel.h" +#include "nsThemeConstants.h" +#include "nsRenderingContext.h" + +using namespace mozilla; + +// +// NS_NewScrollbarFrame +// +// Creates a new scrollbar frame and returns it +// +nsIFrame* +NS_NewScrollbarFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsScrollbarFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarFrame) + +NS_QUERYFRAME_HEAD(nsScrollbarFrame) + NS_QUERYFRAME_ENTRY(nsScrollbarFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +void +nsScrollbarFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + // We want to be a reflow root since we use reflows to move the + // slider. Any reflow inside the scrollbar frame will be a reflow to + // move the slider and will thus not change anything outside of the + // scrollbar or change the size of the scrollbar frame. + mState |= NS_FRAME_REFLOW_ROOT; +} + +NS_IMETHODIMP +nsScrollbarFrame::Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + nsresult rv = nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus); + NS_ENSURE_SUCCESS(rv, rv); + + // nsGfxScrollFrame may have told us to shrink to nothing. If so, make sure our + // desired size agrees. + if (aReflowState.availableWidth == 0) { + aDesiredSize.width = 0; + } + if (aReflowState.availableHeight == 0) { + aDesiredSize.height = 0; + } + + return NS_OK; +} + +nsIAtom* +nsScrollbarFrame::GetType() const +{ + return nsGkAtoms::scrollbarFrame; +} + +NS_IMETHODIMP +nsScrollbarFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + + // if the current position changes, notify any nsGfxScrollFrame + // parent we may have + if (aAttribute != nsGkAtoms::curpos) + return rv; + + nsIScrollableFrame* scrollable = do_QueryFrame(GetParent()); + if (!scrollable) + return rv; + + nsCOMPtr<nsIContent> kungFuDeathGrip(mContent); + scrollable->CurPosAttributeChanged(mContent); + return rv; +} + +NS_IMETHODIMP +nsScrollbarFrame::HandlePress(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsScrollbarFrame::HandleMultiplePress(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsScrollbarFrame::HandleDrag(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsScrollbarFrame::HandleRelease(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +void +nsScrollbarFrame::SetScrollbarMediatorContent(nsIContent* aMediator) +{ + mScrollbarMediator = aMediator; +} + +nsIScrollbarMediator* +nsScrollbarFrame::GetScrollbarMediator() +{ + if (!mScrollbarMediator) + return nullptr; + nsIFrame* f = mScrollbarMediator->GetPrimaryFrame(); + + // check if the frame is a scroll frame. If so, get the scrollable frame + // inside it. + nsIScrollableFrame* scrollFrame = do_QueryFrame(f); + if (scrollFrame) { + f = scrollFrame->GetScrolledFrame(); + } + + nsIScrollbarMediator* sbm = do_QueryFrame(f); + return sbm; +} + +NS_IMETHODIMP +nsScrollbarFrame::GetMargin(nsMargin& aMargin) +{ + aMargin.SizeTo(0,0,0,0); + + if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { + nsPresContext* presContext = PresContext(); + nsITheme* theme = presContext->GetTheme(); + if (theme) { + nsIntSize size; + bool isOverridable; + nsRefPtr<nsRenderingContext> rc = + presContext->PresShell()->GetReferenceRenderingContext(); + theme->GetMinimumWidgetSize(rc, this, NS_THEME_SCROLLBAR, &size, + &isOverridable); + if (IsHorizontal()) { + aMargin.top = -presContext->DevPixelsToAppUnits(size.height); + } + else { + if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { + aMargin.right = -presContext->DevPixelsToAppUnits(size.width); + } + else { + aMargin.left = -presContext->DevPixelsToAppUnits(size.width); + } + } + return NS_OK; + } + } + + return nsBox::GetMargin(aMargin); +} diff --git a/layout/xul/base/src/nsScrollbarFrame.h b/layout/xul/base/src/nsScrollbarFrame.h new file mode 100644 index 000000000..1942f6e19 --- /dev/null +++ b/layout/xul/base/src/nsScrollbarFrame.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// nsScrollbarFrame +// + +#ifndef nsScrollbarFrame_h__ +#define nsScrollbarFrame_h__ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +class nsIScrollbarMediator; + +nsIFrame* NS_NewScrollbarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsScrollbarFrame : public nsBoxFrame +{ +public: + nsScrollbarFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsBoxFrame(aShell, aContext), mScrollbarMediator(nullptr) {} + + NS_DECL_QUERYFRAME_TARGET(nsScrollbarFrame) + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE { + return MakeFrameName(NS_LITERAL_STRING("ScrollbarFrame"), aResult); + } +#endif + + // nsIFrame overrides + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + NS_IMETHOD HandlePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) MOZ_OVERRIDE; + + NS_IMETHOD HandleDrag(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + + NS_IMETHOD Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) MOZ_OVERRIDE; + + virtual nsIAtom* GetType() const MOZ_OVERRIDE; + + void SetScrollbarMediatorContent(nsIContent* aMediator); + nsIScrollbarMediator* GetScrollbarMediator(); + + // nsBox methods + + /** + * Treat scrollbars as clipping their children; overflowing children + * will not be allowed to set an overflow rect on this + * frame. This means that when the scroll code decides to hide a + * scrollframe by setting its height or width to zero, that will + * hide the children too. + */ + virtual bool DoesClipChildren() MOZ_OVERRIDE { return true; } + + NS_IMETHOD GetMargin(nsMargin& aMargin) MOZ_OVERRIDE; + +private: + nsCOMPtr<nsIContent> mScrollbarMediator; +}; // class nsScrollbarFrame + +#endif diff --git a/layout/xul/base/src/nsSliderFrame.cpp b/layout/xul/base/src/nsSliderFrame.cpp new file mode 100644 index 000000000..82f3dcd67 --- /dev/null +++ b/layout/xul/base/src/nsSliderFrame.cpp @@ -0,0 +1,1157 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsSliderFrame.h" +#include "nsStyleContext.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsCOMPtr.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsHTMLParts.h" +#include "nsIPresShell.h" +#include "nsCSSRendering.h" +#include "nsEventListenerManager.h" +#include "nsIDOMMouseEvent.h" +#include "nsScrollbarButtonFrame.h" +#include "nsISliderListener.h" +#include "nsIScrollbarMediator.h" +#include "nsScrollbarFrame.h" +#include "nsRepeatService.h" +#include "nsBoxLayoutState.h" +#include "nsSprocketLayout.h" +#include "nsIServiceManager.h" +#include "nsGUIEvent.h" +#include "nsContentUtils.h" +#include "nsLayoutUtils.h" +#include "nsDisplayList.h" +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include <algorithm> + +using namespace mozilla; + +bool nsSliderFrame::gMiddlePref = false; +int32_t nsSliderFrame::gSnapMultiplier; + +// Turn this on if you want to debug slider frames. +#undef DEBUG_SLIDER + +static already_AddRefed<nsIContent> +GetContentOfBox(nsIFrame *aBox) +{ + nsCOMPtr<nsIContent> content = aBox->GetContent(); + return content.forget(); +} + +nsIFrame* +NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSliderFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame) + +nsSliderFrame::nsSliderFrame(nsIPresShell* aPresShell, nsStyleContext* aContext): + nsBoxFrame(aPresShell, aContext), + mCurPos(0), + mChange(0), + mUserChanged(false) +{ +} + +// stop timer +nsSliderFrame::~nsSliderFrame() +{ +} + +void +nsSliderFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + static bool gotPrefs = false; + if (!gotPrefs) { + gotPrefs = true; + + gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition"); + gSnapMultiplier = Preferences::GetInt("slider.snapMultiplier"); + } + + mCurPos = GetCurrentPosition(aContent); +} + +NS_IMETHODIMP +nsSliderFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame); + if (mFrames.IsEmpty()) + RemoveListener(); + + return rv; +} + +NS_IMETHODIMP +nsSliderFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + bool wasEmpty = mFrames.IsEmpty(); + nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); + if (wasEmpty) + AddListener(); + + return rv; +} + +NS_IMETHODIMP +nsSliderFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + // if we have no children and on was added then make sure we add the + // listener + bool wasEmpty = mFrames.IsEmpty(); + nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList); + if (wasEmpty) + AddListener(); + + return rv; +} + +int32_t +nsSliderFrame::GetCurrentPosition(nsIContent* content) +{ + return GetIntegerAttribute(content, nsGkAtoms::curpos, 0); +} + +int32_t +nsSliderFrame::GetMinPosition(nsIContent* content) +{ + return GetIntegerAttribute(content, nsGkAtoms::minpos, 0); +} + +int32_t +nsSliderFrame::GetMaxPosition(nsIContent* content) +{ + return GetIntegerAttribute(content, nsGkAtoms::maxpos, 100); +} + +int32_t +nsSliderFrame::GetIncrement(nsIContent* content) +{ + return GetIntegerAttribute(content, nsGkAtoms::increment, 1); +} + + +int32_t +nsSliderFrame::GetPageIncrement(nsIContent* content) +{ + return GetIntegerAttribute(content, nsGkAtoms::pageincrement, 10); +} + +int32_t +nsSliderFrame::GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue) +{ + nsAutoString value; + content->GetAttr(kNameSpaceID_None, atom, value); + if (!value.IsEmpty()) { + nsresult error; + + // convert it to an integer + defaultValue = value.ToInteger(&error); + } + + return defaultValue; +} + +class nsValueChangedRunnable : public nsRunnable +{ +public: + nsValueChangedRunnable(nsISliderListener* aListener, + nsIAtom* aWhich, + int32_t aValue, + bool aUserChanged) + : mListener(aListener), mWhich(aWhich), + mValue(aValue), mUserChanged(aUserChanged) + {} + + NS_IMETHODIMP Run() + { + return mListener->ValueChanged(nsDependentAtomString(mWhich), + mValue, mUserChanged); + } + + nsCOMPtr<nsISliderListener> mListener; + nsCOMPtr<nsIAtom> mWhich; + int32_t mValue; + bool mUserChanged; +}; + +class nsDragStateChangedRunnable : public nsRunnable +{ +public: + nsDragStateChangedRunnable(nsISliderListener* aListener, + bool aDragBeginning) + : mListener(aListener), + mDragBeginning(aDragBeginning) + {} + + NS_IMETHODIMP Run() + { + return mListener->DragStateChanged(mDragBeginning); + } + + nsCOMPtr<nsISliderListener> mListener; + bool mDragBeginning; +}; + +NS_IMETHODIMP +nsSliderFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + // if the current position changes + if (aAttribute == nsGkAtoms::curpos) { + CurrentPositionChanged(); + } else if (aAttribute == nsGkAtoms::minpos || + aAttribute == nsGkAtoms::maxpos) { + // bounds check it. + + nsIFrame* scrollbarBox = GetScrollbar(); + nsCOMPtr<nsIContent> scrollbar; + scrollbar = GetContentOfBox(scrollbarBox); + int32_t current = GetCurrentPosition(scrollbar); + int32_t min = GetMinPosition(scrollbar); + int32_t max = GetMaxPosition(scrollbar); + + // inform the parent <scale> that the minimum or maximum changed + nsIFrame* parent = GetParent(); + if (parent) { + nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent()); + if (sliderListener) { + nsContentUtils::AddScriptRunner( + new nsValueChangedRunnable(sliderListener, aAttribute, + aAttribute == nsGkAtoms::minpos ? min : max, false)); + } + } + + if (current < min || current > max) + { + if (current < min || max < min) + current = min; + else if (current > max) + current = max; + + // set the new position and notify observers + nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox); + if (scrollbarFrame) { + nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator(); + if (mediator) { + mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), current); + } + } + + nsContentUtils::AddScriptRunner( + new nsSetAttrRunnable(scrollbar, nsGkAtoms::curpos, current)); + } + } + + if (aAttribute == nsGkAtoms::minpos || + aAttribute == nsGkAtoms::maxpos || + aAttribute == nsGkAtoms::pageincrement || + aAttribute == nsGkAtoms::increment) { + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + + return rv; +} + +void +nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (aBuilder->IsForEventDelivery() && isDraggingThumb()) { + // This is EVIL, we shouldn't be messing with event delivery just to get + // thumb mouse drag events to arrive at the slider! + aLists.Outlines()->AppendNewToTop(new (aBuilder) + nsDisplayEventReceiver(aBuilder, this)); + return; + } + + nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); +} + +void +nsSliderFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // if we are too small to have a thumb don't paint it. + nsIFrame* thumb = GetChildBox(); + + if (thumb) { + nsRect thumbRect(thumb->GetRect()); + nsMargin m; + thumb->GetMargin(m); + thumbRect.Inflate(m); + + nsRect crect; + GetClientRect(crect); + + if (crect.width < thumbRect.width || crect.height < thumbRect.height) + return; + } + + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); +} + +NS_IMETHODIMP +nsSliderFrame::DoLayout(nsBoxLayoutState& aState) +{ + // get the thumb should be our only child + nsIFrame* thumbBox = GetChildBox(); + + if (!thumbBox) { + SyncLayout(aState); + return NS_OK; + } + + EnsureOrient(); + +#ifdef DEBUG_LAYOUT + if (mState & NS_STATE_DEBUG_WAS_SET) { + if (mState & NS_STATE_SET_TO_DEBUG) + SetDebug(aState, true); + else + SetDebug(aState, false); + } +#endif + + // get the content area inside our borders + nsRect clientRect; + GetClientRect(clientRect); + + // get the scrollbar + nsIFrame* scrollbarBox = GetScrollbar(); + nsCOMPtr<nsIContent> scrollbar; + scrollbar = GetContentOfBox(scrollbarBox); + + // get the thumb's pref size + nsSize thumbSize = thumbBox->GetPrefSize(aState); + + if (IsHorizontal()) + thumbSize.height = clientRect.height; + else + thumbSize.width = clientRect.width; + + int32_t curPos = GetCurrentPosition(scrollbar); + int32_t minPos = GetMinPosition(scrollbar); + int32_t maxPos = GetMaxPosition(scrollbar); + int32_t pageIncrement = GetPageIncrement(scrollbar); + + maxPos = std::max(minPos, maxPos); + curPos = clamped(curPos, minPos, maxPos); + + nscoord& availableLength = IsHorizontal() ? clientRect.width : clientRect.height; + nscoord& thumbLength = IsHorizontal() ? thumbSize.width : thumbSize.height; + + if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetFlex(aState) > 0) { + float ratio = float(pageIncrement) / float(maxPos - minPos + pageIncrement); + thumbLength = std::max(thumbLength, NSToCoordRound(availableLength * ratio)); + } + + // Round the thumb's length to device pixels. + nsPresContext* presContext = PresContext(); + thumbLength = presContext->DevPixelsToAppUnits( + presContext->AppUnitsToDevPixels(thumbLength)); + + // mRatio translates the thumb position in app units to the value. + mRatio = (minPos != maxPos) ? float(availableLength - thumbLength) / float(maxPos - minPos) : 1; + + // in reverse mode, curpos is reversed such that lower values are to the + // right or bottom and increase leftwards or upwards. In this case, use the + // offset from the end instead of the beginning. + bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, + nsGkAtoms::reverse, eCaseMatters); + nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos); + + // set the thumb's coord to be the current pos * the ratio. + nsRect thumbRect(clientRect.x, clientRect.y, thumbSize.width, thumbSize.height); + int32_t& thumbPos = (IsHorizontal() ? thumbRect.x : thumbRect.y); + thumbPos += NSToCoordRound(pos * mRatio); + + nsRect oldThumbRect(thumbBox->GetRect()); + LayoutChildAt(aState, thumbBox, thumbRect); + + SyncLayout(aState); + + // Redraw only if thumb changed size. + if (!oldThumbRect.IsEqualInterior(thumbRect)) + Redraw(aState); + + return NS_OK; +} + + +NS_IMETHODIMP +nsSliderFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + + // If a web page calls event.preventDefault() we still want to + // scroll when scroll arrow is clicked. See bug 511075. + if (!mContent->IsInNativeAnonymousSubtree() && + nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + nsIFrame* scrollbarBox = GetScrollbar(); + nsCOMPtr<nsIContent> scrollbar; + scrollbar = GetContentOfBox(scrollbarBox); + bool isHorizontal = IsHorizontal(); + + if (isDraggingThumb()) + { + switch (aEvent->message) { + case NS_TOUCH_MOVE: + case NS_MOUSE_MOVE: { + nsPoint eventPoint; + if (!GetEventPoint(aEvent, eventPoint)) { + break; + } + if (mChange) { + // We're in the process of moving the thumb to the mouse, + // but the mouse just moved. Make sure to update our + // destination point. + mDestinationPoint = eventPoint; + StopRepeat(); + StartRepeat(); + break; + } + + nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y; + + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + return NS_OK; + } + + // take our current position and subtract the start location + pos -= mDragStart; + bool isMouseOutsideThumb = false; + if (gSnapMultiplier) { + nsSize thumbSize = thumbFrame->GetSize(); + if (isHorizontal) { + // horizontal scrollbar - check if mouse is above or below thumb + // XXXbz what about looking at the .y of the thumb's rect? Is that + // always zero here? + if (eventPoint.y < -gSnapMultiplier * thumbSize.height || + eventPoint.y > thumbSize.height + + gSnapMultiplier * thumbSize.height) + isMouseOutsideThumb = true; + } + else { + // vertical scrollbar - check if mouse is left or right of thumb + if (eventPoint.x < -gSnapMultiplier * thumbSize.width || + eventPoint.x > thumbSize.width + + gSnapMultiplier * thumbSize.width) + isMouseOutsideThumb = true; + } + } + if (aEvent->eventStructType == NS_TOUCH_EVENT) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } + if (isMouseOutsideThumb) + { + SetCurrentThumbPosition(scrollbar, mThumbStart, false, false); + return NS_OK; + } + + // set it + SetCurrentThumbPosition(scrollbar, pos, false, true); // with snapping + } + break; + + case NS_TOUCH_END: + case NS_MOUSE_BUTTON_UP: + if (ShouldScrollForEvent(aEvent)) { + // stop capturing + AddListener(); + DragThumb(false); + if (mChange) { + StopRepeat(); + mChange = 0; + } + //we MUST call nsFrame HandleEvent for mouse ups to maintain the selection state and capture state. + return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + } + } + + //return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + return NS_OK; + } else if (ShouldScrollToClickForEvent(aEvent)) { + nsPoint eventPoint; + if (!GetEventPoint(aEvent, eventPoint)) { + return NS_OK; + } + nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y; + + // adjust so that the middle of the thumb is placed under the click + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + return NS_OK; + } + nsSize thumbSize = thumbFrame->GetSize(); + nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height; + + // set it + nsWeakFrame weakFrame(this); + // should aMaySnap be true here? + SetCurrentThumbPosition(scrollbar, pos - thumbLength/2, false, false); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + + DragThumb(true); + if (aEvent->eventStructType == NS_TOUCH_EVENT) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } + + if (isHorizontal) + mThumbStart = thumbFrame->GetPosition().x; + else + mThumbStart = thumbFrame->GetPosition().y; + + mDragStart = pos - mThumbStart; + } + + // XXX hack until handle release is actually called in nsframe. +// if (aEvent->message == NS_MOUSE_EXIT_SYNTH || aEvent->message == NS_MOUSE_RIGHT_BUTTON_UP || aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) + // HandleRelease(aPresContext, aEvent, aEventStatus); + + if (aEvent->message == NS_MOUSE_EXIT_SYNTH && mChange) + HandleRelease(aPresContext, aEvent, aEventStatus); + + return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +// Helper function to collect the "scroll to click" metric. Beware of +// caching this, users expect to be able to change the system preference +// and see the browser change its behavior immediately. +bool +nsSliderFrame::GetScrollToClick() +{ + if (GetScrollbar() != this) { + return LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollToClick, false); + } + + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick, + nsGkAtoms::_true, eCaseMatters)) { + return true; + } + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick, + nsGkAtoms::_false, eCaseMatters)) { + return false; + } + +#ifdef XP_MACOSX + return true; +#else + return false; +#endif +} + +nsIFrame* +nsSliderFrame::GetScrollbar() +{ + // if we are in a scrollbar then return the scrollbar's content node + // if we are not then return ours. + nsIFrame* scrollbar; + nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar); + + if (scrollbar == nullptr) + return this; + + return scrollbar->IsBoxFrame() ? scrollbar : this; +} + +void +nsSliderFrame::PageUpDown(nscoord change) +{ + // on a page up or down get our page increment. We get this by getting the scrollbar we are in and + // asking it for the current position and the page increment. If we are not in a scrollbar we will + // get the values from our own node. + nsIFrame* scrollbarBox = GetScrollbar(); + nsCOMPtr<nsIContent> scrollbar; + scrollbar = GetContentOfBox(scrollbarBox); + + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, + nsGkAtoms::reverse, eCaseMatters)) + change = -change; + + nscoord pageIncrement = GetPageIncrement(scrollbar); + int32_t curpos = GetCurrentPosition(scrollbar); + int32_t minpos = GetMinPosition(scrollbar); + int32_t maxpos = GetMaxPosition(scrollbar); + + // get the new position and make sure it is in bounds + int32_t newpos = curpos + change * pageIncrement; + if (newpos < minpos || maxpos < minpos) + newpos = minpos; + else if (newpos > maxpos) + newpos = maxpos; + + SetCurrentPositionInternal(scrollbar, newpos, true); +} + +// called when the current position changed and we need to update the thumb's location +void +nsSliderFrame::CurrentPositionChanged() +{ + nsIFrame* scrollbarBox = GetScrollbar(); + nsCOMPtr<nsIContent> scrollbar; + scrollbar = GetContentOfBox(scrollbarBox); + + // get the current position + int32_t curPos = GetCurrentPosition(scrollbar); + + // do nothing if the position did not change + if (mCurPos == curPos) + return; + + // get our current min and max position from our content node + int32_t minPos = GetMinPosition(scrollbar); + int32_t maxPos = GetMaxPosition(scrollbar); + + maxPos = std::max(minPos, maxPos); + curPos = clamped(curPos, minPos, maxPos); + + // get the thumb's rect + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) + return; // The thumb may stream in asynchronously via XBL. + + nsRect thumbRect = thumbFrame->GetRect(); + + nsRect clientRect; + GetClientRect(clientRect); + + // figure out the new rect + nsRect newThumbRect(thumbRect); + + bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, + nsGkAtoms::reverse, eCaseMatters); + nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos); + + if (IsHorizontal()) + newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio); + else + newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio); + + // set the rect + thumbFrame->SetRect(newThumbRect); + + // Request a repaint of the scrollbar + SchedulePaint(); + + mCurPos = curPos; + + // inform the parent <scale> if it exists that the value changed + nsIFrame* parent = GetParent(); + if (parent) { + nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent()); + if (sliderListener) { + nsContentUtils::AddScriptRunner( + new nsValueChangedRunnable(sliderListener, nsGkAtoms::curpos, mCurPos, mUserChanged)); + } + } +} + +static void UpdateAttribute(nsIContent* aScrollbar, nscoord aNewPos, bool aNotify, bool aIsSmooth) { + nsAutoString str; + str.AppendInt(aNewPos); + + if (aIsSmooth) { + aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false); + } + aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, str, aNotify); + if (aIsSmooth) { + aScrollbar->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false); + } +} + +// Use this function when you want to set the scroll position via the position +// of the scrollbar thumb, e.g. when dragging the slider. This function scrolls +// the content in such a way that thumbRect.x/.y becomes aNewThumbPos. +void +nsSliderFrame::SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewThumbPos, + bool aIsSmooth, bool aMaySnap) +{ + nsRect crect; + GetClientRect(crect); + nscoord offset = IsHorizontal() ? crect.x : crect.y; + int32_t newPos = NSToIntRound((aNewThumbPos - offset) / mRatio); + + if (aMaySnap && mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::snap, + nsGkAtoms::_true, eCaseMatters)) { + // If snap="true", then the slider may only be set to min + (increment * x). + // Otherwise, the slider may be set to any positive integer. + int32_t increment = GetIncrement(aScrollbar); + newPos = NSToIntRound(newPos / float(increment)) * increment; + } + + SetCurrentPosition(aScrollbar, newPos, aIsSmooth); +} + +// Use this function when you know the target scroll position of the scrolled content. +// aNewPos should be passed to this function as a position as if the minpos is 0. +// That is, the minpos will be added to the position by this function. In a reverse +// direction slider, the newpos should be the distance from the end. +void +nsSliderFrame::SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos, + bool aIsSmooth) +{ + // get min and max position from our content node + int32_t minpos = GetMinPosition(aScrollbar); + int32_t maxpos = GetMaxPosition(aScrollbar); + + // in reverse direction sliders, flip the value so that it goes from + // right to left, or bottom to top. + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, + nsGkAtoms::reverse, eCaseMatters)) + aNewPos = maxpos - aNewPos; + else + aNewPos += minpos; + + // get the new position and make sure it is in bounds + if (aNewPos < minpos || maxpos < minpos) + aNewPos = minpos; + else if (aNewPos > maxpos) + aNewPos = maxpos; + + SetCurrentPositionInternal(aScrollbar, aNewPos, aIsSmooth); +} + +void +nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t aNewPos, + bool aIsSmooth) +{ + nsCOMPtr<nsIContent> scrollbar = aScrollbar; + nsIFrame* scrollbarBox = GetScrollbar(); + + mUserChanged = true; + + nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox); + if (scrollbarFrame) { + // See if we have a mediator. + nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator(); + if (mediator) { + nsRefPtr<nsPresContext> context = PresContext(); + nsCOMPtr<nsIContent> content = GetContent(); + mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), aNewPos); + // 'mediator' might be dangling now... + UpdateAttribute(scrollbar, aNewPos, false, aIsSmooth); + nsIFrame* frame = content->GetPrimaryFrame(); + if (frame && frame->GetType() == nsGkAtoms::sliderFrame) { + static_cast<nsSliderFrame*>(frame)->CurrentPositionChanged(); + } + mUserChanged = false; + return; + } + } + + UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth); + mUserChanged = false; + +#ifdef DEBUG_SLIDER + printf("Current Pos=%d\n",aNewPos); +#endif + +} + +nsIAtom* +nsSliderFrame::GetType() const +{ + return nsGkAtoms::sliderFrame; +} + +NS_IMETHODIMP +nsSliderFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + nsresult r = nsBoxFrame::SetInitialChildList(aListID, aChildList); + + AddListener(); + + return r; +} + +nsresult +nsSliderMediator::HandleEvent(nsIDOMEvent* aEvent) +{ + // Only process the event if the thumb is not being dragged. + if (mSlider && !mSlider->isDraggingThumb()) + return mSlider->StartDrag(aEvent); + + return NS_OK; +} + +nsresult +nsSliderFrame::StartDrag(nsIDOMEvent* aEvent) +{ +#ifdef DEBUG_SLIDER + printf("Begin dragging\n"); +#endif + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters)) + return NS_OK; + + nsGUIEvent *event = static_cast<nsGUIEvent*>(aEvent->GetInternalNSEvent()); + + if (!ShouldScrollForEvent(event)) { + return NS_OK; + } + + nsPoint pt; + if (!GetEventPoint(event, pt)) { + return NS_OK; + } + bool isHorizontal = IsHorizontal(); + nscoord pos = isHorizontal ? pt.x : pt.y; + + // If we should scroll-to-click, first place the middle of the slider thumb + // under the mouse. + nsCOMPtr<nsIContent> scrollbar; + nscoord newpos = pos; + bool scrollToClick = ShouldScrollToClickForEvent(event); + if (scrollToClick) { + // adjust so that the middle of the thumb is placed under the click + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + return NS_OK; + } + nsSize thumbSize = thumbFrame->GetSize(); + nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height; + + newpos -= (thumbLength/2); + + nsIFrame* scrollbarBox = GetScrollbar(); + scrollbar = GetContentOfBox(scrollbarBox); + } + + DragThumb(true); + + if (scrollToClick) { + // should aMaySnap be true here? + SetCurrentThumbPosition(scrollbar, newpos, false, false); + } + + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + return NS_OK; + } + + if (isHorizontal) + mThumbStart = thumbFrame->GetPosition().x; + else + mThumbStart = thumbFrame->GetPosition().y; + + mDragStart = pos - mThumbStart; + +#ifdef DEBUG_SLIDER + printf("Pressed mDragStart=%d\n",mDragStart); +#endif + + return NS_OK; +} + +void +nsSliderFrame::DragThumb(bool aGrabMouseEvents) +{ + // inform the parent <scale> that a drag is beginning or ending + nsIFrame* parent = GetParent(); + if (parent) { + nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent()); + if (sliderListener) { + nsContentUtils::AddScriptRunner( + new nsDragStateChangedRunnable(sliderListener, aGrabMouseEvents)); + } + } + + nsIPresShell::SetCapturingContent(aGrabMouseEvents ? GetContent() : nullptr, + aGrabMouseEvents ? CAPTURE_IGNOREALLOWED : 0); +} + +bool +nsSliderFrame::isDraggingThumb() +{ + return (nsIPresShell::GetCapturingContent() == GetContent()); +} + +void +nsSliderFrame::AddListener() +{ + if (!mMediator) { + mMediator = new nsSliderMediator(this); + } + + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + return; + } + thumbFrame->GetContent()-> + AddSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, + false, false); + thumbFrame->GetContent()-> + AddSystemEventListener(NS_LITERAL_STRING("touchstart"), mMediator, + false, false); +} + +void +nsSliderFrame::RemoveListener() +{ + NS_ASSERTION(mMediator, "No listener was ever added!!"); + + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) + return; + + thumbFrame->GetContent()-> + RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, false); +} + +bool +nsSliderFrame::ShouldScrollForEvent(nsGUIEvent* aEvent) +{ + switch (aEvent->message) { + case NS_TOUCH_START: + case NS_TOUCH_END: + return true; + case NS_MOUSE_BUTTON_DOWN: + case NS_MOUSE_BUTTON_UP: { + uint16_t button = static_cast<nsMouseEvent*>(aEvent)->button; + return (button == nsMouseEvent::eLeftButton) || + (button == nsMouseEvent::eMiddleButton && gMiddlePref); + } + default: + return false; + } +} + +bool +nsSliderFrame::ShouldScrollToClickForEvent(nsGUIEvent* aEvent) +{ + if (!ShouldScrollForEvent(aEvent)) { + return false; + } + + if (aEvent->message == NS_TOUCH_START) { + return GetScrollToClick(); + } + + if (aEvent->message != NS_MOUSE_BUTTON_DOWN) { + return false; + } + +#ifdef XP_MACOSX + // On Mac, clicking the scrollbar thumb should never scroll to click. + if (IsEventOverThumb(aEvent)) { + return false; + } +#endif + + nsMouseEvent* mouseEvent = static_cast<nsMouseEvent*>(aEvent); + if (mouseEvent->button == nsMouseEvent::eLeftButton) { +#ifdef XP_MACOSX + bool invertPref = mouseEvent->IsAlt(); +#else + bool invertPref = mouseEvent->IsShift(); +#endif + return GetScrollToClick() != invertPref; + } + + return true; +} + +bool +nsSliderFrame::IsEventOverThumb(nsGUIEvent* aEvent) +{ + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + return false; + } + + nsPoint eventPoint; + if (!GetEventPoint(aEvent, eventPoint)) { + return false; + } + + bool isHorizontal = IsHorizontal(); + nsRect thumbRect = thumbFrame->GetRect(); + nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y; + nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y; + nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost(); + + return eventPos >= thumbStart && eventPos < thumbEnd; +} + +NS_IMETHODIMP +nsSliderFrame::HandlePress(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + if (!ShouldScrollForEvent(aEvent) || ShouldScrollToClickForEvent(aEvent)) { + return NS_OK; + } + + if (IsEventOverThumb(aEvent)) { + return NS_OK; + } + + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) // display:none? + return NS_OK; + + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters)) + return NS_OK; + + nsRect thumbRect = thumbFrame->GetRect(); + + nscoord change = 1; + nsPoint eventPoint; + if (!GetEventPoint(aEvent, eventPoint)) { + return NS_OK; + } + if (IsHorizontal() ? eventPoint.x < thumbRect.x + : eventPoint.y < thumbRect.y) + change = -1; + + mChange = change; + DragThumb(true); + mDestinationPoint = eventPoint; + StartRepeat(); + PageUpDown(change); + return NS_OK; +} + +NS_IMETHODIMP +nsSliderFrame::HandleRelease(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + StopRepeat(); + + return NS_OK; +} + +void +nsSliderFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // tell our mediator if we have one we are gone. + if (mMediator) { + mMediator->SetSlider(nullptr); + mMediator = nullptr; + } + StopRepeat(); + + // call base class Destroy() + nsBoxFrame::DestroyFrom(aDestructRoot); +} + +nsSize +nsSliderFrame::GetPrefSize(nsBoxLayoutState& aState) +{ + EnsureOrient(); + return nsBoxFrame::GetPrefSize(aState); +} + +nsSize +nsSliderFrame::GetMinSize(nsBoxLayoutState& aState) +{ + EnsureOrient(); + + // our min size is just our borders and padding + return nsBox::GetMinSize(aState); +} + +nsSize +nsSliderFrame::GetMaxSize(nsBoxLayoutState& aState) +{ + EnsureOrient(); + return nsBoxFrame::GetMaxSize(aState); +} + +void +nsSliderFrame::EnsureOrient() +{ + nsIFrame* scrollbarBox = GetScrollbar(); + + bool isHorizontal = (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0; + if (isHorizontal) + mState |= NS_STATE_IS_HORIZONTAL; + else + mState &= ~NS_STATE_IS_HORIZONTAL; +} + + +void nsSliderFrame::Notify(void) +{ + bool stop = false; + + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + StopRepeat(); + return; + } + nsRect thumbRect = thumbFrame->GetRect(); + + bool isHorizontal = IsHorizontal(); + + // See if the thumb has moved past our destination point. + // if it has we want to stop. + if (isHorizontal) { + if (mChange < 0) { + if (thumbRect.x < mDestinationPoint.x) + stop = true; + } else { + if (thumbRect.x + thumbRect.width > mDestinationPoint.x) + stop = true; + } + } else { + if (mChange < 0) { + if (thumbRect.y < mDestinationPoint.y) + stop = true; + } else { + if (thumbRect.y + thumbRect.height > mDestinationPoint.y) + stop = true; + } + } + + + if (stop) { + StopRepeat(); + } else { + PageUpDown(mChange); + } +} + +NS_IMPL_ISUPPORTS1(nsSliderMediator, + nsIDOMEventListener) diff --git a/layout/xul/base/src/nsSliderFrame.h b/layout/xul/base/src/nsSliderFrame.h new file mode 100644 index 000000000..49aa6d156 --- /dev/null +++ b/layout/xul/base/src/nsSliderFrame.h @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSliderFrame_h__ +#define nsSliderFrame_h__ + +#include "mozilla/Attributes.h" +#include "nsRepeatService.h" +#include "nsBoxFrame.h" +#include "nsIAtom.h" +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "nsIDOMEventListener.h" + +class nsString; +class nsITimer; +class nsSliderFrame; + +nsIFrame* NS_NewSliderFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsSliderMediator : public nsIDOMEventListener +{ +public: + + NS_DECL_ISUPPORTS + + nsSliderFrame* mSlider; + + nsSliderMediator(nsSliderFrame* aSlider) { mSlider = aSlider; } + virtual ~nsSliderMediator() {} + + virtual void SetSlider(nsSliderFrame* aSlider) { mSlider = aSlider; } + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE; +}; + +class nsSliderFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend class nsSliderMediator; + + nsSliderFrame(nsIPresShell* aShell, nsStyleContext* aContext); + virtual ~nsSliderFrame(); + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE { + return MakeFrameName(NS_LITERAL_STRING("SliderFrame"), aResult); + } +#endif + + virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + // nsIFrame overrides + NS_IMETHOD AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) MOZ_OVERRIDE; + + NS_IMETHOD InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) MOZ_OVERRIDE; + + NS_IMETHOD RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) MOZ_OVERRIDE; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* asPrevInFlow) MOZ_OVERRIDE; + + + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + NS_IMETHOD SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) MOZ_OVERRIDE; + + virtual nsIAtom* GetType() const MOZ_OVERRIDE; + + nsresult StartDrag(nsIDOMEvent* aEvent); + + static int32_t GetCurrentPosition(nsIContent* content); + static int32_t GetMinPosition(nsIContent* content); + static int32_t GetMaxPosition(nsIContent* content); + static int32_t GetIncrement(nsIContent* content); + static int32_t GetPageIncrement(nsIContent* content); + static int32_t GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue); + void EnsureOrient(); + + NS_IMETHOD HandlePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) MOZ_OVERRIDE { return NS_OK; } + + NS_IMETHOD HandleDrag(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE { return NS_OK; } + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + +private: + + bool GetScrollToClick(); + nsIFrame* GetScrollbar(); + bool ShouldScrollForEvent(nsGUIEvent* aEvent); + bool ShouldScrollToClickForEvent(nsGUIEvent* aEvent); + bool IsEventOverThumb(nsGUIEvent* aEvent); + + void PageUpDown(nscoord change); + void SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewPos, bool aIsSmooth, + bool aMaySnap); + void SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos, bool aIsSmooth); + void SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t pos, + bool aIsSmooth); + void CurrentPositionChanged(); + + void DragThumb(bool aGrabMouseEvents); + void AddListener(); + void RemoveListener(); + bool isDraggingThumb(); + + void StartRepeat() { + nsRepeatService::GetInstance()->Start(Notify, this); + } + void StopRepeat() { + nsRepeatService::GetInstance()->Stop(Notify, this); + } + void Notify(); + static void Notify(void* aData) { + (static_cast<nsSliderFrame*>(aData))->Notify(); + } + + nsPoint mDestinationPoint; + nsRefPtr<nsSliderMediator> mMediator; + + float mRatio; + + nscoord mDragStart; + nscoord mThumbStart; + + int32_t mCurPos; + + nscoord mChange; + + // true if an attribute change has been caused by the user manipulating the + // slider. This allows notifications to tell how a slider's current position + // was changed. + bool mUserChanged; + + static bool gMiddlePref; + static int32_t gSnapMultiplier; +}; // class nsSliderFrame + +#endif diff --git a/layout/xul/base/src/nsSplitterFrame.cpp b/layout/xul/base/src/nsSplitterFrame.cpp new file mode 100644 index 000000000..8d3659d61 --- /dev/null +++ b/layout/xul/base/src/nsSplitterFrame.cpp @@ -0,0 +1,1051 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsSplitterFrame.h" +#include "nsGkAtoms.h" +#include "nsIDOMElement.h" +#include "nsIDOMXULElement.h" +#include "nsPresContext.h" +#include "nsRenderingContext.h" +#include "nsIDocument.h" +#include "nsINameSpaceManager.h" +#include "nsScrollbarButtonFrame.h" +#include "nsIDOMEventListener.h" +#include "nsIDOMMouseEvent.h" +#include "nsIPresShell.h" +#include "nsFrameList.h" +#include "nsHTMLParts.h" +#include "nsStyleContext.h" +#include "nsBoxLayoutState.h" +#include "nsIServiceManager.h" +#include "nsContainerFrame.h" +#include "nsGUIEvent.h" +#include "nsAutoPtr.h" +#include "nsContentCID.h" +#include "nsStyleSet.h" +#include "nsLayoutUtils.h" +#include "nsDisplayList.h" +#include "nsContentUtils.h" +#include "mozilla/dom/Element.h" + +class nsSplitterInfo { +public: + nscoord min; + nscoord max; + nscoord current; + nscoord changed; + nsCOMPtr<nsIContent> childElem; + int32_t flex; + int32_t index; +}; + +class nsSplitterFrameInner : public nsIDOMEventListener +{ +public: + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + nsSplitterFrameInner(nsSplitterFrame* aSplitter) + { + mOuter = aSplitter; + mPressed = false; + } + virtual ~nsSplitterFrameInner(); + + void Disconnect() { mOuter = nullptr; } + + nsresult MouseDown(nsIDOMEvent* aMouseEvent); + nsresult MouseUp(nsIDOMEvent* aMouseEvent); + nsresult MouseMove(nsIDOMEvent* aMouseEvent); + + void MouseDrag(nsPresContext* aPresContext, nsGUIEvent* aEvent); + void MouseUp(nsPresContext* aPresContext, nsGUIEvent* aEvent); + + void AdjustChildren(nsPresContext* aPresContext); + void AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, int32_t aCount, bool aIsHorizontal); + + void AddRemoveSpace(nscoord aDiff, + nsSplitterInfo* aChildInfos, + int32_t aCount, + int32_t& aSpaceLeft); + + void ResizeChildTo(nsPresContext* aPresContext, + nscoord& aDiff, + nsSplitterInfo* aChildrenBeforeInfos, + nsSplitterInfo* aChildrenAfterInfos, + int32_t aChildrenBeforeCount, + int32_t aChildrenAfterCount, + bool aBounded); + + void UpdateState(); + + void AddListener(nsPresContext* aPresContext); + void RemoveListener(); + + enum ResizeType { Closest, Farthest, Flex, Grow }; + enum State { Open, CollapsedBefore, CollapsedAfter, Dragging }; + enum CollapseDirection { Before, After }; + + ResizeType GetResizeBefore(); + ResizeType GetResizeAfter(); + State GetState(); + + void Reverse(nsSplitterInfo*& aIndexes, int32_t aCount); + bool SupportsCollapseDirection(CollapseDirection aDirection); + + void EnsureOrient(); + void SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize); + + nsSplitterFrame* mOuter; + bool mDidDrag; + nscoord mDragStart; + nscoord mCurrentPos; + nsIFrame* mParentBox; + bool mPressed; + nsSplitterInfo* mChildInfosBefore; + nsSplitterInfo* mChildInfosAfter; + int32_t mChildInfosBeforeCount; + int32_t mChildInfosAfterCount; + State mState; + nscoord mSplitterPos; + bool mDragging; + +}; + +NS_IMPL_ISUPPORTS1(nsSplitterFrameInner, nsIDOMEventListener) + +nsSplitterFrameInner::ResizeType +nsSplitterFrameInner::GetResizeBefore() +{ + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::farthest, &nsGkAtoms::flex, nullptr}; + switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::resizebefore, + strings, eCaseMatters)) { + case 0: return Farthest; + case 1: return Flex; + } + return Closest; +} + +nsSplitterFrameInner::~nsSplitterFrameInner() +{ + delete[] mChildInfosBefore; + delete[] mChildInfosAfter; +} + +nsSplitterFrameInner::ResizeType +nsSplitterFrameInner::GetResizeAfter() +{ + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::farthest, &nsGkAtoms::flex, &nsGkAtoms::grow, nullptr}; + switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::resizeafter, + strings, eCaseMatters)) { + case 0: return Farthest; + case 1: return Flex; + case 2: return Grow; + } + return Closest; +} + +nsSplitterFrameInner::State +nsSplitterFrameInner::GetState() +{ + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::dragging, &nsGkAtoms::collapsed, nullptr}; + static nsIContent::AttrValuesArray strings_substate[] = + {&nsGkAtoms::before, &nsGkAtoms::after, nullptr}; + switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::state, + strings, eCaseMatters)) { + case 0: return Dragging; + case 1: + switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::substate, + strings_substate, + eCaseMatters)) { + case 0: return CollapsedBefore; + case 1: return CollapsedAfter; + default: + if (SupportsCollapseDirection(After)) + return CollapsedAfter; + return CollapsedBefore; + } + } + return Open; +} + +// +// NS_NewSplitterFrame +// +// Creates a new Toolbar frame and returns it +// +nsIFrame* +NS_NewSplitterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSplitterFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame) + +nsSplitterFrame::nsSplitterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +: nsBoxFrame(aPresShell, aContext), + mInner(0) +{ +} + +void +nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (mInner) { + mInner->RemoveListener(); + mInner->Disconnect(); + mInner->Release(); + mInner = nullptr; + } + nsBoxFrame::DestroyFrom(aDestructRoot); +} + + +NS_IMETHODIMP +nsSplitterFrame::GetCursor(const nsPoint& aPoint, + nsIFrame::Cursor& aCursor) +{ + return nsBoxFrame::GetCursor(aPoint, aCursor); + + /* + if (IsHorizontal()) + aCursor = NS_STYLE_CURSOR_N_RESIZE; + else + aCursor = NS_STYLE_CURSOR_W_RESIZE; + + return NS_OK; + */ +} + +NS_IMETHODIMP +nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + // if the alignment changed. Let the grippy know + if (aAttribute == nsGkAtoms::align) { + // tell the slider its attribute changed so it can + // update itself + nsIFrame* grippy = nullptr; + nsScrollbarButtonFrame::GetChildWithTag(PresContext(), nsGkAtoms::grippy, this, grippy); + if (grippy) + grippy->AttributeChanged(aNameSpaceID, aAttribute, aModType); + } else if (aAttribute == nsGkAtoms::state) { + mInner->UpdateState(); + } + + return rv; +} + +/** + * Initialize us. If we are in a box get our alignment so we know what direction we are + */ +void +nsSplitterFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + MOZ_ASSERT(!mInner); + mInner = new nsSplitterFrameInner(this); + + mInner->AddRef(); + mInner->mChildInfosAfter = nullptr; + mInner->mChildInfosBefore = nullptr; + mInner->mState = nsSplitterFrameInner::Open; + mInner->mDragging = false; + + // determine orientation of parent, and if vertical, set orient to vertical + // on splitter content, then re-resolve style + // XXXbz this is pretty messed up, since this can change whether we should + // have a frame at all. This really needs a better solution. + if (aParent && aParent->IsBoxFrame()) { + if (!aParent->IsHorizontal()) { + if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None, + nsGkAtoms::orient)) { + aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, + NS_LITERAL_STRING("vertical"), false); + nsStyleContext* parentStyleContext = StyleContext()->GetParent(); + nsRefPtr<nsStyleContext> newContext = PresContext()->StyleSet()-> + ResolveStyleFor(aContent->AsElement(), parentStyleContext); + SetStyleContextWithoutNotification(newContext); + } + } + } + + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + mInner->mState = nsSplitterFrameInner::Open; + mInner->AddListener(PresContext()); + mInner->mParentBox = nullptr; +} + +NS_IMETHODIMP +nsSplitterFrame::DoLayout(nsBoxLayoutState& aState) +{ + if (GetStateBits() & NS_FRAME_FIRST_REFLOW) + { + mInner->mParentBox = GetParentBox(); + mInner->UpdateState(); + } + + return nsBoxFrame::DoLayout(aState); +} + + +void +nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal) +{ + nsIFrame* box = GetParentBox(); + if (box) { + aIsHorizontal = !box->IsHorizontal(); + } + else + nsBoxFrame::GetInitialOrientation(aIsHorizontal); +} + +NS_IMETHODIMP +nsSplitterFrame::HandlePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleRelease(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +void +nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); + + // if the mouse is captured always return us as the frame. + if (mInner->mDragging) + { + // XXX It's probably better not to check visibility here, right? + aLists.Outlines()->AppendNewToTop(new (aBuilder) + nsDisplayEventReceiver(aBuilder, this)); + return; + } +} + +NS_IMETHODIMP +nsSplitterFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + nsWeakFrame weakFrame(this); + nsRefPtr<nsSplitterFrameInner> kungFuDeathGrip(mInner); + switch (aEvent->message) { + case NS_MOUSE_MOVE: + mInner->MouseDrag(aPresContext, aEvent); + break; + + case NS_MOUSE_BUTTON_UP: + if (aEvent->eventStructType == NS_MOUSE_EVENT && + static_cast<nsMouseEvent*>(aEvent)->button == + nsMouseEvent::eLeftButton) { + mInner->MouseUp(aPresContext, aEvent); + } + break; + } + + NS_ENSURE_STATE(weakFrame.IsAlive()); + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +void +nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext, nsGUIEvent* aEvent) +{ + if (mDragging && mOuter) { + AdjustChildren(aPresContext); + AddListener(aPresContext); + nsIPresShell::SetCapturingContent(nullptr, 0); // XXXndeakin is this needed? + mDragging = false; + State newState = GetState(); + // if the state is dragging then make it Open. + if (newState == Dragging) + mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, EmptyString(), true); + + mPressed = false; + + // if we dragged then fire a command event. + if (mDidDrag) { + nsCOMPtr<nsIDOMXULElement> element = do_QueryInterface(mOuter->GetContent()); + element->DoCommand(); + } + + //printf("MouseUp\n"); + } + + delete[] mChildInfosBefore; + delete[] mChildInfosAfter; + mChildInfosBefore = nullptr; + mChildInfosAfter = nullptr; + mChildInfosBeforeCount = 0; + mChildInfosAfterCount = 0; +} + +void +nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext, nsGUIEvent* aEvent) +{ + if (mDragging && mOuter) { + + //printf("Dragging\n"); + + bool isHorizontal = !mOuter->IsHorizontal(); + // convert coord to pixels + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, + mParentBox); + nscoord pos = isHorizontal ? pt.x : pt.y; + + // mDragStart is in frame coordinates + nscoord start = mDragStart; + + // take our current position and subtract the start location + pos -= start; + + //printf("Diff=%d\n", pos); + + ResizeType resizeAfter = GetResizeAfter(); + + bool bounded; + + if (resizeAfter == nsSplitterFrameInner::Grow) + bounded = false; + else + bounded = true; + + int i; + for (i=0; i < mChildInfosBeforeCount; i++) + mChildInfosBefore[i].changed = mChildInfosBefore[i].current; + + for (i=0; i < mChildInfosAfterCount; i++) + mChildInfosAfter[i].changed = mChildInfosAfter[i].current; + + nscoord oldPos = pos; + + ResizeChildTo(aPresContext, pos, mChildInfosBefore, mChildInfosAfter, mChildInfosBeforeCount, mChildInfosAfterCount, bounded); + + State currentState = GetState(); + bool supportsBefore = SupportsCollapseDirection(Before); + bool supportsAfter = SupportsCollapseDirection(After); + + const bool isRTL = mOuter->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + bool pastEnd = oldPos > 0 && oldPos > pos; + bool pastBegin = oldPos < 0 && oldPos < pos; + if (isRTL) { + // Swap the boundary checks in RTL mode + bool tmp = pastEnd; + pastEnd = pastBegin; + pastBegin = tmp; + } + const bool isCollapsedBefore = pastBegin && supportsBefore; + const bool isCollapsedAfter = pastEnd && supportsAfter; + + // if we are in a collapsed position + if (isCollapsedBefore || isCollapsedAfter) + { + // and we are not collapsed then collapse + if (currentState == Dragging) { + if (pastEnd) + { + //printf("Collapse right\n"); + if (supportsAfter) + { + nsCOMPtr<nsIContent> outer = mOuter->mContent; + outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, + NS_LITERAL_STRING("after"), + true); + outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, + NS_LITERAL_STRING("collapsed"), + true); + } + + } else if (pastBegin) + { + //printf("Collapse left\n"); + if (supportsBefore) + { + nsCOMPtr<nsIContent> outer = mOuter->mContent; + outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, + NS_LITERAL_STRING("before"), + true); + outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, + NS_LITERAL_STRING("collapsed"), + true); + } + } + } + } else { + // if we are not in a collapsed position and we are not dragging make sure + // we are dragging. + if (currentState != Dragging) + mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, NS_LITERAL_STRING("dragging"), true); + AdjustChildren(aPresContext); + } + + mDidDrag = true; + } +} + +void +nsSplitterFrameInner::AddListener(nsPresContext* aPresContext) +{ + mOuter->GetContent()-> + AddEventListener(NS_LITERAL_STRING("mouseup"), this, false, false); + mOuter->GetContent()-> + AddEventListener(NS_LITERAL_STRING("mousedown"), this, false, false); + mOuter->GetContent()-> + AddEventListener(NS_LITERAL_STRING("mousemove"), this, false, false); + mOuter->GetContent()-> + AddEventListener(NS_LITERAL_STRING("mouseout"), this, false, false); +} + +void +nsSplitterFrameInner::RemoveListener() +{ + ENSURE_TRUE(mOuter); + mOuter->GetContent()-> + RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, false); + mOuter->GetContent()-> + RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, false); + mOuter->GetContent()-> + RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, false); + mOuter->GetContent()-> + RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, false); +} + +nsresult +nsSplitterFrameInner::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString eventType; + aEvent->GetType(eventType); + if (eventType.EqualsLiteral("mouseup")) + return MouseUp(aEvent); + if (eventType.EqualsLiteral("mousedown")) + return MouseDown(aEvent); + if (eventType.EqualsLiteral("mousemove") || + eventType.EqualsLiteral("mouseout")) + return MouseMove(aEvent); + + NS_ABORT(); + return NS_OK; +} + +nsresult +nsSplitterFrameInner::MouseUp(nsIDOMEvent* aMouseEvent) +{ + NS_ENSURE_TRUE(mOuter, NS_OK); + mPressed = false; + + nsIPresShell::SetCapturingContent(nullptr, 0); + + return NS_OK; +} + +nsresult +nsSplitterFrameInner::MouseDown(nsIDOMEvent* aMouseEvent) +{ + NS_ENSURE_TRUE(mOuter, NS_OK); + nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aMouseEvent)); + if (!mouseEvent) + return NS_OK; + + uint16_t button = 0; + mouseEvent->GetButton(&button); + + // only if left button + if (button != 0) + return NS_OK; + + if (mOuter->GetContent()-> + AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters)) + return NS_OK; + + mParentBox = mOuter->GetParentBox(); + if (!mParentBox) + return NS_OK; + + // get our index + nsPresContext* outerPresContext = mOuter->PresContext(); + const nsFrameList& siblingList(mParentBox->PrincipalChildList()); + int32_t childIndex = siblingList.IndexOf(mOuter); + // if it's 0 (or not found) then stop right here. + // It might be not found if we're not in the parent's primary frame list. + if (childIndex <= 0) + return NS_OK; + + int32_t childCount = siblingList.GetLength(); + // if it's the last index then we need to allow for resizeafter="grow" + if (childIndex == childCount - 1 && GetResizeAfter() != Grow) + return NS_OK; + + nsRefPtr<nsRenderingContext> rc = + outerPresContext->PresShell()->GetReferenceRenderingContext(); + NS_ENSURE_TRUE(rc, NS_ERROR_FAILURE); + nsBoxLayoutState state(outerPresContext, rc); + mCurrentPos = 0; + mPressed = true; + + mDidDrag = false; + + EnsureOrient(); + bool isHorizontal = !mOuter->IsHorizontal(); + + ResizeType resizeBefore = GetResizeBefore(); + ResizeType resizeAfter = GetResizeAfter(); + + delete[] mChildInfosBefore; + delete[] mChildInfosAfter; + mChildInfosBefore = new nsSplitterInfo[childCount]; + mChildInfosAfter = new nsSplitterInfo[childCount]; + + // create info 2 lists. One of the children before us and one after. + int32_t count = 0; + mChildInfosBeforeCount = 0; + mChildInfosAfterCount = 0; + + nsIFrame* childBox = mParentBox->GetChildBox(); + + while (nullptr != childBox) + { + nsIContent* content = childBox->GetContent(); + nsIDocument* doc = content->OwnerDoc(); + int32_t dummy; + nsIAtom* atom = doc->BindingManager()->ResolveTag(content, &dummy); + + // skip over any splitters + if (atom != nsGkAtoms::splitter) { + nsSize prefSize = childBox->GetPrefSize(state); + nsSize minSize = childBox->GetMinSize(state); + nsSize maxSize = nsBox::BoundsCheckMinMax(minSize, childBox->GetMaxSize(state)); + prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize); + + mOuter->AddMargin(childBox, minSize); + mOuter->AddMargin(childBox, prefSize); + mOuter->AddMargin(childBox, maxSize); + + nscoord flex = childBox->GetFlex(state); + + nsMargin margin(0,0,0,0); + childBox->GetMargin(margin); + nsRect r(childBox->GetRect()); + r.Inflate(margin); + + // We need to check for hidden attribute too, since treecols with + // the hidden="true" attribute are not really hidden, just collapsed + if (!content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::fixed, + nsGkAtoms::_true, eCaseMatters) && + !content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) { + if (count < childIndex && (resizeBefore != Flex || flex > 0)) { + mChildInfosBefore[mChildInfosBeforeCount].childElem = content; + mChildInfosBefore[mChildInfosBeforeCount].min = isHorizontal ? minSize.width : minSize.height; + mChildInfosBefore[mChildInfosBeforeCount].max = isHorizontal ? maxSize.width : maxSize.height; + mChildInfosBefore[mChildInfosBeforeCount].current = isHorizontal ? r.width : r.height; + mChildInfosBefore[mChildInfosBeforeCount].flex = flex; + mChildInfosBefore[mChildInfosBeforeCount].index = count; + mChildInfosBefore[mChildInfosBeforeCount].changed = mChildInfosBefore[mChildInfosBeforeCount].current; + mChildInfosBeforeCount++; + } else if (count > childIndex && (resizeAfter != Flex || flex > 0)) { + mChildInfosAfter[mChildInfosAfterCount].childElem = content; + mChildInfosAfter[mChildInfosAfterCount].min = isHorizontal ? minSize.width : minSize.height; + mChildInfosAfter[mChildInfosAfterCount].max = isHorizontal ? maxSize.width : maxSize.height; + mChildInfosAfter[mChildInfosAfterCount].current = isHorizontal ? r.width : r.height; + mChildInfosAfter[mChildInfosAfterCount].flex = flex; + mChildInfosAfter[mChildInfosAfterCount].index = count; + mChildInfosAfter[mChildInfosAfterCount].changed = mChildInfosAfter[mChildInfosAfterCount].current; + mChildInfosAfterCount++; + } + } + } + + childBox = childBox->GetNextBox(); + count++; + } + + if (!mParentBox->IsNormalDirection()) { + // The before array is really the after array, and the order needs to be reversed. + // First reverse both arrays. + Reverse(mChildInfosBefore, mChildInfosBeforeCount); + Reverse(mChildInfosAfter, mChildInfosAfterCount); + + // Now swap the two arrays. + nscoord newAfterCount = mChildInfosBeforeCount; + mChildInfosBeforeCount = mChildInfosAfterCount; + mChildInfosAfterCount = newAfterCount; + nsSplitterInfo* temp = mChildInfosAfter; + mChildInfosAfter = mChildInfosBefore; + mChildInfosBefore = temp; + } + + // if resizebefore is not Farthest, reverse the list because the first child + // in the list is the farthest, and we want the first child to be the closest. + if (resizeBefore != Farthest) + Reverse(mChildInfosBefore, mChildInfosBeforeCount); + + // if the resizeafter is the Farthest we must reverse the list because the first child in the list + // is the closest we want the first child to be the Farthest. + if (resizeAfter == Farthest) + Reverse(mChildInfosAfter, mChildInfosAfterCount); + + // grow only applys to the children after. If grow is set then no space should be taken out of any children after + // us. To do this we just set the size of that list to be 0. + if (resizeAfter == Grow) + mChildInfosAfterCount = 0; + + int32_t c; + nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, + mParentBox); + if (isHorizontal) { + c = pt.x; + mSplitterPos = mOuter->mRect.x; + } else { + c = pt.y; + mSplitterPos = mOuter->mRect.y; + } + + mDragStart = c; + + //printf("Pressed mDragStart=%d\n",mDragStart); + + nsIPresShell::SetCapturingContent(mOuter->GetContent(), CAPTURE_IGNOREALLOWED); + + return NS_OK; +} + +nsresult +nsSplitterFrameInner::MouseMove(nsIDOMEvent* aMouseEvent) +{ + NS_ENSURE_TRUE(mOuter, NS_OK); + if (!mPressed) + return NS_OK; + + if (mDragging) + return NS_OK; + + nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this); + mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, + NS_LITERAL_STRING("dragging"), true); + + RemoveListener(); + mDragging = true; + + return NS_OK; +} + +void +nsSplitterFrameInner::Reverse(nsSplitterInfo*& aChildInfos, int32_t aCount) +{ + nsSplitterInfo* infos = new nsSplitterInfo[aCount]; + + for (int i=0; i < aCount; i++) + infos[i] = aChildInfos[aCount - 1 - i]; + + delete[] aChildInfos; + aChildInfos = infos; +} + +bool +nsSplitterFrameInner::SupportsCollapseDirection +( + nsSplitterFrameInner::CollapseDirection aDirection +) +{ + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::before, &nsGkAtoms::after, &nsGkAtoms::both, nullptr}; + + switch (mOuter->mContent->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::collapse, + strings, eCaseMatters)) { + case 0: + return (aDirection == Before); + case 1: + return (aDirection == After); + case 2: + return true; + } + + return false; +} + +void +nsSplitterFrameInner::UpdateState() +{ + // State Transitions: + // Open -> Dragging + // Open -> CollapsedBefore + // Open -> CollapsedAfter + // CollapsedBefore -> Open + // CollapsedBefore -> Dragging + // CollapsedAfter -> Open + // CollapsedAfter -> Dragging + // Dragging -> Open + // Dragging -> CollapsedBefore (auto collapse) + // Dragging -> CollapsedAfter (auto collapse) + + State newState = GetState(); + + if (newState == mState) { + // No change. + return; + } + + if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) && + mOuter->GetParent()->IsBoxFrame()) { + // Find the splitter's immediate sibling. + nsIFrame* splitterSibling; + if (newState == CollapsedBefore || mState == CollapsedBefore) { + splitterSibling = mOuter->GetPrevSibling(); + } else { + splitterSibling = mOuter->GetNextSibling(); + } + + if (splitterSibling) { + nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent(); + if (sibling) { + if (mState == CollapsedBefore || mState == CollapsedAfter) { + // CollapsedBefore -> Open + // CollapsedBefore -> Dragging + // CollapsedAfter -> Open + // CollapsedAfter -> Dragging + nsContentUtils::AddScriptRunner( + new nsUnsetAttrRunnable(sibling, nsGkAtoms::collapsed)); + } else if ((mState == Open || mState == Dragging) + && (newState == CollapsedBefore || + newState == CollapsedAfter)) { + // Open -> CollapsedBefore / CollapsedAfter + // Dragging -> CollapsedBefore / CollapsedAfter + nsContentUtils::AddScriptRunner( + new nsSetAttrRunnable(sibling, nsGkAtoms::collapsed, + NS_LITERAL_STRING("true"))); + } + } + } + } + mState = newState; +} + +void +nsSplitterFrameInner::EnsureOrient() +{ + bool isHorizontal = !(mParentBox->GetStateBits() & NS_STATE_IS_HORIZONTAL); + if (isHorizontal) + mOuter->mState |= NS_STATE_IS_HORIZONTAL; + else + mOuter->mState &= ~NS_STATE_IS_HORIZONTAL; +} + +void +nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) +{ + EnsureOrient(); + bool isHorizontal = !mOuter->IsHorizontal(); + + AdjustChildren(aPresContext, mChildInfosBefore, mChildInfosBeforeCount, isHorizontal); + AdjustChildren(aPresContext, mChildInfosAfter, mChildInfosAfterCount, isHorizontal); +} + +static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox, nsIContent* aContent) +{ + nsIFrame* childBox = aParentBox->GetChildBox(); + + while (nullptr != childBox) { + if (childBox->GetContent() == aContent) { + return childBox; + } + childBox = childBox->GetNextBox(); + } + return nullptr; +} + +void +nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, int32_t aCount, bool aIsHorizontal) +{ + ///printf("------- AdjustChildren------\n"); + + nsBoxLayoutState state(aPresContext); + + nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); + + // first set all the widths. + nsIFrame* child = mOuter->GetChildBox(); + while(child) + { + SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr); + child = child->GetNextBox(); + } + + // now set our changed widths. + for (int i=0; i < aCount; i++) + { + nscoord pref = aChildInfos[i].changed; + nsIFrame* childBox = GetChildBoxForContent(mParentBox, aChildInfos[i].childElem); + + if (childBox) { + SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref); + } + } +} + +void +nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize) +{ + nsRect rect(aChildBox->GetRect()); + nscoord pref = 0; + + if (!aSize) + { + if (aIsHorizontal) + pref = rect.width; + else + pref = rect.height; + } else { + pref = *aSize; + } + + nsMargin margin(0,0,0,0); + aChildBox->GetMargin(margin); + + nsCOMPtr<nsIAtom> attribute; + + if (aIsHorizontal) { + pref -= (margin.left + margin.right); + attribute = nsGkAtoms::width; + } else { + pref -= (margin.top + margin.bottom); + attribute = nsGkAtoms::height; + } + + nsIContent* content = aChildBox->GetContent(); + + // set its preferred size. + nsAutoString prefValue; + prefValue.AppendInt(pref/aOnePixel); + if (content->AttrValueIs(kNameSpaceID_None, attribute, + prefValue, eCaseMatters)) + return; + + nsWeakFrame weakBox(aChildBox); + content->SetAttr(kNameSpaceID_None, attribute, prefValue, true); + ENSURE_TRUE(weakBox.IsAlive()); + aState.PresShell()->FrameNeedsReflow(aChildBox, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); +} + + +void +nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff, + nsSplitterInfo* aChildInfos, + int32_t aCount, + int32_t& aSpaceLeft) +{ + aSpaceLeft = 0; + + for (int i=0; i < aCount; i++) { + nscoord min = aChildInfos[i].min; + nscoord max = aChildInfos[i].max; + nscoord& c = aChildInfos[i].changed; + + // figure our how much space to add or remove + if (c + aDiff < min) { + aDiff += (c - min); + c = min; + } else if (c + aDiff > max) { + aDiff -= (max - c); + c = max; + } else { + c += aDiff; + aDiff = 0; + } + + // there is not space left? We are done + if (aDiff == 0) + break; + } + + aSpaceLeft = aDiff; +} + +/** + * Ok if we want to resize a child we will know the actual size in pixels we want it to be. + * This is not the preferred size. But they only way we can change a child is my manipulating its + * preferred size. So give the actual pixel size this return method will return figure out the preferred + * size and set it. + */ + +void +nsSplitterFrameInner::ResizeChildTo(nsPresContext* aPresContext, + nscoord& aDiff, + nsSplitterInfo* aChildrenBeforeInfos, + nsSplitterInfo* aChildrenAfterInfos, + int32_t aChildrenBeforeCount, + int32_t aChildrenAfterCount, + bool aBounded) +{ + nscoord spaceLeft; + AddRemoveSpace(aDiff, aChildrenBeforeInfos,aChildrenBeforeCount,spaceLeft); + + // if there is any space left over remove it from the dif we were originally given + aDiff -= spaceLeft; + AddRemoveSpace(-aDiff, aChildrenAfterInfos,aChildrenAfterCount,spaceLeft); + + if (spaceLeft != 0) { + if (aBounded) { + aDiff += spaceLeft; + AddRemoveSpace(spaceLeft, aChildrenBeforeInfos,aChildrenBeforeCount,spaceLeft); + } else { + spaceLeft = 0; + } + } +} diff --git a/layout/xul/base/src/nsSplitterFrame.h b/layout/xul/base/src/nsSplitterFrame.h new file mode 100644 index 000000000..c2bb828b1 --- /dev/null +++ b/layout/xul/base/src/nsSplitterFrame.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// nsSplitterFrame +// + +#ifndef nsSplitterFrame_h__ +#define nsSplitterFrame_h__ + + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +class nsSplitterFrameInner; + +nsIFrame* NS_NewSplitterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsSplitterFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + nsSplitterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE { + return MakeFrameName(NS_LITERAL_STRING("SplitterFrame"), aResult); + } +#endif + + // nsIFrame overrides + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + + NS_IMETHOD GetCursor(const nsPoint& aPoint, + nsIFrame::Cursor& aCursor) MOZ_OVERRIDE; + + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + NS_IMETHOD HandlePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) MOZ_OVERRIDE; + + NS_IMETHOD HandleDrag(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + nsGUIEvent * aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + virtual void GetInitialOrientation(bool& aIsHorizontal) MOZ_OVERRIDE; + +private: + + friend class nsSplitterFrameInner; + nsSplitterFrameInner* mInner; + +}; // class nsSplitterFrame + +#endif diff --git a/layout/xul/base/src/nsSprocketLayout.cpp b/layout/xul/base/src/nsSprocketLayout.cpp new file mode 100644 index 000000000..3c4224083 --- /dev/null +++ b/layout/xul/base/src/nsSprocketLayout.cpp @@ -0,0 +1,1643 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsBoxLayoutState.h" +#include "nsSprocketLayout.h" +#include "nsPresContext.h" +#include "nsCOMPtr.h" +#include "nsIContent.h" +#include "nsIPresShell.h" +#include "nsContainerFrame.h" +#include "nsBoxFrame.h" +#include "StackArena.h" +#include "mozilla/Likely.h" +#include <algorithm> + +nsBoxLayout* nsSprocketLayout::gInstance = nullptr; + +//#define DEBUG_GROW + +#define DEBUG_SPRING_SIZE 8 +#define DEBUG_BORDER_SIZE 2 +#define COIL_SIZE 8 + + +nsresult +NS_NewSprocketLayout( nsIPresShell* aPresShell, nsCOMPtr<nsBoxLayout>& aNewLayout) +{ + if (!nsSprocketLayout::gInstance) { + nsSprocketLayout::gInstance = new nsSprocketLayout(); + NS_IF_ADDREF(nsSprocketLayout::gInstance); + } + // we have not instance variables so just return our static one. + aNewLayout = nsSprocketLayout::gInstance; + return NS_OK; +} + +/*static*/ void +nsSprocketLayout::Shutdown() +{ + NS_IF_RELEASE(gInstance); +} + +nsSprocketLayout::nsSprocketLayout() +{ +} + +bool +nsSprocketLayout::IsHorizontal(nsIFrame* aBox) +{ + return (aBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0; +} + +void +nsSprocketLayout::GetFrameState(nsIFrame* aBox, nsFrameState& aState) +{ + aState = aBox->GetStateBits(); +} + +static uint8_t +GetFrameDirection(nsIFrame* aBox) +{ + return aBox->StyleVisibility()->mDirection; +} + +static void +HandleBoxPack(nsIFrame* aBox, const nsFrameState& aFrameState, nscoord& aX, nscoord& aY, + const nsRect& aOriginalRect, const nsRect& aClientRect) +{ + // In the normal direction we lay out our kids in the positive direction (e.g., |x| will get + // bigger for a horizontal box, and |y| will get bigger for a vertical box). In the reverse + // direction, the opposite is true. We'll be laying out each child at a smaller |x| or + // |y|. + uint8_t frameDirection = GetFrameDirection(aBox); + + if (aFrameState & NS_STATE_IS_HORIZONTAL) { + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) { + // The normal direction. |x| increases as we move through our children. + aX = aClientRect.x; + } + else { + // The reverse direction. |x| decreases as we move through our children. + aX = aClientRect.x + aOriginalRect.width; + } + // |y| is always in the normal direction in horizontal boxes + aY = aClientRect.y; + } + else { + // take direction property into account for |x| in vertical boxes + if (frameDirection == NS_STYLE_DIRECTION_LTR) { + // The normal direction. |x| increases as we move through our children. + aX = aClientRect.x; + } + else { + // The reverse direction. |x| decreases as we move through our children. + aX = aClientRect.x + aOriginalRect.width; + } + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) { + // The normal direction. |y| increases as we move through our children. + aY = aClientRect.y; + } + else { + // The reverse direction. |y| decreases as we move through our children. + aY = aClientRect.y + aOriginalRect.height; + } + } + + // Get our pack/alignment information. + nsIFrame::Halignment halign = aBox->GetHAlign(); + nsIFrame::Valignment valign = aBox->GetVAlign(); + + // The following code handles box PACKING. Packing comes into play in the case where the computed size for + // all of our children (now stored in our client rect) is smaller than the size available for + // the box (stored in |aOriginalRect|). + // + // Here we adjust our |x| and |y| variables accordingly so that we start at the beginning, + // middle, or end of the box. + // + // XXXdwh JUSTIFY needs to be implemented! + if (aFrameState & NS_STATE_IS_HORIZONTAL) { + switch(halign) { + case nsBoxFrame::hAlign_Left: + break; // Nothing to do. The default initialized us properly. + + case nsBoxFrame::hAlign_Center: + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) + aX += (aOriginalRect.width - aClientRect.width)/2; + else + aX -= (aOriginalRect.width - aClientRect.width)/2; + break; + + case nsBoxFrame::hAlign_Right: + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) + aX += (aOriginalRect.width - aClientRect.width); + else + aX -= (aOriginalRect.width - aClientRect.width); + break; // Nothing to do for the reverse dir. The default initialized us properly. + } + } else { + switch(valign) { + case nsBoxFrame::vAlign_Top: + case nsBoxFrame::vAlign_BaseLine: // This value is technically impossible to specify for pack. + break; // Don't do anything. We were initialized correctly. + + case nsBoxFrame::vAlign_Middle: + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) + aY += (aOriginalRect.height - aClientRect.height)/2; + else + aY -= (aOriginalRect.height - aClientRect.height)/2; + break; + + case nsBoxFrame::vAlign_Bottom: + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) + aY += (aOriginalRect.height - aClientRect.height); + else + aY -= (aOriginalRect.height - aClientRect.height); + break; + } + } +} + +NS_IMETHODIMP +nsSprocketLayout::Layout(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + // See if we are collapsed. If we are, then simply iterate over all our + // children and give them a rect of 0 width and height. + if (aBox->IsCollapsed()) { + nsIFrame* child = aBox->GetChildBox(); + while(child) + { + nsBoxFrame::LayoutChildAt(aState, child, nsRect(0,0,0,0)); + child = child->GetNextBox(); + } + return NS_OK; + } + + nsBoxLayoutState::AutoReflowDepth depth(aState); + mozilla::AutoStackArena arena; + + // ----- figure out our size ---------- + const nsSize originalSize = aBox->GetSize(); + + // -- make sure we remove our border and padding ---- + nsRect clientRect; + aBox->GetClientRect(clientRect); + + // |originalClientRect| represents the rect of the entire box (excluding borders + // and padding). We store it here because we're going to use |clientRect| to hold + // the required size for all our kids. As an example, consider an hbox with a + // specified width of 300. If the kids total only 150 pixels of width, then + // we have 150 pixels left over. |clientRect| is going to hold a width of 150 and + // is going to be adjusted based off the value of the PACK property. If flexible + // objects are in the box, then the two rects will match. + nsRect originalClientRect(clientRect); + + // The frame state contains cached knowledge about our box, such as our orientation + // and direction. + nsFrameState frameState = 0; + GetFrameState(aBox, frameState); + + // Build a list of our children's desired sizes and computed sizes + nsBoxSize* boxSizes = nullptr; + nsComputedBoxSize* computedBoxSizes = nullptr; + + nscoord min = 0; + nscoord max = 0; + int32_t flexes = 0; + PopulateBoxSizes(aBox, aState, boxSizes, min, max, flexes); + + // The |size| variable will hold the total size of children along the axis of + // the box. Continuing with the example begun in the comment above, size would + // be 150 pixels. + nscoord size = clientRect.width; + if (!IsHorizontal(aBox)) + size = clientRect.height; + ComputeChildSizes(aBox, aState, size, boxSizes, computedBoxSizes); + + // After the call to ComputeChildSizes, the |size| variable contains the + // total required size of all the children. We adjust our clientRect in the + // appropriate dimension to match this size. In our example, we now assign + // 150 pixels into the clientRect.width. + // + // The variables |min| and |max| hold the minimum required size box must be + // in the OPPOSITE orientation, e.g., for a horizontal box, |min| is the minimum + // height we require to enclose our children, and |max| is the maximum height + // required to enclose our children. + if (IsHorizontal(aBox)) { + clientRect.width = size; + if (clientRect.height < min) + clientRect.height = min; + + if (frameState & NS_STATE_AUTO_STRETCH) { + if (clientRect.height > max) + clientRect.height = max; + } + } else { + clientRect.height = size; + if (clientRect.width < min) + clientRect.width = min; + + if (frameState & NS_STATE_AUTO_STRETCH) { + if (clientRect.width > max) + clientRect.width = max; + } + } + + // With the sizes computed, now it's time to lay out our children. + bool finished; + nscoord passes = 0; + + // We flow children at their preferred locations (along with the appropriate computed flex). + // After we flow a child, it is possible that the child will change its size. If/when this happens, + // we have to do another pass. Typically only 2 passes are required, but the code is prepared to + // do as many passes as are necessary to achieve equilibrium. + nscoord x = 0; + nscoord y = 0; + nscoord origX = 0; + nscoord origY = 0; + + // |childResized| lets us know if a child changed its size after we attempted to lay it out at + // the specified size. If this happens, we usually have to do another pass. + bool childResized = false; + + // |passes| stores our number of passes. If for any reason we end up doing more than, say, 10 + // passes, we assert to indicate that something is seriously screwed up. + passes = 0; + do + { +#ifdef DEBUG_REFLOW + if (passes > 0) { + AddIndents(); + printf("ChildResized doing pass: %d\n", passes); + } +#endif + + // Always assume that we're done. This will change if, for example, children don't stay + // the same size after being flowed. + finished = true; + + // Handle box packing. + HandleBoxPack(aBox, frameState, x, y, originalClientRect, clientRect); + + // Now that packing is taken care of we set up a few additional + // tracking variables. + origX = x; + origY = y; + + nscoord nextX = x; + nscoord nextY = y; + + // Now we iterate over our box children and our box size lists in + // parallel. For each child, we look at its sizes and figure out + // where to place it. + nsComputedBoxSize* childComputedBoxSize = computedBoxSizes; + nsBoxSize* childBoxSize = boxSizes; + + nsIFrame* child = aBox->GetChildBox(); + + int32_t count = 0; + while (child || (childBoxSize && childBoxSize->bogus)) + { + // If for some reason, our lists are not the same length, we guard + // by bailing out of the loop. + if (childBoxSize == nullptr) { + NS_NOTREACHED("Lists not the same length."); + break; + } + + nscoord width = clientRect.width; + nscoord height = clientRect.height; + + if (!childBoxSize->bogus) { + // We have a valid box size entry. This entry already contains information about our + // sizes along the axis of the box (e.g., widths in a horizontal box). If our default + // ALIGN is not stretch, however, then we also need to know the child's size along the + // opposite axis. + if (!(frameState & NS_STATE_AUTO_STRETCH)) { + nsSize prefSize = child->GetPrefSize(aState); + nsSize minSize = child->GetMinSize(aState); + nsSize maxSize = child->GetMaxSize(aState); + prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize); + + AddMargin(child, prefSize); + width = std::min(prefSize.width, originalClientRect.width); + height = std::min(prefSize.height, originalClientRect.height); + } + } + + // Obtain the computed size along the axis of the box for this child from the computedBoxSize entry. + // We store the result in |width| for horizontal boxes and |height| for vertical boxes. + if (frameState & NS_STATE_IS_HORIZONTAL) + width = childComputedBoxSize->size; + else + height = childComputedBoxSize->size; + + // Adjust our x/y for the left/right spacing. + if (frameState & NS_STATE_IS_HORIZONTAL) { + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + x += (childBoxSize->left); + else + x -= (childBoxSize->right); + } else { + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + y += (childBoxSize->left); + else + y -= (childBoxSize->right); + } + + nextX = x; + nextY = y; + + // Now we build a child rect. + nscoord rectX = x; + nscoord rectY = y; + if (!(frameState & NS_STATE_IS_DIRECTION_NORMAL)) { + if (frameState & NS_STATE_IS_HORIZONTAL) + rectX -= width; + else + rectY -= height; + } + + // We now create an accurate child rect based off our computed size information. + nsRect childRect(rectX, rectY, width, height); + + // Sanity check against our clientRect. It is possible that a child specified + // a size that is too large to fit. If that happens, then we have to grow + // our client rect. Remember, clientRect is not the total rect of the enclosing + // box. It currently holds our perception of how big the children needed to + // be. + if (childRect.width > clientRect.width) + clientRect.width = childRect.width; + + if (childRect.height > clientRect.height) + clientRect.height = childRect.height; + + // Either |nextX| or |nextY| is updated by this function call, according + // to our axis. + ComputeChildsNextPosition(aBox, x, y, nextX, nextY, childRect); + + // Now we further update our nextX/Y along our axis. + // We also set childRect.y/x along the opposite axis appropriately for a + // stretch alignment. (Non-stretch alignment is handled below.) + if (frameState & NS_STATE_IS_HORIZONTAL) { + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + nextX += (childBoxSize->right); + else + nextX -= (childBoxSize->left); + childRect.y = originalClientRect.y; + } + else { + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + nextY += (childBoxSize->right); + else + nextY -= (childBoxSize->left); + childRect.x = originalClientRect.x; + } + + // If we encounter a completely bogus box size, we just leave this child completely + // alone and continue through the loop to the next child. + if (childBoxSize->bogus) + { + childComputedBoxSize = childComputedBoxSize->next; + childBoxSize = childBoxSize->next; + count++; + x = nextX; + y = nextY; + continue; + } + + nsMargin margin(0,0,0,0); + + bool layout = true; + + // Deflate the rect of our child by its margin. + child->GetMargin(margin); + childRect.Deflate(margin); + if (childRect.width < 0) + childRect.width = 0; + if (childRect.height < 0) + childRect.height = 0; + + // Now we're trying to figure out if we have to lay out this child, i.e., to call + // the child's Layout method. + if (passes > 0) { + layout = false; + } else { + // Always perform layout if we are dirty or have dirty children + if (!NS_SUBTREE_DIRTY(child)) + layout = false; + } + + nsRect oldRect(child->GetRect()); + + // Non-stretch alignment will be handled in AlignChildren(), so don't + // change child out-of-axis positions yet. + if (!(frameState & NS_STATE_AUTO_STRETCH)) { + if (frameState & NS_STATE_IS_HORIZONTAL) { + childRect.y = oldRect.y; + } else { + childRect.x = oldRect.x; + } + } + + // We computed a childRect. Now we want to set the bounds of the child to be that rect. + // If our old rect is different, then we know our size changed and we cache that fact + // in the |sizeChanged| variable. + + child->SetBounds(aState, childRect); + bool sizeChanged = (childRect.width != oldRect.width || + childRect.height != oldRect.height); + + if (sizeChanged) { + // Our size is different. Sanity check against our maximum allowed size to ensure + // we didn't exceed it. + nsSize minSize = child->GetMinSize(aState); + nsSize maxSize = child->GetMaxSize(aState); + maxSize = nsBox::BoundsCheckMinMax(minSize, maxSize); + + // make sure the size is in our max size. + if (childRect.width > maxSize.width) + childRect.width = maxSize.width; + + if (childRect.height > maxSize.height) + childRect.height = maxSize.height; + + // set it again + child->SetBounds(aState, childRect); + } + + // If we already determined that layout was required or if our size has changed, then + // we make sure to call layout on the child, since its children may need to be shifted + // around as a result of the size change. + if (layout || sizeChanged) + child->Layout(aState); + + // If the child was a block or inline (e.g., HTML) it may have changed its rect *during* layout. + // We have to check for this. + nsRect newChildRect(child->GetRect()); + + if (!newChildRect.IsEqualInterior(childRect)) { +#ifdef DEBUG_GROW + child->DumpBox(stdout); + printf(" GREW from (%d,%d) -> (%d,%d)\n", childRect.width, childRect.height, newChildRect.width, newChildRect.height); +#endif + newChildRect.Inflate(margin); + childRect.Inflate(margin); + + // The child changed size during layout. The ChildResized method handles this + // scenario. + ChildResized(aBox, + aState, + child, + childBoxSize, + childComputedBoxSize, + boxSizes, + computedBoxSizes, + childRect, + newChildRect, + clientRect, + flexes, + finished); + + // We note that a child changed size, which means that another pass will be required. + childResized = true; + + // Now that a child resized, it's entirely possible that OUR rect is too small. Now we + // ensure that |originalClientRect| is grown to accommodate the size of |clientRect|. + if (clientRect.width > originalClientRect.width) + originalClientRect.width = clientRect.width; + + if (clientRect.height > originalClientRect.height) + originalClientRect.height = clientRect.height; + + if (!(frameState & NS_STATE_IS_DIRECTION_NORMAL)) { + // Our childRect had its XMost() or YMost() (depending on our layout + // direction), positioned at a certain point. Ensure that the + // newChildRect satisfies the same constraint. Note that this is + // just equivalent to adjusting the x/y by the difference in + // width/height between childRect and newChildRect. So we don't need + // to reaccount for the left and right of the box layout state again. + if (frameState & NS_STATE_IS_HORIZONTAL) + newChildRect.x = childRect.XMost() - newChildRect.width; + else + newChildRect.y = childRect.YMost() - newChildRect.height; + } + + // If the child resized then recompute its position. + ComputeChildsNextPosition(aBox, x, y, nextX, nextY, newChildRect); + + if (newChildRect.width >= margin.left + margin.right && newChildRect.height >= margin.top + margin.bottom) + newChildRect.Deflate(margin); + + if (childRect.width >= margin.left + margin.right && childRect.height >= margin.top + margin.bottom) + childRect.Deflate(margin); + + child->SetBounds(aState, newChildRect); + + // If we are the first box that changed size, then we don't need to do a second pass + if (count == 0) + finished = true; + } + + // Now update our x/y finally. + x = nextX; + y = nextY; + + // Move to the next child. + childComputedBoxSize = childComputedBoxSize->next; + childBoxSize = childBoxSize->next; + + child = child->GetNextBox(); + count++; + } + + // Sanity-checking code to ensure we don't do an infinite # of passes. + passes++; + NS_ASSERTION(passes < 10, "A Box's child is constantly growing!!!!!"); + if (passes > 10) + break; + } while (false == finished); + + // Get rid of our size lists. + while(boxSizes) + { + nsBoxSize* toDelete = boxSizes; + boxSizes = boxSizes->next; + delete toDelete; + } + + while(computedBoxSizes) + { + nsComputedBoxSize* toDelete = computedBoxSizes; + computedBoxSizes = computedBoxSizes->next; + delete toDelete; + } + + if (childResized) { + // See if one of our children forced us to get bigger + nsRect tmpClientRect(originalClientRect); + nsMargin bp(0,0,0,0); + aBox->GetBorderAndPadding(bp); + tmpClientRect.Inflate(bp); + + if (tmpClientRect.width > originalSize.width || tmpClientRect.height > originalSize.height) + { + // if it did reset our bounds. + nsRect bounds(aBox->GetRect()); + if (tmpClientRect.width > originalSize.width) + bounds.width = tmpClientRect.width; + + if (tmpClientRect.height > originalSize.height) + bounds.height = tmpClientRect.height; + + aBox->SetBounds(aState, bounds); + } + } + + // Because our size grew, we now have to readjust because of box packing. Repack + // in order to update our x and y to the correct values. + HandleBoxPack(aBox, frameState, x, y, originalClientRect, clientRect); + + // Compare against our original x and y and only worry about adjusting the children if + // we really did have to change the positions because of packing (typically for 'center' + // or 'end' pack values). + if (x != origX || y != origY) { + nsIFrame* child = aBox->GetChildBox(); + + // reposition all our children + while (child) + { + nsRect childRect(child->GetRect()); + childRect.x += (x - origX); + childRect.y += (y - origY); + child->SetBounds(aState, childRect); + child = child->GetNextBox(); + } + } + + // Perform out-of-axis alignment for non-stretch alignments + if (!(frameState & NS_STATE_AUTO_STRETCH)) { + AlignChildren(aBox, aState); + } + + // That's it! If you made it this far without having a nervous breakdown, + // congratulations! Go get yourself a beer. + return NS_OK; +} + +void +nsSprocketLayout::PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aState, nsBoxSize*& aBoxSizes, nscoord& aMinSize, nscoord& aMaxSize, int32_t& aFlexes) +{ + // used for the equal size flag + nscoord biggestPrefWidth = 0; + nscoord biggestMinWidth = 0; + nscoord smallestMaxWidth = NS_INTRINSICSIZE; + + nsFrameState frameState = 0; + GetFrameState(aBox, frameState); + + //if (frameState & NS_STATE_CURRENTLY_IN_DEBUG) + // printf("In debug\n"); + + aMinSize = 0; + aMaxSize = NS_INTRINSICSIZE; + + bool isHorizontal; + + if (IsHorizontal(aBox)) + isHorizontal = true; + else + isHorizontal = false; + + // this is a nice little optimization + // it turns out that if we only have 1 flexable child + // then it does not matter what its preferred size is + // there is nothing to flex it relative. This is great + // because we can avoid asking for a preferred size in this + // case. Why is this good? Well you might have html inside it + // and asking html for its preferred size is rather expensive. + // so we can just optimize it out this way. + + // set flexes + nsIFrame* child = aBox->GetChildBox(); + + aFlexes = 0; + nsBoxSize* currentBox = nullptr; + +#if 0 + nsBoxSize* start = aBoxSizes; + + while(child) + { + // ok if we started with a list move down the list + // until we reach the end. Then start looking at childen. + // This feature is used extensively for Grid. + nscoord flex = 0; + + if (!start) { + if (!currentBox) { + aBoxSizes = new (aState) nsBoxSize(); + currentBox = aBoxSizes; + } else { + currentBox->next = new (aState) nsBoxSize(); + currentBox = currentBox->next; + } + + + flex = child->GetFlex(aState); + + currentBox->flex = flex; + currentBox->collapsed = child->IsCollapsed(); + } else { + flex = start->flex; + start = start->next; + } + + if (flex > 0) + aFlexes++; + + child = child->GetNextBox(); + } +#endif + + // get pref, min, max + child = aBox->GetChildBox(); + currentBox = aBoxSizes; + nsBoxSize* last = nullptr; + + nscoord maxFlex = 0; + int32_t childCount = 0; + + while(child) + { + while (currentBox && currentBox->bogus) { + last = currentBox; + currentBox = currentBox->next; + } + ++childCount; + nsSize pref(0,0); + nsSize minSize(0,0); + nsSize maxSize(NS_INTRINSICSIZE,NS_INTRINSICSIZE); + nscoord ascent = 0; + bool collapsed = child->IsCollapsed(); + + if (!collapsed) { + // only one flexible child? Cool we will just make its preferred size + // 0 then and not even have to ask for it. + //if (flexes != 1) { + + pref = child->GetPrefSize(aState); + minSize = child->GetMinSize(aState); + maxSize = nsBox::BoundsCheckMinMax(minSize, child->GetMaxSize(aState)); + ascent = child->GetBoxAscent(aState); + nsMargin margin; + child->GetMargin(margin); + ascent += margin.top; + //} + + pref = nsBox::BoundsCheck(minSize, pref, maxSize); + + AddMargin(child, pref); + AddMargin(child, minSize); + AddMargin(child, maxSize); + } + + if (!currentBox) { + // create one. + currentBox = new (aState) nsBoxSize(); + if (!aBoxSizes) { + aBoxSizes = currentBox; + last = aBoxSizes; + } else { + last->next = currentBox; + last = currentBox; + } + + nscoord minWidth; + nscoord maxWidth; + nscoord prefWidth; + + // get sizes from child + if (isHorizontal) { + minWidth = minSize.width; + maxWidth = maxSize.width; + prefWidth = pref.width; + } else { + minWidth = minSize.height; + maxWidth = maxSize.height; + prefWidth = pref.height; + } + + nscoord flex = child->GetFlex(aState); + + // set them if you collapsed you are not flexible. + if (collapsed) { + currentBox->flex = 0; + } + else { + if (flex > maxFlex) { + maxFlex = flex; + } + currentBox->flex = flex; + } + + // we specified all our children are equal size; + if (frameState & NS_STATE_EQUAL_SIZE) { + + if (prefWidth > biggestPrefWidth) + biggestPrefWidth = prefWidth; + + if (minWidth > biggestMinWidth) + biggestMinWidth = minWidth; + + if (maxWidth < smallestMaxWidth) + smallestMaxWidth = maxWidth; + } else { // not we can set our children right now. + currentBox->pref = prefWidth; + currentBox->min = minWidth; + currentBox->max = maxWidth; + } + + NS_ASSERTION(minWidth <= prefWidth && prefWidth <= maxWidth,"Bad min, pref, max widths!"); + + } + + if (!isHorizontal) { + if (minSize.width > aMinSize) + aMinSize = minSize.width; + + if (maxSize.width < aMaxSize) + aMaxSize = maxSize.width; + + } else { + if (minSize.height > aMinSize) + aMinSize = minSize.height; + + if (maxSize.height < aMaxSize) + aMaxSize = maxSize.height; + } + + currentBox->collapsed = collapsed; + aFlexes += currentBox->flex; + + child = child->GetNextBox(); + + last = currentBox; + currentBox = currentBox->next; + + } + + if (childCount > 0) { + nscoord maxAllowedFlex = nscoord_MAX / childCount; + + if (MOZ_UNLIKELY(maxFlex > maxAllowedFlex)) { + // clamp all the flexes + currentBox = aBoxSizes; + while (currentBox) { + currentBox->flex = std::min(currentBox->flex, maxAllowedFlex); + currentBox = currentBox->next; + } + } + } +#ifdef DEBUG + else { + NS_ASSERTION(maxFlex == 0, "How did that happen?"); + } +#endif + + // we specified all our children are equal size; + if (frameState & NS_STATE_EQUAL_SIZE) { + smallestMaxWidth = std::max(smallestMaxWidth, biggestMinWidth); + biggestPrefWidth = nsBox::BoundsCheck(biggestMinWidth, biggestPrefWidth, smallestMaxWidth); + + currentBox = aBoxSizes; + + while(currentBox) + { + if (!currentBox->collapsed) { + currentBox->pref = biggestPrefWidth; + currentBox->min = biggestMinWidth; + currentBox->max = smallestMaxWidth; + } else { + currentBox->pref = 0; + currentBox->min = 0; + currentBox->max = 0; + } + currentBox = currentBox->next; + } + } + +} + +void +nsSprocketLayout::ComputeChildsNextPosition(nsIFrame* aBox, + const nscoord& aCurX, + const nscoord& aCurY, + nscoord& aNextX, + nscoord& aNextY, + const nsRect& aCurrentChildSize) +{ + // Get the position along the box axis for the child. + // The out-of-axis position is not set. + nsFrameState frameState = 0; + GetFrameState(aBox, frameState); + + if (IsHorizontal(aBox)) { + // horizontal box's children. + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + aNextX = aCurX + aCurrentChildSize.width; + else + aNextX = aCurX - aCurrentChildSize.width; + + } else { + // vertical box's children. + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + aNextY = aCurY + aCurrentChildSize.height; + else + aNextY = aCurY - aCurrentChildSize.height; + } +} + +void +nsSprocketLayout::AlignChildren(nsIFrame* aBox, + nsBoxLayoutState& aState) +{ + nsFrameState frameState = 0; + GetFrameState(aBox, frameState); + bool isHorizontal = (frameState & NS_STATE_IS_HORIZONTAL) != 0; + nsRect clientRect; + aBox->GetClientRect(clientRect); + + NS_PRECONDITION(!(frameState & NS_STATE_AUTO_STRETCH), + "Only AlignChildren() with non-stretch alignment"); + + // These are only calculated if needed + nsIFrame::Halignment halign; + nsIFrame::Valignment valign; + nscoord maxAscent; + bool isLTR; + + if (isHorizontal) { + valign = aBox->GetVAlign(); + if (valign == nsBoxFrame::vAlign_BaseLine) { + maxAscent = aBox->GetBoxAscent(aState); + } + } else { + isLTR = GetFrameDirection(aBox) == NS_STYLE_DIRECTION_LTR; + halign = aBox->GetHAlign(); + } + + nsIFrame* child = aBox->GetChildBox(); + while (child) { + + nsMargin margin; + child->GetMargin(margin); + nsRect childRect = child->GetRect(); + + if (isHorizontal) { + const nscoord startAlign = clientRect.y + margin.top; + const nscoord endAlign = + clientRect.YMost() - margin.bottom - childRect.height; + + nscoord y; + switch (valign) { + case nsBoxFrame::vAlign_Top: + y = startAlign; + break; + case nsBoxFrame::vAlign_Middle: + // Should this center the border box? + // This centers the margin box, the historical behavior. + y = (startAlign + endAlign) / 2; + break; + case nsBoxFrame::vAlign_Bottom: + y = endAlign; + break; + case nsBoxFrame::vAlign_BaseLine: + // Alignments don't force the box to grow (only sizes do), + // so keep the children within the box. + y = maxAscent - child->GetBoxAscent(aState); + y = std::max(startAlign, y); + y = std::min(y, endAlign); + break; + } + + childRect.y = y; + + } else { // vertical box + const nscoord leftAlign = clientRect.x + margin.left; + const nscoord rightAlign = + clientRect.XMost() - margin.right - childRect.width; + + nscoord x; + switch (halign) { + case nsBoxFrame::hAlign_Left: // start + x = isLTR ? leftAlign : rightAlign; + break; + case nsBoxFrame::hAlign_Center: + x = (leftAlign + rightAlign) / 2; + break; + case nsBoxFrame::hAlign_Right: // end + x = isLTR ? rightAlign : leftAlign; + break; + } + + childRect.x = x; + } + + if (childRect.TopLeft() != child->GetPosition()) { + child->SetBounds(aState, childRect); + } + + child = child->GetNextBox(); + } +} + +void +nsSprocketLayout::ChildResized(nsIFrame* aBox, + nsBoxLayoutState& aState, + nsIFrame* aChild, + nsBoxSize* aChildBoxSize, + nsComputedBoxSize* aChildComputedSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize* aComputedBoxSizes, + const nsRect& aChildLayoutRect, + nsRect& aChildActualRect, + nsRect& aContainingRect, + int32_t aFlexes, + bool& aFinished) + +{ + nsRect childCurrentRect(aChildLayoutRect); + + bool isHorizontal = IsHorizontal(aBox); + nscoord childLayoutWidth = GET_WIDTH(aChildLayoutRect,isHorizontal); + nscoord& childActualWidth = GET_WIDTH(aChildActualRect,isHorizontal); + nscoord& containingWidth = GET_WIDTH(aContainingRect,isHorizontal); + + //nscoord childLayoutHeight = GET_HEIGHT(aChildLayoutRect,isHorizontal); + nscoord& childActualHeight = GET_HEIGHT(aChildActualRect,isHorizontal); + nscoord& containingHeight = GET_HEIGHT(aContainingRect,isHorizontal); + + bool recompute = false; + + // if we are a horizontal box see if the child will fit inside us. + if ( childActualHeight > containingHeight) { + // if we are a horizontal box and the child is bigger than our height + + // ok if the height changed then we need to reflow everyone but us at the new height + // so we will set the changed index to be us. And signal that we need a new pass. + + nsSize min = aChild->GetMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, aChild->GetMaxSize(aState)); + AddMargin(aChild, max); + + if (isHorizontal) + childActualHeight = max.height < childActualHeight ? max.height : childActualHeight; + else + childActualHeight = max.width < childActualHeight ? max.width : childActualHeight; + + // only set if it changes + if (childActualHeight > containingHeight) { + containingHeight = childActualHeight; + + // remember we do not need to clear the resized list because changing the height of a horizontal box + // will not affect the width of any of its children because block flow left to right, top to bottom. Just trust me + // on this one. + aFinished = false; + + // only recompute if there are flexes. + if (aFlexes > 0) { + // relayout everything + recompute = true; + InvalidateComputedSizes(aComputedBoxSizes); + nsComputedBoxSize* node = aComputedBoxSizes; + + while(node) { + node->resized = false; + node = node->next; + } + + } + } + } + + if (childActualWidth > childLayoutWidth) { + nsSize min = aChild->GetMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, aChild->GetMaxSize(aState)); + + AddMargin(aChild, max); + + // our width now becomes the new size + + if (isHorizontal) + childActualWidth = max.width < childActualWidth ? max.width : childActualWidth; + else + childActualWidth = max.height < childActualWidth ? max.height : childActualWidth; + + if (childActualWidth > childLayoutWidth) { + aChildComputedSize->size = childActualWidth; + aChildBoxSize->min = childActualWidth; + if (aChildBoxSize->pref < childActualWidth) + aChildBoxSize->pref = childActualWidth; + if (aChildBoxSize->max < childActualWidth) + aChildBoxSize->max = childActualWidth; + + // if we have flexible elements with us then reflex things. Otherwise we can skip doing it. + if (aFlexes > 0) { + InvalidateComputedSizes(aComputedBoxSizes); + + nsComputedBoxSize* node = aComputedBoxSizes; + aChildComputedSize->resized = true; + + while(node) { + if (node->resized) + node->valid = true; + + node = node->next; + } + + recompute = true; + aFinished = false; + } else { + containingWidth += aChildComputedSize->size - childLayoutWidth; + } + } + } + + if (recompute) + ComputeChildSizes(aBox, aState, containingWidth, aBoxSizes, aComputedBoxSizes); + + if (!childCurrentRect.IsEqualInterior(aChildActualRect)) { + // the childRect includes the margin + // make sure we remove it before setting + // the bounds. + nsMargin margin(0,0,0,0); + aChild->GetMargin(margin); + nsRect rect(aChildActualRect); + if (rect.width >= margin.left + margin.right && rect.height >= margin.top + margin.bottom) + rect.Deflate(margin); + + aChild->SetBounds(aState, rect); + aChild->Layout(aState); + } + +} + +void +nsSprocketLayout::InvalidateComputedSizes(nsComputedBoxSize* aComputedBoxSizes) +{ + while(aComputedBoxSizes) { + aComputedBoxSizes->valid = false; + aComputedBoxSizes = aComputedBoxSizes->next; + } +} + +void +nsSprocketLayout::ComputeChildSizes(nsIFrame* aBox, + nsBoxLayoutState& aState, + nscoord& aGivenSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize*& aComputedBoxSizes) +{ + + //nscoord onePixel = aState.PresContext()->IntScaledPixelsToTwips(1); + + int32_t sizeRemaining = aGivenSize; + int32_t spacerConstantsRemaining = 0; + + // ----- calculate the spacers constants and the size remaining ----- + + if (!aComputedBoxSizes) + aComputedBoxSizes = new (aState) nsComputedBoxSize(); + + nsBoxSize* boxSizes = aBoxSizes; + nsComputedBoxSize* computedBoxSizes = aComputedBoxSizes; + int32_t count = 0; + int32_t validCount = 0; + + while (boxSizes) + { + + NS_ASSERTION((boxSizes->min <= boxSizes->pref && boxSizes->pref <= boxSizes->max),"bad pref, min, max size"); + + + // ignore collapsed children + // if (boxSizes->collapsed) + // { + // computedBoxSizes->valid = true; + // computedBoxSizes->size = boxSizes->pref; + // validCount++; + // boxSizes->flex = 0; + // }// else { + + if (computedBoxSizes->valid) { + sizeRemaining -= computedBoxSizes->size; + validCount++; + } else { + if (boxSizes->flex == 0) + { + computedBoxSizes->valid = true; + computedBoxSizes->size = boxSizes->pref; + validCount++; + } + + spacerConstantsRemaining += boxSizes->flex; + sizeRemaining -= boxSizes->pref; + } + + sizeRemaining -= (boxSizes->left + boxSizes->right); + + //} + + boxSizes = boxSizes->next; + + if (boxSizes && !computedBoxSizes->next) + computedBoxSizes->next = new (aState) nsComputedBoxSize(); + + computedBoxSizes = computedBoxSizes->next; + count++; + } + + // everything accounted for? + if (validCount < count) + { + // ----- Ok we are give a size to fit into so stretch or squeeze to fit + // ----- Make sure we look at our min and max size + bool limit = true; + for (int pass=1; true == limit; pass++) + { + limit = false; + boxSizes = aBoxSizes; + computedBoxSizes = aComputedBoxSizes; + + while (boxSizes) { + + // ignore collapsed spacers + + // if (!boxSizes->collapsed) { + + nscoord pref = 0; + nscoord max = NS_INTRINSICSIZE; + nscoord min = 0; + nscoord flex = 0; + + pref = boxSizes->pref; + min = boxSizes->min; + max = boxSizes->max; + flex = boxSizes->flex; + + // ----- look at our min and max limits make sure we aren't too small or too big ----- + if (!computedBoxSizes->valid) { + int32_t newSize = pref + int32_t(int64_t(sizeRemaining) * flex / spacerConstantsRemaining); + + if (newSize<=min) { + computedBoxSizes->size = min; + computedBoxSizes->valid = true; + spacerConstantsRemaining -= flex; + sizeRemaining += pref; + sizeRemaining -= min; + limit = true; + } else if (newSize>=max) { + computedBoxSizes->size = max; + computedBoxSizes->valid = true; + spacerConstantsRemaining -= flex; + sizeRemaining += pref; + sizeRemaining -= max; + limit = true; + } + } + // } + boxSizes = boxSizes->next; + computedBoxSizes = computedBoxSizes->next; + } + } + } + + // ---- once we have removed and min and max issues just stretch us out in the remaining space + // ---- or shrink us. Depends on the size remaining and the spacer constants + aGivenSize = 0; + boxSizes = aBoxSizes; + computedBoxSizes = aComputedBoxSizes; + + while (boxSizes) { + + // ignore collapsed spacers + // if (!(boxSizes && boxSizes->collapsed)) { + + nscoord pref = 0; + nscoord flex = 0; + pref = boxSizes->pref; + flex = boxSizes->flex; + + if (!computedBoxSizes->valid) { + computedBoxSizes->size = pref + int32_t(int64_t(sizeRemaining) * flex / spacerConstantsRemaining); + computedBoxSizes->valid = true; + } + + aGivenSize += (boxSizes->left + boxSizes->right); + aGivenSize += computedBoxSizes->size; + + // } + + boxSizes = boxSizes->next; + computedBoxSizes = computedBoxSizes->next; + } +} + + +nsSize +nsSprocketLayout::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize vpref (0, 0); + bool isHorizontal = IsHorizontal(aBox); + + nscoord biggestPref = 0; + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = aBox->GetChildBox(); + nsFrameState frameState = 0; + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsCollapsed()) + { + nsSize pref = child->GetPrefSize(aState); + AddMargin(child, pref); + + if (isEqual) { + if (isHorizontal) + { + if (pref.width > biggestPref) + biggestPref = pref.width; + } else { + if (pref.height > biggestPref) + biggestPref = pref.height; + } + } + + AddLargestSize(vpref, pref, isHorizontal); + count++; + } + + child = child->GetNextBox(); + } + + if (isEqual) { + if (isHorizontal) + vpref.width = biggestPref*count; + else + vpref.height = biggestPref*count; + } + + // now add our border and padding + AddBorderAndPadding(aBox, vpref); + + return vpref; +} + +nsSize +nsSprocketLayout::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize (0, 0); + bool isHorizontal = IsHorizontal(aBox); + + nscoord biggestMin = 0; + + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = aBox->GetChildBox(); + nsFrameState frameState = 0; + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsCollapsed()) + { + nsSize min = child->GetMinSize(aState); + nsSize pref(0,0); + + // if the child is not flexible then + // its min size is its pref size. + if (child->GetFlex(aState) == 0) { + pref = child->GetPrefSize(aState); + if (isHorizontal) + min.width = pref.width; + else + min.height = pref.height; + } + + if (isEqual) { + if (isHorizontal) + { + if (min.width > biggestMin) + biggestMin = min.width; + } else { + if (min.height > biggestMin) + biggestMin = min.height; + } + } + + AddMargin(child, min); + AddLargestSize(minSize, min, isHorizontal); + count++; + } + + child = child->GetNextBox(); + } + + + if (isEqual) { + if (isHorizontal) + minSize.width = biggestMin*count; + else + minSize.height = biggestMin*count; + } + + // now add our border and padding + AddBorderAndPadding(aBox, minSize); + + return minSize; +} + +nsSize +nsSprocketLayout::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + + bool isHorizontal = IsHorizontal(aBox); + + nscoord smallestMax = NS_INTRINSICSIZE; + nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE); + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = aBox->GetChildBox(); + nsFrameState frameState = 0; + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsCollapsed()) + { + // if completely redefined don't even ask our child for its size. + nsSize min = child->GetMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, child->GetMaxSize(aState)); + + AddMargin(child, max); + AddSmallestSize(maxSize, max, isHorizontal); + + if (isEqual) { + if (isHorizontal) + { + if (max.width < smallestMax) + smallestMax = max.width; + } else { + if (max.height < smallestMax) + smallestMax = max.height; + } + } + count++; + } + + child = child->GetNextBox(); + } + + if (isEqual) { + if (isHorizontal) { + if (smallestMax != NS_INTRINSICSIZE) + maxSize.width = smallestMax*count; + else + maxSize.width = NS_INTRINSICSIZE; + } else { + if (smallestMax != NS_INTRINSICSIZE) + maxSize.height = smallestMax*count; + else + maxSize.height = NS_INTRINSICSIZE; + } + } + + // now add our border and padding + AddBorderAndPadding(aBox, maxSize); + + return maxSize; +} + + +nscoord +nsSprocketLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nscoord vAscent = 0; + + bool isHorizontal = IsHorizontal(aBox); + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = aBox->GetChildBox(); + + while (child) + { + // ignore collapsed children + //if (!child->IsCollapsed()) + //{ + // if completely redefined don't even ask our child for its size. + nscoord ascent = child->GetBoxAscent(aState); + + nsMargin margin; + child->GetMargin(margin); + ascent += margin.top; + + if (isHorizontal) + { + if (ascent > vAscent) + vAscent = ascent; + } else { + if (vAscent == 0) + vAscent = ascent; + } + //} + + child = child->GetNextBox(); + } + + nsMargin borderPadding; + aBox->GetBorderAndPadding(borderPadding); + + return vAscent + borderPadding.top; +} + +void +nsSprocketLayout::SetLargestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal) +{ + if (aIsHorizontal) + { + if (aSize1.height < aSize2.height) + aSize1.height = aSize2.height; + } else { + if (aSize1.width < aSize2.width) + aSize1.width = aSize2.width; + } +} + +void +nsSprocketLayout::SetSmallestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal) +{ + if (aIsHorizontal) + { + if (aSize1.height > aSize2.height) + aSize1.height = aSize2.height; + } else { + if (aSize1.width > aSize2.width) + aSize1.width = aSize2.width; + + } +} + +void +nsSprocketLayout::AddLargestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal) +{ + if (aIsHorizontal) + AddCoord(aSize.width, aSizeToAdd.width); + else + AddCoord(aSize.height, aSizeToAdd.height); + + SetLargestSize(aSize, aSizeToAdd, aIsHorizontal); +} + +void +nsSprocketLayout::AddCoord(nscoord& aCoord, nscoord aCoordToAdd) +{ + if (aCoord != NS_INTRINSICSIZE) + { + if (aCoordToAdd == NS_INTRINSICSIZE) + aCoord = aCoordToAdd; + else + aCoord += aCoordToAdd; + } +} +void +nsSprocketLayout::AddSmallestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal) +{ + if (aIsHorizontal) + AddCoord(aSize.width, aSizeToAdd.width); + else + AddCoord(aSize.height, aSizeToAdd.height); + + SetSmallestSize(aSize, aSizeToAdd, aIsHorizontal); +} + +bool +nsSprocketLayout::GetDefaultFlex(int32_t& aFlex) +{ + aFlex = 0; + return true; +} + +nsComputedBoxSize::nsComputedBoxSize() +{ + resized = false; + valid = false; + size = 0; + next = nullptr; +} + +nsBoxSize::nsBoxSize() +{ + pref = 0; + min = 0; + max = NS_INTRINSICSIZE; + collapsed = false; + left = 0; + right = 0; + flex = 0; + next = nullptr; + bogus = false; +} + + +void* +nsBoxSize::operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW +{ + return mozilla::AutoStackArena::Allocate(sz); +} + + +void +nsBoxSize::operator delete(void* aPtr, size_t sz) +{ +} + + +void* +nsComputedBoxSize::operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW +{ + return mozilla::AutoStackArena::Allocate(sz); +} + +void +nsComputedBoxSize::operator delete(void* aPtr, size_t sz) +{ +} diff --git a/layout/xul/base/src/nsSprocketLayout.h b/layout/xul/base/src/nsSprocketLayout.h new file mode 100644 index 000000000..41e515484 --- /dev/null +++ b/layout/xul/base/src/nsSprocketLayout.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSprocketLayout_h___ +#define nsSprocketLayout_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxLayout.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" + +class nsBoxSize +{ +public: + + nsBoxSize(); + + nscoord pref; + nscoord min; + nscoord max; + nscoord flex; + nscoord left; + nscoord right; + bool collapsed; + bool bogus; + + nsBoxSize* next; + + void* operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW; + void operator delete(void* aPtr, size_t sz); +}; + +class nsComputedBoxSize +{ +public: + nsComputedBoxSize(); + + nscoord size; + bool valid; + bool resized; + nsComputedBoxSize* next; + + void* operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW; + void operator delete(void* aPtr, size_t sz); +}; + +#define GET_WIDTH(size, isHorizontal) (isHorizontal ? size.width : size.height) +#define GET_HEIGHT(size, isHorizontal) (isHorizontal ? size.height : size.width) +#define GET_X(size, isHorizontal) (isHorizontal ? size.x : size.y) +#define GET_Y(size, isHorizontal) (isHorizontal ? size.y : size.x) +#define GET_COORD(aX, aY, isHorizontal) (isHorizontal ? aX : aY) + +#define SET_WIDTH(size, coord, isHorizontal) if (isHorizontal) { (size).width = (coord); } else { (size).height = (coord); } +#define SET_HEIGHT(size, coord, isHorizontal) if (isHorizontal) { (size).height = (coord); } else { (size).width = (coord); } +#define SET_X(size, coord, isHorizontal) if (isHorizontal) { (size).x = (coord); } else { (size).y = (coord); } +#define SET_Y(size, coord, isHorizontal) if (isHorizontal) { (size).y = (coord); } else { (size).x = (coord); } + +#define SET_COORD(aX, aY, coord, isHorizontal) if (isHorizontal) { aX = (coord); } else { aY = (coord); } + +nsresult NS_NewSprocketLayout(nsIPresShell* aPresShell, nsCOMPtr<nsBoxLayout>& aNewLayout); + +class nsSprocketLayout : public nsBoxLayout { + +public: + + friend nsresult NS_NewSprocketLayout(nsIPresShell* aPresShell, nsCOMPtr<nsBoxLayout>& aNewLayout); + static void Shutdown(); + + NS_IMETHOD Layout(nsIFrame* aBox, nsBoxLayoutState& aState) MOZ_OVERRIDE; + + virtual nsSize GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nscoord GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + nsSprocketLayout(); + + static bool IsHorizontal(nsIFrame* aBox); + + static void SetLargestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal); + static void SetSmallestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal); + + static void AddLargestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal); + static void AddSmallestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal); + static void AddCoord(nscoord& aCoord, nscoord aCoordToAdd); + +protected: + + + void ComputeChildsNextPosition(nsIFrame* aBox, + const nscoord& aCurX, + const nscoord& aCurY, + nscoord& aNextX, + nscoord& aNextY, + const nsRect& aChildSize); + + void ChildResized(nsIFrame* aBox, + nsBoxLayoutState& aState, + nsIFrame* aChild, + nsBoxSize* aChildBoxSize, + nsComputedBoxSize* aChildComputedBoxSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize* aComputedBoxSizes, + const nsRect& aChildLayoutRect, + nsRect& aChildActualRect, + nsRect& aContainingRect, + int32_t aFlexes, + bool& aFinished); + + void AlignChildren(nsIFrame* aBox, + nsBoxLayoutState& aState); + + virtual void ComputeChildSizes(nsIFrame* aBox, + nsBoxLayoutState& aState, + nscoord& aGivenSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize*& aComputedBoxSizes); + + + virtual void PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState, + nsBoxSize*& aBoxSizes, nscoord& aMinSize, + nscoord& aMaxSize, int32_t& aFlexes); + + virtual void InvalidateComputedSizes(nsComputedBoxSize* aComputedBoxSizes); + + virtual bool GetDefaultFlex(int32_t& aFlex); + + virtual void GetFrameState(nsIFrame* aBox, nsFrameState& aState); + +private: + + + // because the sprocket layout manager has no instance variables. We + // can make a static one and reuse it everywhere. + static nsBoxLayout* gInstance; + +}; + +#endif + diff --git a/layout/xul/base/src/nsStackFrame.cpp b/layout/xul/base/src/nsStackFrame.cpp new file mode 100644 index 000000000..bd34b6fc7 --- /dev/null +++ b/layout/xul/base/src/nsStackFrame.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsStackFrame.h" +#include "nsStyleContext.h" +#include "nsIContent.h" +#include "nsCOMPtr.h" +#include "nsHTMLParts.h" +#include "nsIPresShell.h" +#include "nsCSSRendering.h" +#include "nsBoxLayoutState.h" +#include "nsStackLayout.h" +#include "nsDisplayList.h" + +nsIFrame* +NS_NewStackFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsStackFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsStackFrame) + +nsStackFrame::nsStackFrame(nsIPresShell* aPresShell, nsStyleContext* aContext): + nsBoxFrame(aPresShell, aContext) +{ + nsCOMPtr<nsBoxLayout> layout; + NS_NewStackLayout(aPresShell, layout); + SetLayoutManager(layout); +} + +// REVIEW: The old code put everything in the background layer. To be more +// consistent with the way other frames work, I'm putting everything in the +// Content() (i.e., foreground) layer (see nsFrame::BuildDisplayListForChild, +// the case for stacking context but non-positioned, non-floating frames). +// This could easily be changed back by hacking nsBoxFrame::BuildDisplayListInternal +// a bit more. +void +nsStackFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // BuildDisplayListForChild puts stacking contexts into the PositionedDescendants + // list. So we need to map that list to aLists.Content(). This is an easy way to + // do that. + nsDisplayList* content = aLists.Content(); + nsDisplayListSet kidLists(content, content, content, content, content, content); + nsIFrame* kid = mFrames.FirstChild(); + while (kid) { + // Force each child into its own true stacking context. + BuildDisplayListForChild(aBuilder, kid, aDirtyRect, kidLists, + DISPLAY_CHILD_FORCE_STACKING_CONTEXT); + kid = kid->GetNextSibling(); + } +} diff --git a/layout/xul/base/src/nsStackFrame.h b/layout/xul/base/src/nsStackFrame.h new file mode 100644 index 000000000..80917d6f9 --- /dev/null +++ b/layout/xul/base/src/nsStackFrame.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Eric D Vaughan + A frame that can have multiple children. Only one child may be displayed at one time. So the + can be flipped though like a Stack of cards. + +**/ + +#ifndef nsStackFrame_h___ +#define nsStackFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +class nsStackFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewStackFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE + { + return MakeFrameName(NS_LITERAL_STRING("Stack"), aResult); + } +#endif + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + +protected: + nsStackFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); +}; // class nsStackFrame + + + +#endif + diff --git a/layout/xul/base/src/nsStackLayout.cpp b/layout/xul/base/src/nsStackLayout.cpp new file mode 100644 index 000000000..764fea461 --- /dev/null +++ b/layout/xul/base/src/nsStackLayout.cpp @@ -0,0 +1,383 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsStackLayout.h" +#include "nsCOMPtr.h" +#include "nsBoxLayoutState.h" +#include "nsBox.h" +#include "nsBoxFrame.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsINameSpaceManager.h" + +using namespace mozilla; + +nsBoxLayout* nsStackLayout::gInstance = nullptr; + +#define SPECIFIED_LEFT (1 << NS_SIDE_LEFT) +#define SPECIFIED_RIGHT (1 << NS_SIDE_RIGHT) +#define SPECIFIED_TOP (1 << NS_SIDE_TOP) +#define SPECIFIED_BOTTOM (1 << NS_SIDE_BOTTOM) + +nsresult +NS_NewStackLayout( nsIPresShell* aPresShell, nsCOMPtr<nsBoxLayout>& aNewLayout) +{ + if (!nsStackLayout::gInstance) { + nsStackLayout::gInstance = new nsStackLayout(); + NS_IF_ADDREF(nsStackLayout::gInstance); + } + // we have not instance variables so just return our static one. + aNewLayout = nsStackLayout::gInstance; + return NS_OK; +} + +/*static*/ void +nsStackLayout::Shutdown() +{ + NS_IF_RELEASE(gInstance); +} + +nsStackLayout::nsStackLayout() +{ +} + +/* + * Sizing: we are as wide as the widest child plus its left offset + * we are tall as the tallest child plus its top offset. + * + * Only children which have -moz-stack-sizing set to stretch-to-fit + * (the default) will be included in the size computations. + */ + +nsSize +nsStackLayout::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize prefSize (0, 0); + + nsIFrame* child = aBox->GetChildBox(); + while (child) { + if (child->StyleXUL()->mStretchStack) { + nsSize pref = child->GetPrefSize(aState); + + AddMargin(child, pref); + nsMargin offset; + GetOffset(aState, child, offset); + pref.width += offset.LeftRight(); + pref.height += offset.TopBottom(); + AddLargestSize(prefSize, pref); + } + + child = child->GetNextBox(); + } + + AddBorderAndPadding(aBox, prefSize); + + return prefSize; +} + +nsSize +nsStackLayout::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize (0, 0); + + nsIFrame* child = aBox->GetChildBox(); + while (child) { + if (child->StyleXUL()->mStretchStack) { + nsSize min = child->GetMinSize(aState); + + AddMargin(child, min); + nsMargin offset; + GetOffset(aState, child, offset); + min.width += offset.LeftRight(); + min.height += offset.TopBottom(); + AddLargestSize(minSize, min); + } + + child = child->GetNextBox(); + } + + AddBorderAndPadding(aBox, minSize); + + return minSize; +} + +nsSize +nsStackLayout::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE); + + nsIFrame* child = aBox->GetChildBox(); + while (child) { + if (child->StyleXUL()->mStretchStack) { + nsSize min = child->GetMinSize(aState); + nsSize max = child->GetMaxSize(aState); + + max = nsBox::BoundsCheckMinMax(min, max); + + AddMargin(child, max); + nsMargin offset; + GetOffset(aState, child, offset); + max.width += offset.LeftRight(); + max.height += offset.TopBottom(); + AddSmallestSize(maxSize, max); + } + + child = child->GetNextBox(); + } + + AddBorderAndPadding(aBox, maxSize); + + return maxSize; +} + + +nscoord +nsStackLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nscoord vAscent = 0; + + nsIFrame* child = aBox->GetChildBox(); + while (child) { + nscoord ascent = child->GetBoxAscent(aState); + nsMargin margin; + child->GetMargin(margin); + ascent += margin.top; + if (ascent > vAscent) + vAscent = ascent; + + child = child->GetNextBox(); + } + + return vAscent; +} + +uint8_t +nsStackLayout::GetOffset(nsBoxLayoutState& aState, nsIFrame* aChild, nsMargin& aOffset) +{ + aOffset = nsMargin(0, 0, 0, 0); + + // get the left, right, top and bottom offsets + + // As an optimization, we cache the fact that we are not positioned to avoid + // wasting time fetching attributes. + if (aChild->IsBoxFrame() && + (aChild->GetStateBits() & NS_STATE_STACK_NOT_POSITIONED)) + return 0; + + uint8_t offsetSpecified = 0; + nsIContent* content = aChild->GetContent(); + if (content) { + bool ltr = aChild->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR; + nsAutoString value; + nsresult error; + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::start, value); + if (!value.IsEmpty()) { + value.Trim("%"); + if (ltr) { + aOffset.left = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + offsetSpecified |= SPECIFIED_LEFT; + } else { + aOffset.right = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + offsetSpecified |= SPECIFIED_RIGHT; + } + } + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::end, value); + if (!value.IsEmpty()) { + value.Trim("%"); + if (ltr) { + aOffset.right = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + offsetSpecified |= SPECIFIED_RIGHT; + } else { + aOffset.left = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + offsetSpecified |= SPECIFIED_LEFT; + } + } + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::left, value); + if (!value.IsEmpty()) { + value.Trim("%"); + aOffset.left = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + offsetSpecified |= SPECIFIED_LEFT; + } + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::right, value); + if (!value.IsEmpty()) { + value.Trim("%"); + aOffset.right = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + offsetSpecified |= SPECIFIED_RIGHT; + } + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::top, value); + if (!value.IsEmpty()) { + value.Trim("%"); + aOffset.top = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + offsetSpecified |= SPECIFIED_TOP; + } + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::bottom, value); + if (!value.IsEmpty()) { + value.Trim("%"); + aOffset.bottom = + nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); + offsetSpecified |= SPECIFIED_BOTTOM; + } + } + + if (!offsetSpecified && aChild->IsBoxFrame()) { + // If no offset was specified at all, then we cache this fact to avoid requerying + // CSS or the content model. + aChild->AddStateBits(NS_STATE_STACK_NOT_POSITIONED); + } + + return offsetSpecified; +} + + +NS_IMETHODIMP +nsStackLayout::Layout(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsRect clientRect; + aBox->GetClientRect(clientRect); + + bool grow; + + do { + nsIFrame* child = aBox->GetChildBox(); + grow = false; + + while (child) + { + nsMargin margin; + child->GetMargin(margin); + nsRect childRect(clientRect); + childRect.Deflate(margin); + + if (childRect.width < 0) + childRect.width = 0; + + if (childRect.height < 0) + childRect.height = 0; + + nsRect oldRect(child->GetRect()); + bool sizeChanged = !oldRect.IsEqualEdges(childRect); + + // only lay out dirty children or children whose sizes have changed + if (sizeChanged || NS_SUBTREE_DIRTY(child)) { + // add in the child's margin + nsMargin margin; + child->GetMargin(margin); + + // obtain our offset from the top left border of the stack's content box. + nsMargin offset; + uint8_t offsetSpecified = GetOffset(aState, child, offset); + + // Set the position and size based on which offsets have been specified: + // left only - offset from left edge, preferred width + // right only - offset from right edge, preferred width + // left and right - offset from left and right edges, width in between this + // neither - no offset, full width of stack + // Vertical direction is similar. + // + // Margins on the child are also included in the edge offsets + if (offsetSpecified) { + if (offsetSpecified & SPECIFIED_LEFT) { + childRect.x = clientRect.x + offset.left + margin.left; + if (offsetSpecified & SPECIFIED_RIGHT) { + nsSize min = child->GetMinSize(aState); + nsSize max = child->GetMaxSize(aState); + nscoord width = clientRect.width - offset.LeftRight() - margin.LeftRight(); + childRect.width = clamped(width, min.width, max.width); + } + else { + childRect.width = child->GetPrefSize(aState).width; + } + } + else if (offsetSpecified & SPECIFIED_RIGHT) { + childRect.width = child->GetPrefSize(aState).width; + childRect.x = clientRect.XMost() - offset.right - margin.right - childRect.width; + } + + if (offsetSpecified & SPECIFIED_TOP) { + childRect.y = clientRect.y + offset.top + margin.top; + if (offsetSpecified & SPECIFIED_BOTTOM) { + nsSize min = child->GetMinSize(aState); + nsSize max = child->GetMaxSize(aState); + nscoord height = clientRect.height - offset.TopBottom() - margin.TopBottom(); + childRect.height = clamped(height, min.height, max.height); + } + else { + childRect.height = child->GetPrefSize(aState).height; + } + } + else if (offsetSpecified & SPECIFIED_BOTTOM) { + childRect.height = child->GetPrefSize(aState).height; + childRect.y = clientRect.YMost() - offset.bottom - margin.bottom - childRect.height; + } + } + + // Now place the child. + child->SetBounds(aState, childRect); + + // Flow the child. + child->Layout(aState); + + // Get the child's new rect. + childRect = child->GetRect(); + childRect.Inflate(margin); + + if (child->StyleXUL()->mStretchStack) { + // Did the child push back on us and get bigger? + if (offset.LeftRight() + childRect.width > clientRect.width) { + clientRect.width = childRect.width + offset.LeftRight(); + grow = true; + } + + if (offset.TopBottom() + childRect.height > clientRect.height) { + clientRect.height = childRect.height + offset.TopBottom(); + grow = true; + } + } + } + + child = child->GetNextBox(); + } + } while (grow); + + // if some HTML inside us got bigger we need to force ourselves to + // get bigger + nsRect bounds(aBox->GetRect()); + nsMargin bp; + aBox->GetBorderAndPadding(bp); + clientRect.Inflate(bp); + + if (clientRect.width > bounds.width || clientRect.height > bounds.height) + { + if (clientRect.width > bounds.width) + bounds.width = clientRect.width; + if (clientRect.height > bounds.height) + bounds.height = clientRect.height; + + aBox->SetBounds(aState, bounds); + } + + return NS_OK; +} + diff --git a/layout/xul/base/src/nsStackLayout.h b/layout/xul/base/src/nsStackLayout.h new file mode 100644 index 000000000..e5d2b1edb --- /dev/null +++ b/layout/xul/base/src/nsStackLayout.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Eric D Vaughan + A frame that can have multiple children. Only one child may be displayed at one time. So the + can be flipped though like a deck of cards. + +**/ + +#ifndef nsStackLayout_h___ +#define nsStackLayout_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxLayout.h" +#include "nsCOMPtr.h" +#include "nsCoord.h" + +class nsIPresShell; + +nsresult NS_NewStackLayout(nsIPresShell* aPresShell, nsCOMPtr<nsBoxLayout>& aNewLayout); + +class nsStackLayout : public nsBoxLayout +{ +public: + + friend nsresult NS_NewStackLayout(nsIPresShell* aPresShell, nsCOMPtr<nsBoxLayout>& aNewLayout); + static void Shutdown(); + + nsStackLayout(); + + NS_IMETHOD Layout(nsIFrame* aBox, nsBoxLayoutState& aState) MOZ_OVERRIDE; + + virtual nsSize GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nscoord GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + // get the child offsets for aChild and set them in aMargin. Returns a + // bitfield mask of the SPECIFIED_LEFT, SPECIFIED_RIGHT, SPECIFIED_TOP and + // SPECIFIED_BOTTOM offsets indicating which sides have been specified by + // attributes. + static uint8_t GetOffset(nsBoxLayoutState& aState, nsIFrame* aChild, nsMargin& aMargin); + +private: + static nsBoxLayout* gInstance; + +}; // class nsStackLayout + + + +#endif + diff --git a/layout/xul/base/src/nsTextBoxFrame.cpp b/layout/xul/base/src/nsTextBoxFrame.cpp new file mode 100644 index 000000000..8f745c678 --- /dev/null +++ b/layout/xul/base/src/nsTextBoxFrame.cpp @@ -0,0 +1,1141 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTextBoxFrame.h" + +#include "nsReadableUtils.h" +#include "nsCOMPtr.h" +#include "nsGkAtoms.h" +#include "nsPresContext.h" +#include "nsRenderingContext.h" +#include "nsStyleContext.h" +#include "nsIContent.h" +#include "nsINameSpaceManager.h" +#include "nsBoxLayoutState.h" +#include "nsMenuBarListener.h" +#include "nsXPIDLString.h" +#include "nsIServiceManager.h" +#include "nsIDOMElement.h" +#include "nsIDOMXULLabelElement.h" +#include "nsEventStateManager.h" +#include "nsITheme.h" +#include "nsUnicharUtils.h" +#include "nsContentUtils.h" +#include "nsCxPusher.h" +#include "nsDisplayList.h" +#include "nsCSSRendering.h" +#include "nsIReflowCallback.h" +#include "nsBoxFrame.h" +#include "mozilla/Preferences.h" +#include "nsLayoutUtils.h" +#include "mozilla/Attributes.h" + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +#ifdef IBMBIDI +#include "nsBidiUtils.h" +#include "nsBidiPresUtils.h" +#endif // IBMBIDI + +using namespace mozilla; + +class nsAccessKeyInfo +{ +public: + int32_t mAccesskeyIndex; + nscoord mBeforeWidth, mAccessWidth, mAccessUnderlineSize, mAccessOffset; +}; + + +bool nsTextBoxFrame::gAlwaysAppendAccessKey = false; +bool nsTextBoxFrame::gAccessKeyPrefInitialized = false; +bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey = false; +bool nsTextBoxFrame::gInsertSeparatorPrefInitialized = false; + +nsIFrame* +NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTextBoxFrame (aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame) + +NS_QUERYFRAME_HEAD(nsTextBoxFrame) + NS_QUERYFRAME_ENTRY(nsTextBoxFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsTextBoxFrameSuper) + +NS_IMETHODIMP +nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + bool aResize; + bool aRedraw; + + UpdateAttributes(aAttribute, aResize, aRedraw); + + if (aResize) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + } else if (aRedraw) { + nsBoxLayoutState state(PresContext()); + Redraw(state); + } + + // If the accesskey changed, register for the new value + // The old value has been unregistered in nsXULElement::SetAttr + if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control) + RegUnregAccessKey(true); + + return NS_OK; +} + +nsTextBoxFrame::nsTextBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsLeafBoxFrame(aShell, aContext), mAccessKeyInfo(nullptr), mCropType(CropRight), + mNeedsReflowCallback(false) +{ + MarkIntrinsicWidthsDirty(); +} + +nsTextBoxFrame::~nsTextBoxFrame() +{ + delete mAccessKeyInfo; +} + + +void +nsTextBoxFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsTextBoxFrameSuper::Init(aContent, aParent, aPrevInFlow); + + bool aResize; + bool aRedraw; + UpdateAttributes(nullptr, aResize, aRedraw); /* update all */ + + // register access key + RegUnregAccessKey(true); +} + +void +nsTextBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // unregister access key + RegUnregAccessKey(false); + nsTextBoxFrameSuper::DestroyFrom(aDestructRoot); +} + +bool +nsTextBoxFrame::AlwaysAppendAccessKey() +{ + if (!gAccessKeyPrefInitialized) + { + gAccessKeyPrefInitialized = true; + + const char* prefName = "intl.menuitems.alwaysappendaccesskeys"; + nsAdoptingString val = Preferences::GetLocalizedString(prefName); + gAlwaysAppendAccessKey = val.Equals(NS_LITERAL_STRING("true")); + } + return gAlwaysAppendAccessKey; +} + +bool +nsTextBoxFrame::InsertSeparatorBeforeAccessKey() +{ + if (!gInsertSeparatorPrefInitialized) + { + gInsertSeparatorPrefInitialized = true; + + const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys"; + nsAdoptingString val = Preferences::GetLocalizedString(prefName); + gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true"); + } + return gInsertSeparatorBeforeAccessKey; +} + +class nsAsyncAccesskeyUpdate MOZ_FINAL : public nsIReflowCallback +{ +public: + nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame) + { + } + + virtual bool ReflowFinished() + { + bool shouldFlush = false; + nsTextBoxFrame* frame = + static_cast<nsTextBoxFrame*>(mWeakFrame.GetFrame()); + if (frame) { + shouldFlush = frame->UpdateAccesskey(mWeakFrame); + } + delete this; + return shouldFlush; + } + + virtual void ReflowCallbackCanceled() + { + delete this; + } + + nsWeakFrame mWeakFrame; +}; + +bool +nsTextBoxFrame::UpdateAccesskey(nsWeakFrame& aWeakThis) +{ + nsAutoString accesskey; + nsCOMPtr<nsIDOMXULLabelElement> labelElement = do_QueryInterface(mContent); + NS_ENSURE_TRUE(aWeakThis.IsAlive(), false); + if (labelElement) { + // Accesskey may be stored on control. + // Because this method is called by the reflow callback, current context + // may not be the right one. Pushing the context of mContent so that + // if nsIDOMXULLabelElement is implemented in XBL, we don't get a + // security exception. + nsCxPusher cx; + if (cx.Push(mContent)) { + labelElement->GetAccessKey(accesskey); + NS_ENSURE_TRUE(aWeakThis.IsAlive(), false); + } + } + else { + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey); + } + + if (!accesskey.Equals(mAccessKey)) { + // Need to get clean mTitle. + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle); + mAccessKey = accesskey; + UpdateAccessTitle(); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + return true; + } + return false; +} + +void +nsTextBoxFrame::UpdateAttributes(nsIAtom* aAttribute, + bool& aResize, + bool& aRedraw) +{ + bool doUpdateTitle = false; + aResize = false; + aRedraw = false; + + if (aAttribute == nullptr || aAttribute == nsGkAtoms::crop) { + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::left, &nsGkAtoms::start, &nsGkAtoms::center, + &nsGkAtoms::right, &nsGkAtoms::end, nullptr}; + CroppingStyle cropType; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop, + strings, eCaseMatters)) { + case 0: + case 1: + cropType = CropLeft; + break; + case 2: + cropType = CropCenter; + break; + case 3: + case 4: + cropType = CropRight; + break; + default: + cropType = CropNone; + break; + } + + if (cropType != mCropType) { + aResize = true; + mCropType = cropType; + } + } + + if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) { + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle); + doUpdateTitle = true; + } + + if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) { + mNeedsReflowCallback = true; + // Ensure that layout is refreshed and reflow callback called. + aResize = true; + } + + if (doUpdateTitle) { + UpdateAccessTitle(); + aResize = true; + } + +} + +class nsDisplayXULTextBox : public nsDisplayItem { +public: + nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder, + nsTextBoxFrame* aFrame) : + nsDisplayItem(aBuilder, aFrame), + mDisableSubpixelAA(false) + { + MOZ_COUNT_CTOR(nsDisplayXULTextBox); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayXULTextBox() { + MOZ_COUNT_DTOR(nsDisplayXULTextBox); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx); + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap); + NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX) + + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder); + + virtual void DisableComponentAlpha() { mDisableSubpixelAA = true; } + + void PaintTextToContext(nsRenderingContext* aCtx, + nsPoint aOffset, + const nscolor* aColor); + + bool mDisableSubpixelAA; +}; + +static void +PaintTextShadowCallback(nsRenderingContext* aCtx, + nsPoint aShadowOffset, + const nscolor& aShadowColor, + void* aData) +{ + reinterpret_cast<nsDisplayXULTextBox*>(aData)-> + PaintTextToContext(aCtx, aShadowOffset, &aShadowColor); +} + +void +nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(), + mDisableSubpixelAA); + + // Paint the text shadow before doing any foreground stuff + nsRect drawRect = static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect + + ToReferenceFrame(); + nsLayoutUtils::PaintTextShadow(mFrame, aCtx, + drawRect, mVisibleRect, + mFrame->StyleColor()->mColor, + PaintTextShadowCallback, + (void*)this); + + PaintTextToContext(aCtx, nsPoint(0, 0), nullptr); +} + +void +nsDisplayXULTextBox::PaintTextToContext(nsRenderingContext* aCtx, + nsPoint aOffset, + const nscolor* aColor) +{ + static_cast<nsTextBoxFrame*>(mFrame)-> + PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame() + aOffset, aColor); +} + +nsRect +nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { + *aSnap = false; + return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); +} + +nsRect +nsDisplayXULTextBox::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) +{ + return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() + + ToReferenceFrame(); +} + +void +nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!IsVisibleForPainting(aBuilder)) + return; + + nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); + + aLists.Content()->AppendNewToTop(new (aBuilder) + nsDisplayXULTextBox(aBuilder, this)); +} + +void +nsTextBoxFrame::PaintTitle(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, + const nscolor* aOverrideColor) +{ + if (mTitle.IsEmpty()) + return; + + DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor); +} + +void +nsTextBoxFrame::DrawText(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + const nsRect& aTextRect, + const nscolor* aOverrideColor) +{ + nsPresContext* presContext = PresContext(); + + // paint the title + nscolor overColor; + nscolor underColor; + nscolor strikeColor; + uint8_t overStyle; + uint8_t underStyle; + uint8_t strikeStyle; + + // Begin with no decorations + uint8_t decorations = NS_STYLE_TEXT_DECORATION_LINE_NONE; + // A mask of all possible decorations. + uint8_t decorMask = NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK; + + nsIFrame* f = this; + do { // find decoration colors + nsStyleContext* context = f->StyleContext(); + if (!context->HasTextDecorationLines()) { + break; + } + const nsStyleTextReset* styleText = context->StyleTextReset(); + + if (decorMask & styleText->mTextDecorationLine) { // a decoration defined here + nscolor color; + if (aOverrideColor) { + color = *aOverrideColor; + } else { + bool isForeground; + styleText->GetDecorationColor(color, isForeground); + if (isForeground) { + color = nsLayoutUtils::GetColor(f, eCSSProperty_color); + } + } + uint8_t style = styleText->GetDecorationStyle(); + + if (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE & decorMask & + styleText->mTextDecorationLine) { + underColor = color; + underStyle = style; + decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; + decorations |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; + } + if (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE & decorMask & + styleText->mTextDecorationLine) { + overColor = color; + overStyle = style; + decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERLINE; + decorations |= NS_STYLE_TEXT_DECORATION_LINE_OVERLINE; + } + if (NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH & decorMask & + styleText->mTextDecorationLine) { + strikeColor = color; + strikeStyle = style; + decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; + decorations |= NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; + } + } + } while (0 != decorMask && + (f = nsLayoutUtils::GetParentOrPlaceholderFor(f))); + + nsRefPtr<nsFontMetrics> fontMet; + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet)); + + nscoord offset; + nscoord size; + nscoord ascent = fontMet->MaxAscent(); + + nscoord baseline = + presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent); + nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext(); + gfxPoint pt(presContext->AppUnitsToGfxUnits(aTextRect.x), + presContext->AppUnitsToGfxUnits(aTextRect.y)); + gfxFloat width = presContext->AppUnitsToGfxUnits(aTextRect.width); + gfxFloat ascentPixel = presContext->AppUnitsToGfxUnits(ascent); + gfxFloat xInFrame = PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x); + gfxRect dirtyRect(presContext->AppUnitsToGfxUnits(aDirtyRect)); + + // Underlines are drawn before overlines, and both before the text + // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1. + // (We don't apply this rule to the access-key underline because we only + // find out where that is as a side effect of drawing the text, in the + // general case -- see below.) + if (decorations & (NS_FONT_DECORATION_OVERLINE | + NS_FONT_DECORATION_UNDERLINE)) { + fontMet->GetUnderline(offset, size); + gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset); + gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size); + if ((decorations & NS_FONT_DECORATION_UNDERLINE) && + underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, underColor, + pt, xInFrame, gfxSize(width, sizePixel), + ascentPixel, offsetPixel, + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, underStyle); + } + if ((decorations & NS_FONT_DECORATION_OVERLINE) && + overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, overColor, + pt, xInFrame, gfxSize(width, sizePixel), + ascentPixel, ascentPixel, + NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, overStyle); + } + } + + nsRefPtr<nsRenderingContext> refContext = + PresContext()->PresShell()->GetReferenceRenderingContext(); + + aRenderingContext.SetFont(fontMet); + refContext->SetFont(fontMet); + + CalculateUnderline(*refContext); + + aRenderingContext.SetColor(aOverrideColor ? *aOverrideColor : StyleColor()->mColor); + +#ifdef IBMBIDI + nsresult rv = NS_ERROR_FAILURE; + + if (mState & NS_FRAME_IS_BIDI) { + presContext->SetBidiEnabled(); + const nsStyleVisibility* vis = StyleVisibility(); + nsBidiDirection direction = (NS_STYLE_DIRECTION_RTL == vis->mDirection) ? NSBIDI_RTL : NSBIDI_LTR; + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + // We let the RenderText function calculate the mnemonic's + // underline position for us. + nsBidiPositionResolve posResolve; + posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex; + rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), direction, + presContext, aRenderingContext, + *refContext, + aTextRect.x, baseline, + &posResolve, + 1); + mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips; + mAccessKeyInfo->mAccessWidth = posResolve.visualWidth; + } + else + { + rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), direction, + presContext, aRenderingContext, + *refContext, + aTextRect.x, baseline); + } + } + if (NS_FAILED(rv) ) +#endif // IBMBIDI + { + aRenderingContext.SetTextRunRTL(false); + + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + // In the simple (non-BiDi) case, we calculate the mnemonic's + // underline position by getting the text metric. + // XXX are attribute values always two byte? + if (mAccessKeyInfo->mAccesskeyIndex > 0) + mAccessKeyInfo->mBeforeWidth = + refContext->GetWidth(mCroppedTitle.get(), + mAccessKeyInfo->mAccesskeyIndex); + else + mAccessKeyInfo->mBeforeWidth = 0; + } + + fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(), + aTextRect.x, baseline, &aRenderingContext, + refContext.get()); + } + + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + aRenderingContext.FillRect(aTextRect.x + mAccessKeyInfo->mBeforeWidth, + aTextRect.y + mAccessKeyInfo->mAccessOffset, + mAccessKeyInfo->mAccessWidth, + mAccessKeyInfo->mAccessUnderlineSize); + } + + // Strikeout is drawn on top of the text, per + // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1. + if ((decorations & NS_FONT_DECORATION_LINE_THROUGH) && + strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + fontMet->GetStrikeout(offset, size); + gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset); + gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size); + nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, strikeColor, + pt, xInFrame, gfxSize(width, sizePixel), ascentPixel, + offsetPixel, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, + strikeStyle); + } +} + +void +nsTextBoxFrame::CalculateUnderline(nsRenderingContext& aRenderingContext) +{ + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + // Calculate all fields of mAccessKeyInfo which + // are the same for both BiDi and non-BiDi frames. + const PRUnichar *titleString = mCroppedTitle.get(); + aRenderingContext.SetTextRunRTL(false); + mAccessKeyInfo->mAccessWidth = + aRenderingContext.GetWidth(titleString[mAccessKeyInfo-> + mAccesskeyIndex]); + + nscoord offset, baseline; + nsFontMetrics* metrics = aRenderingContext.FontMetrics(); + metrics->GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize); + baseline = metrics->MaxAscent(); + mAccessKeyInfo->mAccessOffset = baseline - offset; + } +} + +nscoord +nsTextBoxFrame::CalculateTitleForWidth(nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + nscoord aWidth) +{ + if (mTitle.IsEmpty()) { + mCroppedTitle.Truncate(); + return 0; + } + + nsRefPtr<nsFontMetrics> fm; + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); + aRenderingContext.SetFont(fm); + + // see if the text will completely fit in the width given + nscoord titleWidth = nsLayoutUtils::GetStringWidth(this, &aRenderingContext, + mTitle.get(), mTitle.Length()); + + if (titleWidth <= aWidth) { + mCroppedTitle = mTitle; +#ifdef IBMBIDI + if (HasRTLChars(mTitle)) { + mState |= NS_FRAME_IS_BIDI; + } +#endif // IBMBIDI + return titleWidth; // fits, done. + } + + const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); + // start with an ellipsis + mCroppedTitle.Assign(kEllipsis); + + // see if the width is even smaller than the ellipsis + // if so, clear the text (XXX set as many '.' as we can?). + aRenderingContext.SetTextRunRTL(false); + titleWidth = aRenderingContext.GetWidth(kEllipsis); + + if (titleWidth > aWidth) { + mCroppedTitle.SetLength(0); + return 0; + } + + // if the ellipsis fits perfectly, no use in trying to insert + if (titleWidth == aWidth) + return titleWidth; + + aWidth -= titleWidth; + + // XXX: This whole block should probably take surrogates into account + // XXX and clusters! + // ok crop things + switch (mCropType) + { + case CropNone: + case CropRight: + { + nscoord cwidth; + nscoord twidth = 0; + int length = mTitle.Length(); + int i; + for (i = 0; i < length; ++i) { + PRUnichar ch = mTitle.CharAt(i); + // still in LTR mode + cwidth = aRenderingContext.GetWidth(ch); + if (twidth + cwidth > aWidth) + break; + + twidth += cwidth; +#ifdef IBMBIDI + if (UCS2_CHAR_IS_BIDI(ch) ) { + mState |= NS_FRAME_IS_BIDI; + } +#endif // IBMBIDI + } + + if (i == 0) + return titleWidth; + + // insert what character we can in. + nsAutoString title( mTitle ); + title.Truncate(i); + mCroppedTitle.Insert(title, 0); + } + break; + + case CropLeft: + { + nscoord cwidth; + nscoord twidth = 0; + int length = mTitle.Length(); + int i; + for (i=length-1; i >= 0; --i) { + PRUnichar ch = mTitle.CharAt(i); + cwidth = aRenderingContext.GetWidth(ch); + if (twidth + cwidth > aWidth) + break; + + twidth += cwidth; +#ifdef IBMBIDI + if (UCS2_CHAR_IS_BIDI(ch) ) { + mState |= NS_FRAME_IS_BIDI; + } +#endif // IBMBIDI + } + + if (i == length-1) + return titleWidth; + + nsAutoString copy; + mTitle.Right(copy, length-1-i); + mCroppedTitle += copy; + } + break; + + case CropCenter: + { + nscoord stringWidth = + nsLayoutUtils::GetStringWidth(this, &aRenderingContext, + mTitle.get(), mTitle.Length()); + if (stringWidth <= aWidth) { + // the entire string will fit in the maximum width + mCroppedTitle.Insert(mTitle, 0); + break; + } + + // determine how much of the string will fit in the max width + nscoord charWidth = 0; + nscoord totalWidth = 0; + PRUnichar ch; + int leftPos, rightPos; + nsAutoString leftString, rightString; + + rightPos = mTitle.Length() - 1; + aRenderingContext.SetTextRunRTL(false); + for (leftPos = 0; leftPos <= rightPos;) { + // look at the next character on the left end + ch = mTitle.CharAt(leftPos); + charWidth = aRenderingContext.GetWidth(ch); + totalWidth += charWidth; + if (totalWidth > aWidth) + // greater than the allowable width + break; + leftString.Insert(ch, leftString.Length()); + +#ifdef IBMBIDI + if (UCS2_CHAR_IS_BIDI(ch)) + mState |= NS_FRAME_IS_BIDI; +#endif + + // look at the next character on the right end + if (rightPos > leftPos) { + // haven't looked at this character yet + ch = mTitle.CharAt(rightPos); + charWidth = aRenderingContext.GetWidth(ch); + totalWidth += charWidth; + if (totalWidth > aWidth) + // greater than the allowable width + break; + rightString.Insert(ch, 0); + +#ifdef IBMBIDI + if (UCS2_CHAR_IS_BIDI(ch)) + mState |= NS_FRAME_IS_BIDI; +#endif + } + + // look at the next two characters + leftPos++; + rightPos--; + } + + mCroppedTitle = leftString + kEllipsis + rightString; + } + break; + } + + return nsLayoutUtils::GetStringWidth(this, &aRenderingContext, + mCroppedTitle.get(), mCroppedTitle.Length()); +} + +#define OLD_ELLIPSIS NS_LITERAL_STRING("...") + +// the following block is to append the accesskey to mTitle if there is an accesskey +// but the mTitle doesn't have the character +void +nsTextBoxFrame::UpdateAccessTitle() +{ + /* + * Note that if you change appending access key label spec, + * you need to maintain same logic in following methods. See bug 324159. + * toolkit/content/commonDialog.js (setLabelForNode) + * toolkit/content/widgets/text.xml (formatAccessKey) + */ + int32_t menuAccessKey; + nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); + if (!menuAccessKey || mAccessKey.IsEmpty()) + return; + + if (!AlwaysAppendAccessKey() && + FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator())) + return; + + nsAutoString accessKeyLabel; + accessKeyLabel += '('; + accessKeyLabel += mAccessKey; + ToUpperCase(accessKeyLabel); + accessKeyLabel += ')'; + + if (mTitle.IsEmpty()) { + mTitle = accessKeyLabel; + return; + } + + const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); + uint32_t offset = mTitle.Length(); + if (StringEndsWith(mTitle, kEllipsis)) { + offset -= kEllipsis.Length(); + } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) { + // Try to check with our old ellipsis (for old addons) + offset -= OLD_ELLIPSIS.Length(); + } else { + // Try to check with + // our default ellipsis (for non-localized addons) or ':' + const PRUnichar kLastChar = mTitle.Last(); + if (kLastChar == PRUnichar(0x2026) || kLastChar == PRUnichar(':')) + offset--; + } + + if (InsertSeparatorBeforeAccessKey() && + offset > 0 && !NS_IS_SPACE(mTitle[offset - 1])) { + mTitle.Insert(' ', offset); + offset++; + } + + mTitle.Insert(accessKeyLabel, offset); +} + +void +nsTextBoxFrame::UpdateAccessIndex() +{ + int32_t menuAccessKey; + nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); + if (menuAccessKey) { + if (mAccessKey.IsEmpty()) { + if (mAccessKeyInfo) { + delete mAccessKeyInfo; + mAccessKeyInfo = nullptr; + } + } else { + if (!mAccessKeyInfo) { + mAccessKeyInfo = new nsAccessKeyInfo(); + if (!mAccessKeyInfo) + return; + } + + nsAString::const_iterator start, end; + + mCroppedTitle.BeginReading(start); + mCroppedTitle.EndReading(end); + + // remember the beginning of the string + nsAString::const_iterator originalStart = start; + + bool found; + if (!AlwaysAppendAccessKey()) { + // not appending access key - do case-sensitive search + // first + found = FindInReadable(mAccessKey, start, end); + if (!found) { + // didn't find it - perform a case-insensitive search + start = originalStart; + found = FindInReadable(mAccessKey, start, end, + nsCaseInsensitiveStringComparator()); + } + } else { + found = RFindInReadable(mAccessKey, start, end, + nsCaseInsensitiveStringComparator()); + } + + if (found) + mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start); + else + mAccessKeyInfo->mAccesskeyIndex = kNotFound; + } + } +} + +NS_IMETHODIMP +nsTextBoxFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState) +{ + if (mNeedsReflowCallback) { + nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this); + if (cb) { + PresContext()->PresShell()->PostReflowCallback(cb); + } + mNeedsReflowCallback = false; + } + + nsresult rv = nsLeafBoxFrame::DoLayout(aBoxLayoutState); + + CalcDrawRect(*aBoxLayoutState.GetRenderingContext()); + + const nsStyleText* textStyle = StyleText(); + + nsRect scrollBounds(nsPoint(0, 0), GetSize()); + nsRect textRect = mTextDrawRect; + + nsRefPtr<nsFontMetrics> fontMet; + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet)); + nsBoundingMetrics metrics = + fontMet->GetInkBoundsForVisualOverflow(mCroppedTitle.get(), + mCroppedTitle.Length(), + aBoxLayoutState.GetRenderingContext()); + + textRect.x -= metrics.leftBearing; + textRect.width = metrics.width; + // In DrawText() we always draw with the baseline at MaxAscent() (relative to mTextDrawRect), + textRect.y += fontMet->MaxAscent() - metrics.ascent; + textRect.height = metrics.ascent + metrics.descent; + + // Our scrollable overflow is our bounds; our visual overflow may + // extend beyond that. + nsRect visualBounds; + visualBounds.UnionRect(scrollBounds, textRect); + nsOverflowAreas overflow(visualBounds, scrollBounds); + + if (textStyle->mTextShadow) { + // text-shadow extends our visual but not scrollable bounds + nsRect &vis = overflow.VisualOverflow(); + vis.UnionRect(vis, nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this)); + } + FinishAndStoreOverflow(overflow, GetSize()); + + return rv; +} + +nsRect +nsTextBoxFrame::GetComponentAlphaBounds() +{ + if (StyleText()->mTextShadow) { + return GetVisualOverflowRectRelativeToSelf(); + } + return mTextDrawRect; +} + +bool +nsTextBoxFrame::ComputesOwnOverflowArea() +{ + return true; +} + +/* virtual */ void +nsTextBoxFrame::MarkIntrinsicWidthsDirty() +{ + mNeedsRecalc = true; + nsTextBoxFrameSuper::MarkIntrinsicWidthsDirty(); +} + +void +nsTextBoxFrame::GetTextSize(nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsString& aString, + nsSize& aSize, nscoord& aAscent) +{ + nsRefPtr<nsFontMetrics> fontMet; + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet)); + aSize.height = fontMet->MaxHeight(); + aRenderingContext.SetFont(fontMet); + aSize.width = + nsLayoutUtils::GetStringWidth(this, &aRenderingContext, + aString.get(), aString.Length()); + aAscent = fontMet->MaxAscent(); +} + +void +nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState) +{ + if (mNeedsRecalc) + { + nsSize size; + nsPresContext* presContext = aBoxLayoutState.PresContext(); + nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext(); + if (rendContext) { + GetTextSize(presContext, *rendContext, + mTitle, size, mAscent); + mTextSize = size; + mNeedsRecalc = false; + } + } +} + +void +nsTextBoxFrame::CalcDrawRect(nsRenderingContext &aRenderingContext) +{ + nsRect textRect(nsPoint(0, 0), GetSize()); + nsMargin borderPadding; + GetBorderAndPadding(borderPadding); + textRect.Deflate(borderPadding); + + // determine (cropped) title and underline position + nsPresContext* presContext = PresContext(); + // determine (cropped) title which fits in aRect.width and its width + nscoord titleWidth = + CalculateTitleForWidth(presContext, aRenderingContext, textRect.width); + +#ifdef ACCESSIBILITY + // Make sure to update the accessible tree in case when cropped title is + // changed. + nsAccessibilityService* accService = GetAccService(); + if (accService) { + accService->UpdateLabelValue(PresContext()->PresShell(), mContent, + mCroppedTitle); + } +#endif + + // determine if and at which position to put the underline + UpdateAccessIndex(); + + // make the rect as small as our (cropped) text. + nscoord outerWidth = textRect.width; + textRect.width = titleWidth; + + // Align our text within the overall rect by checking our text-align property. + const nsStyleVisibility* vis = StyleVisibility(); + const nsStyleText* textStyle = StyleText(); + + if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_CENTER) + textRect.x += (outerWidth - textRect.width)/2; + else if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_RIGHT || + (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_DEFAULT && + vis->mDirection == NS_STYLE_DIRECTION_RTL) || + (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_END && + vis->mDirection == NS_STYLE_DIRECTION_LTR)) { + textRect.x += (outerWidth - textRect.width); + } + + mTextDrawRect = textRect; +} + +/** + * Ok return our dimensions + */ +nsSize +nsTextBoxFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState) +{ + CalcTextSize(aBoxLayoutState); + + nsSize size = mTextSize; + DISPLAY_PREF_SIZE(this, size); + + AddBorderAndPadding(size); + bool widthSet, heightSet; + nsIFrame::AddCSSPrefSize(this, size, widthSet, heightSet); + + return size; +} + +/** + * Ok return our dimensions + */ +nsSize +nsTextBoxFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState) +{ + CalcTextSize(aBoxLayoutState); + + nsSize size = mTextSize; + DISPLAY_MIN_SIZE(this, size); + + // if there is cropping our min width becomes our border and padding + if (mCropType != CropNone) + size.width = 0; + + AddBorderAndPadding(size); + bool widthSet, heightSet; + nsIFrame::AddCSSMinSize(aBoxLayoutState, this, size, widthSet, heightSet); + + return size; +} + +nscoord +nsTextBoxFrame::GetBoxAscent(nsBoxLayoutState& aBoxLayoutState) +{ + CalcTextSize(aBoxLayoutState); + + nscoord ascent = mAscent; + + nsMargin m(0,0,0,0); + GetBorderAndPadding(m); + ascent += m.top; + + return ascent; +} + +#ifdef DEBUG +NS_IMETHODIMP +nsTextBoxFrame::GetFrameName(nsAString& aResult) const +{ + MakeFrameName(NS_LITERAL_STRING("TextBox"), aResult); + aResult += NS_LITERAL_STRING("[value=") + mTitle + NS_LITERAL_STRING("]"); + return NS_OK; +} +#endif + +// If you make changes to this function, check its counterparts +// in nsBoxFrame and nsXULLabelFrame +nsresult +nsTextBoxFrame::RegUnregAccessKey(bool aDoReg) +{ + // if we have no content, we can't do anything + if (!mContent) + return NS_ERROR_FAILURE; + + // check if we have a |control| attribute + // do this check first because few elements have control attributes, and we + // can weed out most of the elements quickly. + + // XXXjag a side-effect is that we filter out anonymous <label>s + // in e.g. <menu>, <menuitem>, <button>. These <label>s inherit + // |accesskey| and would otherwise register themselves, overwriting + // the content we really meant to be registered. + if (!mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::control)) + return NS_OK; + + // see if we even have an access key + nsAutoString accessKey; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey); + + if (accessKey.IsEmpty()) + return NS_OK; + + // With a valid PresContext we can get the ESM + // and (un)register the access key + nsEventStateManager *esm = PresContext()->EventStateManager(); + + uint32_t key = accessKey.First(); + if (aDoReg) + esm->RegisterAccessKey(mContent, key); + else + esm->UnregisterAccessKey(mContent, key); + + return NS_OK; +} diff --git a/layout/xul/base/src/nsTextBoxFrame.h b/layout/xul/base/src/nsTextBoxFrame.h new file mode 100644 index 000000000..d0286bcb7 --- /dev/null +++ b/layout/xul/base/src/nsTextBoxFrame.h @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTextBoxFrame_h___ +#define nsTextBoxFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsLeafBoxFrame.h" + +class nsAccessKeyInfo; +class nsAsyncAccesskeyUpdate; + +typedef nsLeafBoxFrame nsTextBoxFrameSuper; +class nsTextBoxFrame : public nsTextBoxFrameSuper +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsTextBoxFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nscoord GetBoxAscent(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void MarkIntrinsicWidthsDirty() MOZ_OVERRIDE; + + enum CroppingStyle { CropNone, CropLeft, CropRight, CropCenter }; + + friend nsIFrame* NS_NewTextBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* asPrevInFlow) MOZ_OVERRIDE; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE; +#endif + + void UpdateAttributes(nsIAtom* aAttribute, + bool& aResize, + bool& aRedraw); + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + virtual ~nsTextBoxFrame(); + + void PaintTitle(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, + const nscolor* aOverrideColor); + + nsRect GetComponentAlphaBounds(); + + virtual bool ComputesOwnOverflowArea() MOZ_OVERRIDE; + + void GetCroppedTitle(nsString& aTitle) const { aTitle = mCroppedTitle; } + +protected: + friend class nsAsyncAccesskeyUpdate; + friend class nsDisplayXULTextBox; + // Should be called only by nsAsyncAccesskeyUpdate. + // Returns true if accesskey was updated. + bool UpdateAccesskey(nsWeakFrame& aWeakThis); + void UpdateAccessTitle(); + void UpdateAccessIndex(); + + // REVIEW: SORRY! Couldn't resist devirtualizing these + void LayoutTitle(nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aRect); + + void CalculateUnderline(nsRenderingContext& aRenderingContext); + + void CalcTextSize(nsBoxLayoutState& aBoxLayoutState); + + void CalcDrawRect(nsRenderingContext &aRenderingContext); + + nsTextBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext); + + nscoord CalculateTitleForWidth(nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + nscoord aWidth); + + void GetTextSize(nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsString& aString, + nsSize& aSize, + nscoord& aAscent); + + nsresult RegUnregAccessKey(bool aDoReg); + +private: + + bool AlwaysAppendAccessKey(); + bool InsertSeparatorBeforeAccessKey(); + + void DrawText(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + const nsRect& aTextRect, + const nscolor* aOverrideColor); + + nsString mTitle; + nsString mCroppedTitle; + nsString mAccessKey; + nsSize mTextSize; + nsRect mTextDrawRect; + nsAccessKeyInfo* mAccessKeyInfo; + + CroppingStyle mCropType; + nscoord mAscent; + bool mNeedsRecalc; + bool mNeedsReflowCallback; + + static bool gAlwaysAppendAccessKey; + static bool gAccessKeyPrefInitialized; + static bool gInsertSeparatorBeforeAccessKey; + static bool gInsertSeparatorPrefInitialized; + +}; // class nsTextBoxFrame + +#endif /* nsTextBoxFrame_h___ */ diff --git a/layout/xul/base/src/nsTitleBarFrame.cpp b/layout/xul/base/src/nsTitleBarFrame.cpp new file mode 100644 index 000000000..3b712c1f7 --- /dev/null +++ b/layout/xul/base/src/nsTitleBarFrame.cpp @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsTitleBarFrame.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMNodeList.h" +#include "nsGkAtoms.h" +#include "nsIWidget.h" +#include "nsMenuPopupFrame.h" +#include "nsPresContext.h" +#include "nsIDocShellTreeItem.h" +#include "nsPIDOMWindow.h" +#include "nsGUIEvent.h" +#include "nsEventDispatcher.h" +#include "nsDisplayList.h" +#include "nsContentUtils.h" + +// +// NS_NewTitleBarFrame +// +// Creates a new TitleBar frame and returns it +// +nsIFrame* +NS_NewTitleBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTitleBarFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTitleBarFrame) + +nsTitleBarFrame::nsTitleBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +:nsBoxFrame(aPresShell, aContext, false) +{ + mTrackingMouseMove = false; + UpdateMouseThrough(); +} + +void +nsTitleBarFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // override, since we don't want children to get events + if (aBuilder->IsForEventDelivery()) { + if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::allowevents, + nsGkAtoms::_true, eCaseMatters)) + return; + } + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); +} + +NS_IMETHODIMP +nsTitleBarFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + bool doDefault = true; + + switch (aEvent->message) { + + case NS_MOUSE_BUTTON_DOWN: { + if (aEvent->eventStructType == NS_MOUSE_EVENT && + static_cast<nsMouseEvent*>(aEvent)->button == + nsMouseEvent::eLeftButton) + { + // titlebar has no effect in non-chrome shells + nsCOMPtr<nsISupports> cont = aPresContext->GetContainer(); + nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont); + if (dsti) { + int32_t type = -1; + if (NS_SUCCEEDED(dsti->GetItemType(&type)) && + type == nsIDocShellTreeItem::typeChrome) { + // we're tracking. + mTrackingMouseMove = true; + + // start capture. + nsIPresShell::SetCapturingContent(GetContent(), CAPTURE_IGNOREALLOWED); + + // remember current mouse coordinates. + mLastPoint = aEvent->refPoint; + } + } + + *aEventStatus = nsEventStatus_eConsumeNoDefault; + doDefault = false; + } + } + break; + + + case NS_MOUSE_BUTTON_UP: { + if(mTrackingMouseMove && aEvent->eventStructType == NS_MOUSE_EVENT && + static_cast<nsMouseEvent*>(aEvent)->button == + nsMouseEvent::eLeftButton) + { + // we're done tracking. + mTrackingMouseMove = false; + + // end capture + nsIPresShell::SetCapturingContent(nullptr, 0); + + *aEventStatus = nsEventStatus_eConsumeNoDefault; + doDefault = false; + } + } + break; + + case NS_MOUSE_MOVE: { + if(mTrackingMouseMove) + { + nsIntPoint nsMoveBy = aEvent->refPoint - mLastPoint; + + nsIFrame* parent = GetParent(); + while (parent) { + nsMenuPopupFrame* popupFrame = do_QueryFrame(parent); + if (popupFrame) + break; + parent = parent->GetParent(); + } + + // if the titlebar is in a popup, move the popup frame, otherwise + // move the widget associated with the window + if (parent) { + nsMenuPopupFrame* menuPopupFrame = static_cast<nsMenuPopupFrame*>(parent); + nsCOMPtr<nsIWidget> widget = menuPopupFrame->GetWidget(); + nsIntRect bounds; + widget->GetScreenBounds(bounds); + menuPopupFrame->MoveTo(bounds.x + nsMoveBy.x, bounds.y + nsMoveBy.y, false); + } + else { + nsIPresShell* presShell = aPresContext->PresShell(); + nsPIDOMWindow *window = presShell->GetDocument()->GetWindow(); + if (window) { + window->MoveBy(nsMoveBy.x, nsMoveBy.y); + } + } + + *aEventStatus = nsEventStatus_eConsumeNoDefault; + + doDefault = false; + } + } + break; + + + + case NS_MOUSE_CLICK: + if (NS_IS_MOUSE_LEFT_CLICK(aEvent)) + { + MouseClicked(aPresContext, aEvent); + } + break; + } + + if ( doDefault ) + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + else + return NS_OK; +} + +void +nsTitleBarFrame::MouseClicked(nsPresContext* aPresContext, nsGUIEvent* aEvent) +{ + // Execute the oncommand event handler. + nsContentUtils::DispatchXULCommand(mContent, + aEvent && aEvent->mFlags.mIsTrusted); +} diff --git a/layout/xul/base/src/nsTitleBarFrame.h b/layout/xul/base/src/nsTitleBarFrame.h new file mode 100644 index 000000000..36b4ebf0e --- /dev/null +++ b/layout/xul/base/src/nsTitleBarFrame.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTitleBarFrame_h___ +#define nsTitleBarFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +class nsTitleBarFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewTitleBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + nsTitleBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + virtual void MouseClicked(nsPresContext* aPresContext, nsGUIEvent* aEvent); + + void UpdateMouseThrough() MOZ_OVERRIDE { AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); } + +protected: + bool mTrackingMouseMove; + nsIntPoint mLastPoint; + +}; // class nsTitleBarFrame + +#endif /* nsTitleBarFrame_h___ */ diff --git a/layout/xul/base/src/nsXULLabelFrame.cpp b/layout/xul/base/src/nsXULLabelFrame.cpp new file mode 100644 index 000000000..d2d944472 --- /dev/null +++ b/layout/xul/base/src/nsXULLabelFrame.cpp @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* derived class of nsBlockFrame used for xul:label elements */ + +#include "nsXULLabelFrame.h" +#include "nsHTMLParts.h" +#include "nsINameSpaceManager.h" +#include "nsEventStateManager.h" + +nsIFrame* +NS_NewXULLabelFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + nsXULLabelFrame* it = new (aPresShell) nsXULLabelFrame(aContext); + + if (it) + it->SetFlags(NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT); + + return it; +} + +NS_IMPL_FRAMEARENA_HELPERS(nsXULLabelFrame) + +// If you make changes to this function, check its counterparts +// in nsBoxFrame and nsTextBoxFrame +nsresult +nsXULLabelFrame::RegUnregAccessKey(bool aDoReg) +{ + // if we have no content, we can't do anything + if (!mContent) + return NS_ERROR_FAILURE; + + // To filter out <label>s without a control attribute. + // XXXjag a side-effect is that we filter out anonymous <label>s + // in e.g. <menu>, <menuitem>, <button>. These <label>s inherit + // |accesskey| and would otherwise register themselves, overwriting + // the content we really meant to be registered. + if (!mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::control)) + return NS_OK; + + nsAutoString accessKey; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey); + + if (accessKey.IsEmpty()) + return NS_OK; + + // With a valid PresContext we can get the ESM + // and register the access key + nsEventStateManager *esm = PresContext()->EventStateManager(); + + uint32_t key = accessKey.First(); + if (aDoReg) + esm->RegisterAccessKey(mContent, key); + else + esm->UnregisterAccessKey(mContent, key); + + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// nsIFrame + +void +nsXULLabelFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBlockFrame::Init(aContent, aParent, aPrevInFlow); + + // register access key + RegUnregAccessKey(true); +} + +void +nsXULLabelFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // unregister access key + RegUnregAccessKey(false); + nsBlockFrame::DestroyFrom(aDestructRoot); +} + +NS_IMETHODIMP +nsXULLabelFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsBlockFrame::AttributeChanged(aNameSpaceID, + aAttribute, aModType); + + // If the accesskey changed, register for the new value + // The old value has been unregistered in nsXULElement::SetAttr + if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control) + RegUnregAccessKey(true); + + return rv; +} + +nsIAtom* +nsXULLabelFrame::GetType() const +{ + return nsGkAtoms::XULLabelFrame; +} + +///////////////////////////////////////////////////////////////////////////// +// Diagnostics + +#ifdef DEBUG +NS_IMETHODIMP +nsXULLabelFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("XULLabel"), aResult); +} +#endif diff --git a/layout/xul/base/src/nsXULLabelFrame.h b/layout/xul/base/src/nsXULLabelFrame.h new file mode 100644 index 000000000..4f13124cb --- /dev/null +++ b/layout/xul/base/src/nsXULLabelFrame.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* derived class of nsBlockFrame used for xul:label elements */ + +#ifndef nsXULLabelFrame_h_ +#define nsXULLabelFrame_h_ + +#include "mozilla/Attributes.h" +#include "nsBlockFrame.h" + +#ifndef MOZ_XUL +#error "This file should not be included" +#endif + +class nsXULLabelFrame : public nsBlockFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewXULLabelFrame(nsIPresShell* aPresShell, + nsStyleContext *aContext); + + // nsIFrame + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::XULLabelFrame + */ + virtual nsIAtom* GetType() const MOZ_OVERRIDE; + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE; +#endif + +protected: + nsXULLabelFrame(nsStyleContext *aContext) : nsBlockFrame(aContext) {} + + nsresult RegUnregAccessKey(bool aDoReg); +}; + +nsIFrame* +NS_NewXULLabelFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +#endif /* !defined(nsXULLabelFrame_h_) */ diff --git a/layout/xul/base/src/nsXULPopupManager.cpp b/layout/xul/base/src/nsXULPopupManager.cpp new file mode 100644 index 000000000..1477cf9cf --- /dev/null +++ b/layout/xul/base/src/nsXULPopupManager.cpp @@ -0,0 +1,2340 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsGkAtoms.h" +#include "nsXULPopupManager.h" +#include "nsMenuFrame.h" +#include "nsMenuPopupFrame.h" +#include "nsMenuBarFrame.h" +#include "nsIPopupBoxObject.h" +#include "nsMenuBarListener.h" +#include "nsContentUtils.h" +#include "nsIDOMDocument.h" +#include "nsDOMEvent.h" +#include "nsIDOMEvent.h" +#include "nsIDOMXULElement.h" +#include "nsIXULDocument.h" +#include "nsIXULTemplateBuilder.h" +#include "nsEventDispatcher.h" +#include "nsEventStateManager.h" +#include "nsCSSFrameConstructor.h" +#include "nsLayoutUtils.h" +#include "nsViewManager.h" +#include "nsIComponentManager.h" +#include "nsITimer.h" +#include "nsFocusManager.h" +#include "nsIDocShell.h" +#include "nsPIDOMWindow.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIBaseWindow.h" +#include "nsIDOMMouseEvent.h" +#include "nsCaret.h" +#include "nsIDocument.h" +#include "nsPIWindowRoot.h" +#include "nsFrameManager.h" +#include "nsIObserverService.h" +#include "mozilla/dom/Element.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Services.h" + +using namespace mozilla; +using namespace mozilla::dom; + +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 + eNavigationDirection_End, // NS_VK_RIGHT + eNavigationDirection_After // NS_VK_DOWN + }, + { + eNavigationDirection_Last, // NS_VK_END + eNavigationDirection_First, // NS_VK_HOME + eNavigationDirection_End, // NS_VK_LEFT + eNavigationDirection_Before, // NS_VK_UP + eNavigationDirection_Start, // NS_VK_RIGHT + eNavigationDirection_After // NS_VK_DOWN + } +}; + +nsXULPopupManager* nsXULPopupManager::sInstance = nullptr; + +nsIContent* nsMenuChainItem::Content() +{ + return mFrame->GetContent(); +} + +void nsMenuChainItem::SetParent(nsMenuChainItem* aParent) +{ + if (mParent) { + NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this"); + mParent->mChild = nullptr; + } + mParent = aParent; + if (mParent) { + if (mParent->mChild) + mParent->mChild->mParent = nullptr; + mParent->mChild = this; + } +} + +void nsMenuChainItem::Detach(nsMenuChainItem** aRoot) +{ + // If the item has a child, set the child's parent to this item's parent, + // effectively removing the item from the chain. If the item has no child, + // just set the parent to null. + if (mChild) { + NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain"); + mChild->SetParent(mParent); + } + else { + // An item without a child should be the first item in the chain, so set + // the first item pointer, pointed to by aRoot, to the parent. + NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain"); + *aRoot = mParent; + SetParent(nullptr); + } +} + +NS_IMPL_ISUPPORTS3(nsXULPopupManager, + nsIDOMEventListener, + nsITimerCallback, + nsIObserver) + +nsXULPopupManager::nsXULPopupManager() : + mRangeOffset(0), + mCachedMousePoint(0, 0), + mCachedModifiers(0), + mActiveMenuBar(nullptr), + mPopups(nullptr), + mNoHidePanels(nullptr), + mTimerMenu(nullptr) +{ + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "xpcom-shutdown", false); + } +} + +nsXULPopupManager::~nsXULPopupManager() +{ + NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open"); +} + +nsresult +nsXULPopupManager::Init() +{ + sInstance = new nsXULPopupManager(); + NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(sInstance); + return NS_OK; +} + +void +nsXULPopupManager::Shutdown() +{ + NS_IF_RELEASE(sInstance); +} + +NS_IMETHODIMP +nsXULPopupManager::Observe(nsISupports *aSubject, + const char *aTopic, + const PRUnichar *aData) +{ + if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { + if (mKeyListener) { + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true); + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true); + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true); + mKeyListener = nullptr; + } + mRangeParent = nullptr; + // mOpeningPopup is cleared explicitly soon after using it. + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "xpcom-shutdown"); + } + } + + return NS_OK; +} + +nsXULPopupManager* +nsXULPopupManager::GetInstance() +{ + MOZ_ASSERT(sInstance); + return sInstance; +} + +bool +nsXULPopupManager::Rollup(uint32_t aCount, nsIContent** aLastRolledUp) +{ + bool consume = false; + + nsMenuChainItem* item = GetTopVisibleMenu(); + if (item) { + if (aLastRolledUp) { + // we need to get the popup that will be closed last, so that + // widget can keep track of it so it doesn't reopen if a mouse + // down event is going to processed. + // Keep going up the menu chain to get the first level menu. This will + // be the one that closes up last. It's possible that this menu doesn't + // end up closing because the popuphiding event was cancelled, but in + // that case we don't need to deal with the menu reopening as it will + // already still be open. + nsMenuChainItem* first = item; + while (first->GetParent()) + first = first->GetParent(); + *aLastRolledUp = first->Content(); + } + + consume = item->Frame()->ConsumeOutsideClicks(); + + // if a number of popups to close has been specified, determine the last + // popup to close + nsIContent* lastPopup = nullptr; + if (aCount != UINT32_MAX) { + nsMenuChainItem* last = item; + while (--aCount && last->GetParent()) { + last = last->GetParent(); + } + if (last) { + lastPopup = last->Content(); + } + } + + HidePopup(item->Content(), true, true, false, lastPopup); + } + + return consume; +} + +//////////////////////////////////////////////////////////////////////// +bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent() +{ + // should rollup only for autocomplete widgets + // XXXndeakin this should really be something the popup has more control over + + nsMenuChainItem* item = GetTopVisibleMenu(); + if (!item) + return false; + + nsIContent* content = item->Frame()->GetContent(); + if (!content) + return false; + + if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel, + nsGkAtoms::_true, eCaseMatters)) + return true; + + if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel, + nsGkAtoms::_false, eCaseMatters)) + return false; + + nsAutoString value; + content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value); + return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete")); +} + +bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent() +{ + nsMenuChainItem* item = GetTopVisibleMenu(); + if (!item) + return false; + + nsMenuPopupFrame* frame = item->Frame(); + if (frame->PopupType() != ePopupTypePanel) + return true; + + nsIContent* content = frame->GetContent(); + return !(content && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::arrow, eCaseMatters)); +} + +// a menu should not roll up if activated by a mouse activate message (eg. X-mouse) +bool nsXULPopupManager::ShouldRollupOnMouseActivate() +{ + return false; +} + +uint32_t +nsXULPopupManager::GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain) +{ + // this method is used by the widget code to determine the list of popups + // that are open. If a mouse click occurs outside one of these popups, the + // panels will roll up. If the click is inside a popup, they will not roll up + uint32_t count = 0, sameTypeCount = 0; + + NS_ASSERTION(aWidgetChain, "null parameter"); + nsMenuChainItem* item = GetTopVisibleMenu(); + while (item) { + nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget(); + NS_ASSERTION(widget, "open popup has no widget"); + aWidgetChain->AppendElement(widget.get()); + // In the case when a menulist inside a panel is open, clicking in the + // panel should still roll up the menu, so if a different type is found, + // stop scanning. + nsMenuChainItem* parent = item->GetParent(); + if (!sameTypeCount) { + count++; + if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() || + item->IsContextMenu() != parent->IsContextMenu()) { + sameTypeCount = count; + } + } + item = parent; + } + + return sameTypeCount; +} + +nsIWidget* +nsXULPopupManager::GetRollupWidget() +{ + nsMenuChainItem* item = GetTopVisibleMenu(); + return item ? item->Frame()->GetWidget() : nullptr; +} + +void +nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindow* aWindow) +{ + // When the parent window is moved, adjust any child popups. Dismissable + // menus and panels are expected to roll up when a window is moved, so there + // is no need to check these popups, only the noautohide popups. + nsMenuChainItem* item = mNoHidePanels; + while (item) { + // only move popups that are within the same window and where auto + // positioning has not been disabled + nsMenuPopupFrame* frame= item->Frame(); + if (frame->GetAutoPosition()) { + nsIContent* popup = frame->GetContent(); + if (popup) { + nsIDocument* document = popup->GetCurrentDoc(); + if (document) { + nsPIDOMWindow* window = document->GetWindow(); + if (window) { + window = window->GetPrivateRoot(); + if (window == aWindow) { + frame->SetPopupPosition(nullptr, true); + } + } + } + } + } + + item = item->GetParent(); + } +} + +static +nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame) +{ + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame); + if (!menuPopupFrame) + return nullptr; + + // no point moving or resizing hidden popups + if (menuPopupFrame->PopupState() != ePopupOpenAndVisible) + return nullptr; + + nsIWidget* widget = menuPopupFrame->GetWidget(); + if (widget && !widget->IsVisible()) + return nullptr; + + return menuPopupFrame; +} + +void +nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt) +{ + nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame); + if (!menuPopupFrame) + return; + + // Convert desired point to CSS pixels for comparison + nsPresContext* presContext = menuPopupFrame->PresContext(); + aPnt.x = presContext->DevPixelsToIntCSSPixels(aPnt.x); + aPnt.y = presContext->DevPixelsToIntCSSPixels(aPnt.y); + + // Don't do anything if the popup is already at the specified location. This + // prevents recursive calls when a popup is positioned. + nsIntPoint currentPnt = menuPopupFrame->ScreenPosition(); + nsIWidget* widget = menuPopupFrame->GetWidget(); + if ((aPnt.x != currentPnt.x || aPnt.y != currentPnt.y) || (widget && + widget->GetClientOffset() != menuPopupFrame->GetLastClientOffset())) { + // Update the popup's position using SetPopupPosition if the popup is + // anchored and at the parent level as these maintain their position + // relative to the parent window. Otherwise, just update the popup to + // the specified screen coordinates. + if (menuPopupFrame->IsAnchored() && + menuPopupFrame->PopupLevel() == ePopupLevelParent) { + menuPopupFrame->SetPopupPosition(nullptr, true); + } + else { + menuPopupFrame->MoveTo(aPnt.x, aPnt.y, false); + } + } +} + +void +nsXULPopupManager::PopupResized(nsIFrame* aFrame, nsIntSize aSize) +{ + nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame); + if (!menuPopupFrame) + return; + + nsView* view = menuPopupFrame->GetView(); + if (!view) + return; + + nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup); + // If the size is what we think it is, we have nothing to do. + if (curDevSize.width == aSize.width && curDevSize.height == aSize.height) + return; + + // The size is different. Convert the actual size to css pixels and store it + // as 'width' and 'height' attributes on the popup. + nsPresContext* presContext = menuPopupFrame->PresContext(); + + nsIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width), + presContext->DevPixelsToIntCSSPixels(aSize.height)); + + nsIContent* popup = menuPopupFrame->GetContent(); + nsAutoString width, height; + width.AppendInt(newCSS.width); + height.AppendInt(newCSS.height); + popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false); + popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true); +} + +nsMenuPopupFrame* +nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush) +{ + if (aShouldFlush) { + nsIDocument *document = aContent->GetCurrentDoc(); + if (document) { + nsCOMPtr<nsIPresShell> presShell = document->GetShell(); + if (presShell) + presShell->FlushPendingNotifications(Flush_Layout); + } + } + + return do_QueryFrame(aContent->GetPrimaryFrame()); +} + +nsMenuChainItem* +nsXULPopupManager::GetTopVisibleMenu() +{ + nsMenuChainItem* item = mPopups; + while (item && item->Frame()->PopupState() == ePopupInvisible) + item = item->GetParent(); + return item; +} + +void +nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset) +{ + *aNode = mRangeParent; + NS_IF_ADDREF(*aNode); + *aOffset = mRangeOffset; +} + +void +nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup, + nsIContent** aTriggerContent) +{ + mCachedMousePoint = nsIntPoint(0, 0); + + if (aTriggerContent) { + *aTriggerContent = nullptr; + if (aEvent) { + // get the trigger content from the event + nsCOMPtr<nsIContent> target = do_QueryInterface( + aEvent->InternalDOMEvent()->GetTarget()); + target.forget(aTriggerContent); + } + } + + mCachedModifiers = 0; + + nsCOMPtr<nsIDOMUIEvent> 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. + NS_ASSERTION(aPopup, "Expected a popup node"); + nsEvent* event = aEvent->GetInternalNSEvent(); + if (event) { + if (event->eventStructType == NS_MOUSE_EVENT || + event->eventStructType == NS_KEY_EVENT) { + mCachedModifiers = static_cast<nsInputEvent*>(event)->modifiers; + } + nsIDocument* doc = aPopup->GetCurrentDoc(); + if (doc) { + nsIPresShell* presShell = doc->GetShell(); + nsPresContext* presContext; + if (presShell && (presContext = presShell->GetPresContext())) { + nsPresContext* rootDocPresContext = + presContext->GetRootPresContext(); + if (!rootDocPresContext) + return; + nsIFrame* rootDocumentRootFrame = rootDocPresContext-> + PresShell()->FrameManager()->GetRootFrame(); + if ((event->eventStructType == NS_MOUSE_EVENT || + event->eventStructType == NS_MOUSE_SCROLL_EVENT || + event->eventStructType == NS_WHEEL_EVENT) && + !(static_cast<nsGUIEvent *>(event))->widget) { + // no widget, so just use the client point if available + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent); + nsIntPoint clientPt; + mouseEvent->GetClientX(&clientPt.x); + mouseEvent->GetClientY(&clientPt.y); + + // XXX this doesn't handle IFRAMEs in transforms + nsPoint thisDocToRootDocOffset = presShell->FrameManager()-> + GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame); + // convert to device pixels + mCachedMousePoint.x = presContext->AppUnitsToDevPixels( + nsPresContext::CSSPixelsToAppUnits(clientPt.x) + thisDocToRootDocOffset.x); + mCachedMousePoint.y = presContext->AppUnitsToDevPixels( + nsPresContext::CSSPixelsToAppUnits(clientPt.y) + thisDocToRootDocOffset.y); + } + else if (rootDocumentRootFrame) { + nsPoint pnt = + nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame); + mCachedMousePoint = nsIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x), + rootDocPresContext->AppUnitsToDevPixels(pnt.y)); + } + } + } + } + } + else { + mRangeParent = nullptr; + mRangeOffset = 0; + } +} + +void +nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate) +{ + if (aActivate) + mActiveMenuBar = aMenuBar; + else if (mActiveMenuBar == aMenuBar) + mActiveMenuBar = nullptr; + + UpdateKeyboardListeners(); +} + +void +nsXULPopupManager::ShowMenu(nsIContent *aMenu, + bool aSelectFirstItem, + bool aAsynchronous) +{ + // generate any template content first. Otherwise, the menupopup may not + // have been created yet. + if (aMenu) { + nsIContent* element = aMenu; + do { + nsCOMPtr<nsIDOMXULElement> xulelem = do_QueryInterface(element); + if (xulelem) { + nsCOMPtr<nsIXULTemplateBuilder> builder; + xulelem->GetBuilder(getter_AddRefs(builder)); + if (builder) { + builder->CreateContents(aMenu, true); + break; + } + } + element = element->GetParent(); + } while (element); + } + + nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame()); + if (!menuFrame || !menuFrame->IsMenu()) + return; + + nsMenuPopupFrame* popupFrame = menuFrame->GetPopup(); + if (!popupFrame || !MayShowPopup(popupFrame)) + return; + + // inherit whether or not we're a context menu from the parent + bool parentIsContextMenu = false; + bool onMenuBar = false; + bool onmenu = menuFrame->IsOnMenu(); + + nsMenuParent* parent = menuFrame->GetMenuParent(); + if (parent && onmenu) { + parentIsContextMenu = parent->IsContextMenu(); + onMenuBar = parent->IsMenuBar(); + } + + nsAutoString position; + if (onMenuBar || !onmenu) + position.AssignLiteral("after_start"); + else + position.AssignLiteral("end_before"); + + // there is no trigger event for menus + InitTriggerEvent(nullptr, nullptr, nullptr); + popupFrame->InitializePopup(aMenu, nullptr, position, 0, 0, true); + + if (aAsynchronous) { + nsCOMPtr<nsIRunnable> event = + new nsXULPopupShowingEvent(popupFrame->GetContent(), + parentIsContextMenu, aSelectFirstItem); + NS_DispatchToCurrentThread(event); + } + else { + nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent(); + FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem); + } +} + +void +nsXULPopupManager::ShowPopup(nsIContent* aPopup, + nsIContent* aAnchorContent, + const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + bool aAttributesOverride, + bool aSelectFirstItem, + nsIDOMEvent* aTriggerEvent) +{ + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); + if (!popupFrame || !MayShowPopup(popupFrame)) + return; + + nsCOMPtr<nsIContent> triggerContent; + InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent)); + + popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition, + aXPos, aYPos, aAttributesOverride); + + FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem); +} + +void +nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + nsIDOMEvent* aTriggerEvent) +{ + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); + if (!popupFrame || !MayShowPopup(popupFrame)) + return; + + nsCOMPtr<nsIContent> triggerContent; + InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent)); + + popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu); + FirePopupShowingEvent(aPopup, aIsContextMenu, false); +} + +void +nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup, + nsIContent* aTriggerContent, + int32_t aXPos, int32_t aYPos) +{ + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); + if (!popupFrame || !MayShowPopup(popupFrame)) + return; + + InitTriggerEvent(nullptr, nullptr, nullptr); + + nsPresContext* pc = popupFrame->PresContext(); + mCachedMousePoint = nsIntPoint(pc->CSSPixelsToDevPixels(aXPos), + pc->CSSPixelsToDevPixels(aYPos)); + + // coordinates are relative to the root widget + nsPresContext* rootPresContext = pc->GetRootPresContext(); + if (rootPresContext) { + nsIWidget *rootWidget = rootPresContext->GetRootWidget(); + if (rootWidget) { + mCachedMousePoint -= rootWidget->WidgetToScreenOffset(); + } + } + + popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false); + + FirePopupShowingEvent(aPopup, false, false); +} + +void +nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup, + nsIContent* aAnchorContent, + nsAString& aAnchor, + nsAString& aAlign, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu) +{ + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); + if (!popupFrame || !MayShowPopup(popupFrame)) + return; + + InitTriggerEvent(nullptr, nullptr, nullptr); + + popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor, + aAlign, aXPos, aYPos); + FirePopupShowingEvent(aPopup, aIsContextMenu, false); +} + +static void +CheckCaretDrawingState() { + + // There is 1 caret per document, we need to find the focused + // document and erase its caret. + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsCOMPtr<nsIDOMWindow> window; + fm->GetFocusedWindow(getter_AddRefs(window)); + if (!window) + return; + + nsCOMPtr<nsIDOMDocument> domDoc; + nsCOMPtr<nsIDocument> focusedDoc; + window->GetDocument(getter_AddRefs(domDoc)); + focusedDoc = do_QueryInterface(domDoc); + if (!focusedDoc) + return; + + nsIPresShell* presShell = focusedDoc->GetShell(); + if (!presShell) + return; + + nsRefPtr<nsCaret> caret = presShell->GetCaret(); + if (!caret) + return; + caret->CheckCaretDrawingState(); + } +} + +void +nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup, + nsMenuPopupFrame* aPopupFrame, + bool aIsContextMenu, + bool aSelectFirstItem) +{ + nsPopupType popupType = aPopupFrame->PopupType(); + bool ismenu = (popupType == ePopupTypeMenu); + + nsMenuChainItem* item = + new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType); + if (!item) + return; + + // install keyboard event listeners for navigating menus. For panels, the + // escape key may be used to close the panel. However, the ignorekeys + // 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(true); + + if (ismenu) { + // if the menu is on a menubar, use the menubar's listener instead + nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent()); + 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); + aPopupFrame->ShowPopup(aIsContextMenu, aSelectFirstItem); + ENSURE_TRUE(weakFrame.IsAlive()); + + // popups normally hide when an outside click occurs. Panels may use + // the noautohide attribute to disable this behaviour. It is expected + // that the application will hide these popups manually. The tooltip + // listener will handle closing the tooltip also. + if (aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip) { + item->SetParent(mNoHidePanels); + mNoHidePanels = item; + } + else { + nsIContent* oldmenu = nullptr; + if (mPopups) + oldmenu = mPopups->Content(); + item->SetParent(mPopups); + mPopups = item; + SetCaptureState(oldmenu); + } + + if (aSelectFirstItem) { + nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true); + aPopupFrame->SetCurrentMenuItem(next); + } + + if (ismenu) + UpdateMenuItems(aPopup); + + // Caret visibility may have been affected, ensure that + // the caret isn't now drawn when it shouldn't be. + CheckCaretDrawingState(); +} + +void +nsXULPopupManager::HidePopup(nsIContent* aPopup, + bool aHideChain, + bool aDeselectMenu, + bool aAsynchronous, + nsIContent* aLastPopup) +{ + // if the popup is on the nohide panels list, remove it but don't close any + // other panels + nsMenuPopupFrame* popupFrame = nullptr; + bool foundPanel = false; + nsMenuChainItem* item = mNoHidePanels; + while (item) { + if (item->Content() == aPopup) { + foundPanel = true; + popupFrame = item->Frame(); + break; + } + item = item->GetParent(); + } + + // when removing a menu, all of the child popups must be closed + nsMenuChainItem* foundMenu = nullptr; + item = mPopups; + while (item) { + if (item->Content() == aPopup) { + foundMenu = item; + break; + } + item = item->GetParent(); + } + + nsPopupType type = ePopupTypePanel; + bool deselectMenu = false; + nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup; + if (foundMenu) { + // at this point, foundMenu will be set to the found item in the list. If + // foundMenu is the topmost menu, the one to remove, then there are no other + // popups to hide. If foundMenu is not the topmost menu, then there may be + // open submenus below it. In this case, we need to make sure that those + // submenus are closed up first. To do this, we scan up the menu list to + // find the topmost popup with only menus between it and foundMenu and + // close that menu first. In synchronous mode, the FirePopupHidingEvent + // method will be called which in turn calls HidePopupCallback to close up + // the next popup in the chain. These two methods will be called in + // sequence recursively to close up all the necessary popups. In + // asynchronous mode, a similar process occurs except that the + // FirePopupHidingEvent method is called asynchronously. In either case, + // nextPopup is set to the content node of the next popup to close, and + // lastPopup is set to the last popup in the chain to close, which will be + // aPopup, or null to close up all menus. + + nsMenuChainItem* topMenu = foundMenu; + // Use IsMenu to ensure that foundMenu is a menu and scan down the child + // list until a non-menu is found. If foundMenu isn't a menu at all, don't + // scan and just close up this menu. + if (foundMenu->IsMenu()) { + item = topMenu->GetChild(); + while (item && item->IsMenu()) { + topMenu = item; + item = item->GetChild(); + } + } + + deselectMenu = aDeselectMenu; + popupToHide = topMenu->Content(); + popupFrame = topMenu->Frame(); + type = popupFrame->PopupType(); + + nsMenuChainItem* parent = topMenu->GetParent(); + + // close up another popup if there is one, and we are either hiding the + // entire chain or the item to hide isn't the topmost popup. + if (parent && (aHideChain || topMenu != foundMenu)) + nextPopup = parent->Content(); + + lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup); + } + else if (foundPanel) { + popupToHide = aPopup; + } + + if (popupFrame) { + nsPopupState state = popupFrame->PopupState(); + // if the popup is already being hidden, don't attempt to hide it again + 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 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, + popupFrame->PresContext(), type, deselectMenu); + } + } +} + +void +nsXULPopupManager::HidePopupCallback(nsIContent* aPopup, + nsMenuPopupFrame* aPopupFrame, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPopupType aPopupType, + bool aDeselectMenu) +{ + if (mCloseTimer && mTimerMenu == aPopupFrame) { + mCloseTimer->Cancel(); + mCloseTimer = nullptr; + mTimerMenu = nullptr; + } + + // The popup to hide is aPopup. Search the list again to find the item that + // corresponds to the popup to hide aPopup. This is done because it's + // possible someone added another item (attempted to open another popup) + // or removed a popup frame during the event processing so the item isn't at + // the front anymore. + nsMenuChainItem* item = mNoHidePanels; + while (item) { + if (item->Content() == aPopup) { + item->Detach(&mNoHidePanels); + break; + } + item = item->GetParent(); + } + + if (!item) { + item = mPopups; + while (item) { + if (item->Content() == aPopup) { + item->Detach(&mPopups); + SetCaptureState(aPopup); + break; + } + item = item->GetParent(); + } + } + + delete item; + + nsWeakFrame weakFrame(aPopupFrame); + aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed); + ENSURE_TRUE(weakFrame.IsAlive()); + + // send the popuphidden event synchronously. This event has no default behaviour. + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(true, NS_XUL_POPUP_HIDDEN, nullptr, nsMouseEvent::eReal); + nsEventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(), + &event, nullptr, &status); + ENSURE_TRUE(weakFrame.IsAlive()); + + // if there are more popups to close, look for the next one + if (aNextPopup && aPopup != aLastPopup) { + nsMenuChainItem* foundMenu = nullptr; + nsMenuChainItem* item = mPopups; + while (item) { + if (item->Content() == aNextPopup) { + foundMenu = item; + break; + } + item = item->GetParent(); + } + + // continue hiding the chain of popups until the last popup aLastPopup + // is reached, or until a popup of a different type is reached. This + // last check is needed so that a menulist inside a non-menu panel only + // closes the menu and not the panel as well. + if (foundMenu && + (aLastPopup || aPopupType == foundMenu->PopupType())) { + + nsCOMPtr<nsIContent> popupToHide = item->Content(); + nsMenuChainItem* parent = item->GetParent(); + + nsCOMPtr<nsIContent> nextPopup; + if (parent && popupToHide != aLastPopup) + nextPopup = parent->Content(); + + nsMenuPopupFrame* popupFrame = item->Frame(); + nsPopupState state = popupFrame->PopupState(); + if (state == ePopupHiding) + return; + if (state != ePopupInvisible) + popupFrame->SetPopupState(ePopupHiding); + + FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup, + popupFrame->PresContext(), + foundMenu->PopupType(), aDeselectMenu); + } + } +} + +void +nsXULPopupManager::HidePopup(nsIFrame* aFrame) +{ + nsMenuPopupFrame* popup = do_QueryFrame(aFrame); + if (popup) + HidePopup(aFrame->GetContent(), false, true, false); +} + +void +nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup) +{ + // Don't close up immediately. + // Kick off a close timer. + KillMenuTimer(); + + int32_t menuDelay = + LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms + + // Kick off the timer. + mCloseTimer = do_CreateInstance("@mozilla.org/timer;1"); + mCloseTimer->InitWithCallback(this, menuDelay, nsITimer::TYPE_ONE_SHOT); + + // the popup will call PopupDestroyed if it is destroyed, which checks if it + // is set to mTimerMenu, so it should be safe to keep a reference to it + mTimerMenu = aPopup; +} + +void +nsXULPopupManager::HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames, + bool aDeselectMenu) +{ + // Create a weak frame list. This is done in a separate array with the + // right capacity predetermined, otherwise the array would get resized and + // move the weak frame pointers around. + nsTArray<nsWeakFrame> weakPopups(aFrames.Length()); + uint32_t f; + for (f = 0; f < aFrames.Length(); f++) { + nsWeakFrame* wframe = weakPopups.AppendElement(); + if (wframe) + *wframe = aFrames[f]; + } + + for (f = 0; f < weakPopups.Length(); f++) { + // check to ensure that the frame is still alive before hiding it. + if (weakPopups[f].IsAlive()) { + nsMenuPopupFrame* frame = + static_cast<nsMenuPopupFrame *>(weakPopups[f].GetFrame()); + frame->HidePopup(true, ePopupInvisible); + } + } + + SetCaptureState(nullptr); +} + +bool +nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected) +{ + nsCOMPtr<nsISupports> doc = aDoc->GetContainer(); + nsCOMPtr<nsIDocShellTreeItem> docShellItem(do_QueryInterface(doc)); + while(docShellItem) { + if (docShellItem == aExpected) + return true; + + nsCOMPtr<nsIDocShellTreeItem> parent; + docShellItem->GetParent(getter_AddRefs(parent)); + docShellItem = parent; + } + + return false; +} + +void +nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide) +{ + nsTArray<nsMenuPopupFrame *> popupsToHide; + + // iterate to get the set of popup frames to hide + nsMenuChainItem* item = mPopups; + while (item) { + nsMenuChainItem* parent = item->GetParent(); + if (item->Frame()->PopupState() != ePopupInvisible && + IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) { + nsMenuPopupFrame* frame = item->Frame(); + item->Detach(&mPopups); + delete item; + popupsToHide.AppendElement(frame); + } + item = parent; + } + + // now look for panels to hide + item = mNoHidePanels; + while (item) { + nsMenuChainItem* parent = item->GetParent(); + if (item->Frame()->PopupState() != ePopupInvisible && + IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) { + nsMenuPopupFrame* frame = item->Frame(); + item->Detach(&mNoHidePanels); + delete item; + popupsToHide.AppendElement(frame); + } + item = parent; + } + + HidePopupsInList(popupsToHide, true); +} + +void +nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent) +{ + CloseMenuMode cmm = CloseMenuMode_Auto; + + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::none, &nsGkAtoms::single, nullptr}; + + switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu, + strings, eCaseMatters)) { + case 0: + cmm = CloseMenuMode_None; + break; + case 1: + cmm = CloseMenuMode_Single; + break; + default: + break; + } + + // When a menuitem is selected to be executed, first hide all the open + // popups, but don't remove them yet. This is needed when a menu command + // opens a modal dialog. The views associated with the popups needed to be + // hidden and the accesibility events fired before the command executes, but + // the popuphiding/popuphidden events are fired afterwards. + nsTArray<nsMenuPopupFrame *> popupsToHide; + nsMenuChainItem* item = GetTopVisibleMenu(); + if (cmm != CloseMenuMode_None) { + while (item) { + // if it isn't a <menupopup>, don't close it automatically + if (!item->IsMenu()) + break; + nsMenuChainItem* next = item->GetParent(); + popupsToHide.AppendElement(item->Frame()); + if (cmm == CloseMenuMode_Single) // only close one level of menu + break; + item = next; + } + + // Now hide the popups. If the closemenu mode is auto, deselect the menu, + // otherwise only one popup is closing, so keep the parent menu selected. + HidePopupsInList(popupsToHide, cmm == CloseMenuMode_Auto); + } + + aEvent->SetCloseMenuMode(cmm); + nsCOMPtr<nsIRunnable> event = aEvent; + NS_DispatchToCurrentThread(event); +} + +void +nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup, + bool aIsContextMenu, + bool aSelectFirstItem) +{ + nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup + + nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); + if (!popupFrame) + return; + + nsPresContext *presContext = popupFrame->PresContext(); + nsCOMPtr<nsIPresShell> presShell = presContext->PresShell(); + nsPopupType popupType = popupFrame->PopupType(); + + // generate the child frames if they have not already been generated + if (!popupFrame->HasGeneratedChildren()) { + popupFrame->SetGeneratedChildren(); + presShell->FrameConstructor()->GenerateChildFrames(popupFrame); + } + + // get the frame again + nsIFrame* frame = aPopup->GetPrimaryFrame(); + if (!frame) + return; + + presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + + // 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(true, NS_XUL_POPUP_SHOWING, nullptr, nsMouseEvent::eReal); + + // coordinates are relative to the root widget + nsPresContext* rootPresContext = + presShell->GetPresContext()->GetRootPresContext(); + if (rootPresContext) { + rootPresContext->PresShell()->GetViewManager()-> + GetRootWidget(getter_AddRefs(event.widget)); + } + else { + event.widget = nullptr; + } + + event.refPoint = mCachedMousePoint; + event.modifiers = mCachedModifiers; + nsEventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status); + + mCachedMousePoint = nsIntPoint(0, 0); + mOpeningPopup = nullptr; + + mCachedModifiers = 0; + + // 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 (popupType == ePopupTypePanel && + !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus, + nsGkAtoms::_true, eCaseMatters)) { + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsIDocument* doc = popup->GetCurrentDoc(); + + // Only remove the focus if the currently focused item is ouside the + // popup. It isn't a big deal if the current focus is in a child popup + // inside the popup as that shouldn't be visible. This check ensures that + // a node inside the popup that is focused during a popupshowing event + // remains focused. + nsCOMPtr<nsIDOMElement> currentFocusElement; + fm->GetFocusedElement(getter_AddRefs(currentFocusElement)); + nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement); + if (doc && currentFocus && + !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) { + fm->ClearFocus(doc->GetWindow()); + } + } + } + + // clear these as they are no longer valid + mRangeParent = nullptr; + mRangeOffset = 0; + + // get the frame again in case it went away + popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); + if (popupFrame) { + // 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->ClearTriggerContent(); + } + else { + ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem); + } + } +} + +void +nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPresContext *aPresContext, + nsPopupType aPopupType, + bool aDeselectMenu) +{ + nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell(); + + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(true, NS_XUL_POPUP_HIDING, nullptr, nsMouseEvent::eReal); + nsEventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status); + + // when a panel is closed, blur whatever has focus inside the popup + if (aPopupType == ePopupTypePanel && + !aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus, + nsGkAtoms::_true, eCaseMatters)) { + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsIDocument* doc = aPopup->GetCurrentDoc(); + + // Remove the focus from the focused node only if it is inside the popup. + nsCOMPtr<nsIDOMElement> currentFocusElement; + fm->GetFocusedElement(getter_AddRefs(currentFocusElement)); + nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement); + if (doc && currentFocus && + nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) { + fm->ClearFocus(doc->GetWindow()); + } + } + } + + // get frame again in case it went away + nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); + if (popupFrame) { + // 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, + aPopupType, aDeselectMenu); + } + } +} + +bool +nsXULPopupManager::IsPopupOpen(nsIContent* aPopup) +{ + // a popup is open if it is in the open list. The assertions ensure that the + // frame is in the correct state. If the popup is in the hiding or invisible + // state, it will still be in the open popup list until it is closed. + nsMenuChainItem* item = mPopups; + while (item) { + if (item->Content() == aPopup) { + NS_ASSERTION(item->Frame()->IsOpen() || + item->Frame()->PopupState() == ePopupHiding || + item->Frame()->PopupState() == ePopupInvisible, + "popup in open list not actually open"); + return true; + } + item = item->GetParent(); + } + + item = mNoHidePanels; + while (item) { + if (item->Content() == aPopup) { + NS_ASSERTION(item->Frame()->IsOpen() || + item->Frame()->PopupState() == ePopupHiding || + item->Frame()->PopupState() == ePopupInvisible, + "popup in open list not actually open"); + return true; + } + item = item->GetParent(); + } + + return false; +} + +bool +nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent) +{ + nsMenuChainItem* item = GetTopVisibleMenu(); + while (item) { + nsMenuPopupFrame* popup = item->Frame(); + if (popup && popup->IsOpen()) { + nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent()); + if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) { + return true; + } + } + item = item->GetParent(); + } + + return false; +} + +nsIFrame* +nsXULPopupManager::GetTopPopup(nsPopupType aType) +{ + 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(); + } + + return nullptr; +} + +void +nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups) +{ + aPopups.Clear(); + + nsMenuChainItem* item = mPopups; + while (item) { + if (item->Frame()->PopupState() == ePopupOpenAndVisible) + aPopups.AppendElement(static_cast<nsIFrame*>(item->Frame())); + item = item->GetParent(); + } + + item = mNoHidePanels; + while (item) { + // skip panels which are not open and visible as well as draggable popups, + // as those don't respond to events. + if (item->Frame()->PopupState() == ePopupOpenAndVisible && !item->Frame()->IsDragPopup()) { + aPopups.AppendElement(static_cast<nsIFrame*>(item->Frame())); + } + item = item->GetParent(); + } +} + +already_AddRefed<nsIDOMNode> +nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip) +{ + if (!aDocument) + return nullptr; + + 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)) { + node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false))); + } + 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(nsMenuPopupFrame::GetTriggerContent(item->Frame())); + if (node) + break; + } + item = item->GetParent(); + } + } + + return node.forget(); +} + +bool +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"); + + nsPopupState state = aPopup->PopupState(); + + // if the popup is not in the open popup chain, then it must have a state that + // is either closed, in the process of being shown, or invisible. + NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed || + state == ePopupShowing || state == ePopupInvisible, + "popup not in XULPopupManager open list is open"); + + // don't show popups unless they are closed or invisible + if (state != ePopupClosed && state != ePopupInvisible) + return false; + + // Don't show popups that we already have in our popup chain + if (IsPopupOpen(aPopup->GetContent())) { + NS_WARNING("Refusing to show duplicate popup"); + return false; + } + + // if the popup was just rolled up, don't reopen it + nsCOMPtr<nsIWidget> widget = aPopup->GetWidget(); + if (widget && widget->GetLastRollup() == aPopup->GetContent()) + return false; + + nsCOMPtr<nsISupports> cont = aPopup->PresContext()->GetContainer(); + nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont); + nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti); + if (!baseWin) + return false; + + int32_t type = -1; + if (NS_FAILED(dsti->GetItemType(&type))) + return false; + + // chrome shells can always open popups, but other types of shells can only + // open popups when they are focused and visible + if (type != nsIDocShellTreeItem::typeChrome) { + // only allow popups in active windows + nsCOMPtr<nsIDocShellTreeItem> root; + dsti->GetRootTreeItem(getter_AddRefs(root)); + nsCOMPtr<nsIDOMWindow> rootWin = do_GetInterface(root); + + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm || !rootWin) + return false; + + nsCOMPtr<nsIDOMWindow> activeWindow; + fm->GetActiveWindow(getter_AddRefs(activeWindow)); + if (activeWindow != rootWin) + return false; + + // only allow popups in visible frames + bool visible; + baseWin->GetVisibility(&visible); + if (!visible) + return false; + } + + // platforms respond differently when an popup is opened in a minimized + // window, so this is always disabled. + nsCOMPtr<nsIWidget> mainWidget; + baseWin->GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) { + return false; + } + + // cannot open a popup that is a submenu of a menupopup that isn't open. + nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent()); + if (menuFrame) { + nsMenuParent* parentPopup = menuFrame->GetMenuParent(); + if (parentPopup && !parentPopup->IsOpen()) + return false; + } + + return true; +} + +void +nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup) +{ + // when a popup frame is destroyed, just unhook it from the list of popups + if (mTimerMenu == aPopup) { + if (mCloseTimer) { + mCloseTimer->Cancel(); + mCloseTimer = nullptr; + } + mTimerMenu = nullptr; + } + + nsMenuChainItem* item = mNoHidePanels; + while (item) { + if (item->Frame() == aPopup) { + item->Detach(&mNoHidePanels); + delete item; + break; + } + item = item->GetParent(); + } + + nsTArray<nsMenuPopupFrame *> popupsToHide; + + item = mPopups; + while (item) { + nsMenuPopupFrame* frame = item->Frame(); + if (frame == aPopup) { + if (frame->PopupState() != ePopupInvisible) { + // Iterate through any child menus and hide them as well, since the + // parent is going away. We won't remove them from the list yet, just + // hide them, as they will be removed from the list when this function + // gets called for that child frame. + nsMenuChainItem* child = item->GetChild(); + while (child) { + // if the popup is a child frame of the menu that was destroyed, add + // it to the list of popups to hide. Don't bother with the events + // since the frames are going away. If the child menu is not a child + // frame, for example, a context menu, use HidePopup instead, but call + // it asynchronously since we are in the middle of frame destruction. + nsMenuPopupFrame* childframe = child->Frame(); + if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) { + popupsToHide.AppendElement(childframe); + } + else { + // HidePopup will take care of hiding any of its children, so + // break out afterwards + HidePopup(child->Content(), false, false, true); + break; + } + + child = child->GetChild(); + } + } + + item->Detach(&mPopups); + delete item; + break; + } + + item = item->GetParent(); + } + + HidePopupsInList(popupsToHide, false); +} + +bool +nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup) +{ + nsMenuChainItem* item = GetTopVisibleMenu(); + while (item && item->Frame() != aPopup) { + if (item->IsContextMenu()) + return true; + item = item->GetParent(); + } + + return false; +} + +void +nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup) +{ + nsMenuChainItem* item = GetTopVisibleMenu(); + if (item && aOldPopup == item->Content()) + return; + + if (mWidget) { + mWidget->CaptureRollupEvents(nullptr, false); + mWidget = nullptr; + } + + if (item) { + nsMenuPopupFrame* popup = item->Frame(); + mWidget = popup->GetWidget(); + if (mWidget) { + mWidget->CaptureRollupEvents(nullptr, true); + popup->AttachedDismissalListener(); + } + } + + UpdateKeyboardListeners(); +} + +void +nsXULPopupManager::UpdateKeyboardListeners() +{ + nsCOMPtr<EventTarget> newTarget; + bool isForMenu = false; + nsMenuChainItem* item = GetTopVisibleMenu(); + if (item) { + if (!item->IgnoreKeys()) + newTarget = item->Content()->GetDocument(); + isForMenu = item->PopupType() == ePopupTypeMenu; + } + else if (mActiveMenuBar) { + newTarget = mActiveMenuBar->GetContent()->GetDocument(); + isForMenu = true; + } + + if (mKeyListener != newTarget) { + if (mKeyListener) { + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true); + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true); + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true); + mKeyListener = nullptr; + nsContentUtils::NotifyInstalledMenuKeyboardListener(false); + } + + if (newTarget) { + newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true); + newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true); + newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true); + nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu); + mKeyListener = newTarget; + } + } +} + +void +nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup) +{ + // Walk all of the menu's children, checking to see if any of them has a + // command attribute. If so, then several attributes must potentially be updated. + + nsCOMPtr<nsIDocument> document = aPopup->GetCurrentDoc(); + if (!document) { + return; + } + + for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild(); + grandChild; + grandChild = grandChild->GetNextSibling()) { + if (grandChild->NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL)) { + // See if we have a command attribute. + nsAutoString command; + grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); + if (!command.IsEmpty()) { + // We do! Look it up in our document + nsRefPtr<dom::Element> commandElement = + document->GetElementById(command); + if (commandElement) { + nsAutoString commandValue; + // The menu's disabled state needs to be updated to match the command. + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, true); + else + grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); + + // The menu's label, accesskey checked and hidden states need to be updated + // to match the command. Note that unlike the disabled state if the + // command has *no* value, we assume the menu is supplying its own. + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, true); + + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, true); + + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, true); + + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue, true); + } + } + } + } +} + +// Notify +// +// The item selection timer has fired, we might have to readjust the +// selected item. There are two cases here that we are trying to deal with: +// (1) diagonal movement from a parent menu to a submenu passing briefly over +// other items, and +// (2) moving out from a submenu to a parent or grandparent menu. +// In both cases, |mTimerMenu| is the menu item that might have an open submenu and +// the first item in |mPopups| is the item the mouse is currently over, which could be +// none of them. +// +// case (1): +// As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the +// submenu, it probably passes through one or more sibilings (B). As the mouse passes +// through B, it becomes the current menu item and the timer is set and mTimerMenu is +// set to A. Before the timer fires, the mouse leaves the menu containing A and B and +// enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|) +// so we have to see if anything in A's children is selected (recall that even disabled +// items are selected, the style just doesn't show it). If that is the case, we need to +// set the selected item back to A. +// +// case (2); +// Item A has an open submenu, and in it there is an item (B) which also has an open +// submenu (so there are 3 menus displayed right now). The mouse then leaves B's child +// submenu and selects an item that is a sibling of A, call it C. When the mouse enters C, +// the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires, +// the mouse is still within C. The correct behavior is to set the current item to C +// and close up the chain parented at A. +// +// This brings up the question of is the logic of case (1) enough? The answer is no, +// and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected +// child, and if it does, set the selected item to A. Because B has a submenu open, it +// is selected and as a result, A is set to be the selected item even though the mouse +// rests in C -- very wrong. +// +// The solution is to use the same idea, but instead of only checking one level, +// drill all the way down to the deepest open submenu and check if it has something +// selected. Since the mouse is in a grandparent, it won't, and we know that we can +// safely close up A and all its children. +// +// The code below melds the two cases together. +// +nsresult +nsXULPopupManager::Notify(nsITimer* aTimer) +{ + if (aTimer == mCloseTimer) + KillMenuTimer(); + + return NS_OK; +} + +void +nsXULPopupManager::KillMenuTimer() +{ + if (mCloseTimer && mTimerMenu) { + mCloseTimer->Cancel(); + mCloseTimer = nullptr; + + if (mTimerMenu->IsOpen()) + HidePopup(mTimerMenu->GetContent(), false, false, true); + } + + mTimerMenu = nullptr; +} + +void +nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent) +{ + if (mCloseTimer && mTimerMenu == aMenuParent) { + mCloseTimer->Cancel(); + mCloseTimer = nullptr; + mTimerMenu = nullptr; + } +} + +static nsGUIEvent* DOMKeyEventToGUIEvent(nsIDOMEvent* aEvent) +{ + nsEvent* evt = aEvent ? aEvent->GetInternalNSEvent() : nullptr; + return evt && evt->eventStructType == NS_KEY_EVENT ? + static_cast<nsGUIEvent *>(evt) : nullptr; +} + +bool +nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, + nsMenuPopupFrame* aFrame) +{ + nsMenuChainItem* item = GetTopVisibleMenu(); + if (!aFrame && item) + aFrame = item->Frame(); + + if (aFrame) { + bool action; + nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action); + if (result) { + aFrame->ChangeMenuItem(result, false); + if (action) { + nsGUIEvent* evt = DOMKeyEventToGUIEvent(aKeyEvent); + nsMenuFrame* menuToOpen = result->Enter(evt); + if (menuToOpen) { + nsCOMPtr<nsIContent> content = menuToOpen->GetContent(); + ShowMenu(content, true, false); + } + } + return true; + } + + return false; + } + + if (mActiveMenuBar) { + nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent); + if (result) { + mActiveMenuBar->SetActive(true); + result->OpenMenu(true); + return true; + } + } + + return false; +} + + +bool +nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode) +{ + // navigate up through the open menus, looking for the topmost one + // in the same hierarchy + nsMenuChainItem* item = nullptr; + nsMenuChainItem* nextitem = GetTopVisibleMenu(); + + while (nextitem) { + item = nextitem; + nextitem = item->GetParent(); + + if (nextitem) { + // 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()); + nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent()); + if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) { + break; + } + } + } + + nsIFrame* itemFrame; + if (item) + itemFrame = item->Frame(); + else if (mActiveMenuBar) + itemFrame = mActiveMenuBar; + else + return false; + + nsNavigationDirection theDirection; + NS_ASSERTION(aKeyCode >= NS_VK_END && aKeyCode <= NS_VK_DOWN, "Illegal key code"); + theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode); + + // if a popup is open, first check for navigation within the popup + if (item && HandleKeyboardNavigationInPopup(item, theDirection)) + return true; + + // no popup handled the key, so check the active menubar, if any + if (mActiveMenuBar) { + nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem(); + + if (NS_DIRECTION_IS_INLINE(theDirection)) { + nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ? + GetNextMenuItem(mActiveMenuBar, currentMenu, false) : + GetPreviousMenuItem(mActiveMenuBar, currentMenu, false); + mActiveMenuBar->ChangeMenuItem(nextItem, true); + return true; + } + else if (NS_DIRECTION_IS_BLOCK(theDirection)) { + // Open the menu and select its first item. + if (currentMenu) { + nsCOMPtr<nsIContent> content = currentMenu->GetContent(); + ShowMenu(content, true, false); + } + return true; + } + } + + return false; +} + +bool +nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item, + nsMenuPopupFrame* aFrame, + nsNavigationDirection aDir) +{ + NS_ASSERTION(aFrame, "aFrame is null"); + NS_ASSERTION(!item || item->Frame() == aFrame, + "aFrame is expected to be equal to item->Frame()"); + + nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem(); + + aFrame->ClearIncrementalString(); + + // This method only gets called if we're open. + if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) { + // We've been opened, but we haven't had anything selected. + // We can handle End, but our parent handles Start. + if (aDir == eNavigationDirection_End) { + nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true); + if (nextItem) { + aFrame->ChangeMenuItem(nextItem, false); + return true; + } + } + return false; + } + + bool isContainer = false; + bool isOpen = false; + if (currentMenu) { + isOpen = currentMenu->IsOpen(); + isContainer = currentMenu->IsMenu(); + if (isOpen) { + // for an open popup, have the child process the event + nsMenuChainItem* child = item ? item->GetChild() : nullptr; + if (child && HandleKeyboardNavigationInPopup(child, aDir)) + return true; + } + else if (aDir == eNavigationDirection_End && + isContainer && !currentMenu->IsDisabled()) { + // The menu is not yet open. Open it and select the first item. + nsCOMPtr<nsIContent> content = currentMenu->GetContent(); + ShowMenu(content, true, false); + return true; + } + } + + // For block progression, we can move in either direction + if (NS_DIRECTION_IS_BLOCK(aDir) || + NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) { + nsMenuFrame* nextItem; + + if (aDir == eNavigationDirection_Before) + nextItem = GetPreviousMenuItem(aFrame, currentMenu, true); + else if (aDir == eNavigationDirection_After) + nextItem = GetNextMenuItem(aFrame, currentMenu, true); + else if (aDir == eNavigationDirection_First) + nextItem = GetNextMenuItem(aFrame, nullptr, true); + else + nextItem = GetPreviousMenuItem(aFrame, nullptr, true); + + if (nextItem) { + aFrame->ChangeMenuItem(nextItem, false); + return true; + } + } + else if (currentMenu && isContainer && isOpen) { + if (aDir == eNavigationDirection_Start) { + // close a submenu when Left is pressed + nsMenuPopupFrame* popupFrame = currentMenu->GetPopup(); + if (popupFrame) + HidePopup(popupFrame->GetContent(), false, false, false); + return true; + } + } + + return false; +} + +nsMenuFrame* +nsXULPopupManager::GetNextMenuItem(nsIFrame* aParent, + nsMenuFrame* aStart, + bool aIsPopup) +{ + nsIFrame* immediateParent = nullptr; + nsPresContext* presContext = aParent->PresContext(); + presContext->PresShell()-> + FrameConstructor()->GetInsertionPoint(aParent, nullptr, &immediateParent); + if (!immediateParent) + immediateParent = aParent; + + nsIFrame* currFrame = nullptr; + if (aStart) + currFrame = aStart->GetNextSibling(); + else + currFrame = immediateParent->GetFirstPrincipalChild(); + + while (currFrame) { + // See if it's a menu item. + if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { + return do_QueryFrame(currFrame); + } + currFrame = currFrame->GetNextSibling(); + } + + currFrame = immediateParent->GetFirstPrincipalChild(); + + // Still don't have anything. Try cycling from the beginning. + while (currFrame && currFrame != aStart) { + // See if it's a menu item. + if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { + return do_QueryFrame(currFrame); + } + + currFrame = currFrame->GetNextSibling(); + } + + // No luck. Just return our start value. + return aStart; +} + +nsMenuFrame* +nsXULPopupManager::GetPreviousMenuItem(nsIFrame* aParent, + nsMenuFrame* aStart, + bool aIsPopup) +{ + nsIFrame* immediateParent = nullptr; + nsPresContext* presContext = aParent->PresContext(); + presContext->PresShell()-> + FrameConstructor()->GetInsertionPoint(aParent, nullptr, &immediateParent); + if (!immediateParent) + immediateParent = aParent; + + const nsFrameList& frames(immediateParent->PrincipalChildList()); + + nsIFrame* currFrame = nullptr; + if (aStart) + currFrame = aStart->GetPrevSibling(); + else + currFrame = frames.LastChild(); + + while (currFrame) { + // See if it's a menu item. + if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { + return do_QueryFrame(currFrame); + } + currFrame = currFrame->GetPrevSibling(); + } + + currFrame = frames.LastChild(); + + // Still don't have anything. Try cycling from the end. + while (currFrame && currFrame != aStart) { + // See if it's a menu item. + if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { + return do_QueryFrame(currFrame); + } + + currFrame = currFrame->GetPrevSibling(); + } + + // No luck. Just return our start value. + return aStart; +} + +bool +nsXULPopupManager::IsValidMenuItem(nsPresContext* aPresContext, + nsIContent* aContent, + bool aOnPopup) +{ + int32_t ns = aContent->GetNameSpaceID(); + nsIAtom *tag = aContent->Tag(); + if (ns == kNameSpaceID_XUL) { + if (tag != nsGkAtoms::menu && tag != nsGkAtoms::menuitem) + return false; + } + else if (ns != kNameSpaceID_XHTML || !aOnPopup || tag != nsGkAtoms::option) { + return false; + } + + bool skipNavigatingDisabledMenuItem = true; + if (aOnPopup) { + skipNavigatingDisabledMenuItem = + LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem, + 0) != 0; + } + + return !(skipNavigatingDisabledMenuItem && + aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters)); +} + +nsresult +nsXULPopupManager::HandleEvent(nsIDOMEvent* aEvent) +{ + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent); + NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED); + + nsAutoString eventType; + keyEvent->GetType(eventType); + if (eventType.EqualsLiteral("keyup")) { + return KeyUp(keyEvent); + } + if (eventType.EqualsLiteral("keydown")) { + return KeyDown(keyEvent); + } + if (eventType.EqualsLiteral("keypress")) { + return KeyPress(keyEvent); + } + + NS_ABORT(); + + return NS_OK; +} + +nsresult +nsXULPopupManager::KeyUp(nsIDOMKeyEvent* aKeyEvent) +{ + // don't do anything if a menu isn't open or a menubar isn't active + if (!mActiveMenuBar) { + nsMenuChainItem* item = GetTopVisibleMenu(); + if (!item || item->PopupType() != ePopupTypeMenu) + return NS_OK; + } + + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + + return NS_OK; // I am consuming event +} + +nsresult +nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent) +{ + nsMenuChainItem* item = GetTopVisibleMenu(); + if (item && item->Frame()->IsMenuLocked()) + return NS_OK; + + // don't do anything if a menu isn't open or a menubar isn't active + if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu)) + return NS_OK; + + int32_t menuAccessKey = -1; + + // If the key just pressed is the access key (usually Alt), + // dismiss and unfocus the menu. + + nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); + if (menuAccessKey) { + uint32_t theChar; + aKeyEvent->GetKeyCode(&theChar); + + if (theChar == (uint32_t)menuAccessKey) { + bool ctrl = false; + if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL) + aKeyEvent->GetCtrlKey(&ctrl); + bool alt=false; + if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT) + aKeyEvent->GetAltKey(&alt); + bool shift=false; + if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT) + aKeyEvent->GetShiftKey(&shift); + bool meta=false; + if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_META) + aKeyEvent->GetMetaKey(&meta); + if (!(ctrl || alt || shift || meta)) { + // The access key just went down and no other + // modifiers are already down. + if (mPopups) + Rollup(0, nullptr); + else if (mActiveMenuBar) + mActiveMenuBar->MenuClosed(); + } + } + } + + // Since a menu was open, eat the event to keep other event + // listeners from becoming confused. + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + return NS_OK; // I am consuming event +} + +nsresult +nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent) +{ + // Don't check prevent default flag -- menus always get first shot at key events. + // When a menu is open, the prevent default flag on a keypress is always set, so + // that no one else uses the key event. + + nsMenuChainItem* item = GetTopVisibleMenu(); + if (item && item->Frame()->IsMenuLocked()) + return NS_OK; + + //handlers shouldn't be triggered by non-trusted events. + bool trustedEvent = false; + if (aKeyEvent) { + aKeyEvent->GetIsTrusted(&trustedEvent); + } + + if (!trustedEvent) + return NS_OK; + + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); + NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED); + uint32_t theChar; + keyEvent->GetKeyCode(&theChar); + + // Escape should close panels, but the other keys should have no effect. + if (item && item->PopupType() != ePopupTypeMenu) { + if (theChar == NS_VK_ESCAPE) { + HidePopup(item->Content(), false, false, false); + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + } + return NS_OK; + } + + // if a menu is open or a menubar is active, it consumes the key event + bool consume = (mPopups || mActiveMenuBar); + + if (theChar == NS_VK_LEFT || + theChar == NS_VK_RIGHT || + theChar == NS_VK_UP || + theChar == NS_VK_DOWN || + theChar == NS_VK_HOME || + theChar == NS_VK_END) { + HandleKeyboardNavigation(theChar); + } + else if (theChar == NS_VK_ESCAPE) { + // Pressing Escape hides one level of menus only. If no menu is open, + // check if a menubar is active and inform it that a menu closed. Even + // though in this latter case, a menu didn't actually close, the effect + // ends up being the same. Similar for the tab key below. + if (item) + HidePopup(item->Content(), false, false, false); + else if (mActiveMenuBar) + mActiveMenuBar->MenuClosed(); + } + else if (theChar == NS_VK_TAB +#ifndef XP_MACOSX + || theChar == NS_VK_F10 +#endif + ) { + // close popups or deactivate menubar when Tab or F10 are pressed + if (item) + Rollup(0, nullptr); + else if (mActiveMenuBar) + mActiveMenuBar->MenuClosed(); + } + else if (theChar == NS_VK_ENTER || + theChar == NS_VK_RETURN) { + // If there is a popup open, check if the current item needs to be opened. + // Otherwise, tell the active menubar, if any, to activate the menu. The + // Enter method will return a menu if one needs to be opened as a result. + nsMenuFrame* menuToOpen = nullptr; + nsMenuChainItem* item = GetTopVisibleMenu(); + nsGUIEvent* evt = DOMKeyEventToGUIEvent(aKeyEvent); + if (item) + menuToOpen = item->Frame()->Enter(evt); + else if (mActiveMenuBar) + menuToOpen = mActiveMenuBar->Enter(evt); + if (menuToOpen) { + nsCOMPtr<nsIContent> content = menuToOpen->GetContent(); + ShowMenu(content, true, false); + } + } + else { + HandleShortcutNavigation(keyEvent, nullptr); + } + + if (consume) { + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + } + + return NS_OK; // I am consuming event +} + +NS_IMETHODIMP +nsXULPopupShowingEvent::Run() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULPopupHidingEvent::Run() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + + nsIDocument *document = mPopup->GetCurrentDoc(); + if (pm && document) { + nsIPresShell* presShell = document->GetShell(); + if (presShell) { + nsPresContext* context = presShell->GetPresContext(); + if (context) { + pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup, + context, mPopupType, mDeselectMenu); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULMenuCommandEvent::Run() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) + return NS_OK; + + // The order of the nsViewManager and nsIPresShell COM pointers is + // important below. We want the pres shell to get released before the + // associated view manager on exit from this function. + // See bug 54233. + // XXXndeakin is this still needed? + + nsCOMPtr<nsIContent> popup; + nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame()); + nsWeakFrame weakFrame(menuFrame); + if (menuFrame && mFlipChecked) { + if (menuFrame->IsChecked()) { + mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true); + } else { + mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, + NS_LITERAL_STRING("true"), true); + } + } + + if (menuFrame && weakFrame.IsAlive()) { + // Find the popup that the menu is inside. Below, this popup will + // need to be hidden. + nsIFrame* frame = menuFrame->GetParent(); + while (frame) { + nsMenuPopupFrame* popupFrame = do_QueryFrame(frame); + if (popupFrame) { + popup = popupFrame->GetContent(); + break; + } + frame = frame->GetParent(); + } + + nsPresContext* presContext = menuFrame->PresContext(); + nsCOMPtr<nsIPresShell> shell = presContext->PresShell(); + nsRefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager(); + + // Deselect ourselves. + if (mCloseMenuMode != CloseMenuMode_None) + menuFrame->SelectMenu(false); + + nsAutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr, + shell->GetDocument()); + nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell, + mControl, mAlt, mShift, mMeta); + } + + if (popup && mCloseMenuMode != CloseMenuMode_None) + pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false); + + return NS_OK; +} diff --git a/layout/xul/base/src/nsXULTooltipListener.cpp b/layout/xul/base/src/nsXULTooltipListener.cpp new file mode 100644 index 000000000..f9e45d939 --- /dev/null +++ b/layout/xul/base/src/nsXULTooltipListener.cpp @@ -0,0 +1,729 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXULTooltipListener.h" + +#include "nsDOMEvent.h" +#include "nsIDOMMouseEvent.h" +#include "nsIDOMXULDocument.h" +#include "nsIDOMXULElement.h" +#include "nsIDocument.h" +#include "nsGkAtoms.h" +#include "nsIPopupBoxObject.h" +#include "nsMenuPopupFrame.h" +#include "nsIServiceManager.h" +#include "nsIDragService.h" +#include "nsIDragSession.h" +#ifdef MOZ_XUL +#include "nsITreeView.h" +#endif +#include "nsGUIEvent.h" +#include "nsIScriptContext.h" +#include "nsPIDOMWindow.h" +#ifdef MOZ_XUL +#include "nsXULPopupManager.h" +#endif +#include "nsIRootBox.h" +#include "nsEventDispatcher.h" +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla; +using namespace mozilla::dom; + +nsXULTooltipListener* nsXULTooltipListener::mInstance = nullptr; + +////////////////////////////////////////////////////////////////////////// +//// nsISupports + +nsXULTooltipListener::nsXULTooltipListener() + : mMouseScreenX(0) + , mMouseScreenY(0) + , mTooltipShownOnce(false) +#ifdef MOZ_XUL + , mIsSourceTree(false) + , mNeedTitletip(false) + , mLastTreeRow(-1) +#endif +{ + if (sTooltipListenerCount++ == 0) { + // register the callback so we get notified of updates + Preferences::RegisterCallback(ToolbarTipsPrefChanged, + "browser.chrome.toolbar_tips"); + + // Call the pref callback to initialize our state. + ToolbarTipsPrefChanged("browser.chrome.toolbar_tips", nullptr); + } +} + +nsXULTooltipListener::~nsXULTooltipListener() +{ + if (nsXULTooltipListener::mInstance == this) { + ClearTooltipCache(); + } + HideTooltip(); + + if (--sTooltipListenerCount == 0) { + // Unregister our pref observer + Preferences::UnregisterCallback(ToolbarTipsPrefChanged, + "browser.chrome.toolbar_tips"); + } +} + +NS_IMPL_ISUPPORTS1(nsXULTooltipListener, nsIDOMEventListener) + +void +nsXULTooltipListener::MouseOut(nsIDOMEvent* aEvent) +{ + // reset flag so that tooltip will display on the next MouseMove + mTooltipShownOnce = false; + + // if the timer is running and no tooltip is shown, we + // have to cancel the timer here so that it doesn't + // show the tooltip if we move the mouse out of the window + nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); + if (mTooltipTimer && !currentTooltip) { + mTooltipTimer->Cancel(); + mTooltipTimer = nullptr; + return; + } + +#ifdef DEBUG_crap + if (mNeedTitletip) + return; +#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<nsIDOMNode> targetNode = do_QueryInterface( + aEvent->InternalDOMEvent()->GetTarget()); + + 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 = nullptr; + } + } + } + } +#endif +} + +void +nsXULTooltipListener::MouseMove(nsIDOMEvent* aEvent) +{ + if (!sShowTooltips) + return; + + // stash the coordinates of the event so that we can still get back to it from within the + // timer callback. On win32, we'll get a MouseMove event even when a popup goes away -- + // even when the mouse doesn't change position! To get around this, we make sure the + // mouse has really moved before proceeding. + nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aEvent)); + if (!mouseEvent) + return; + int32_t newMouseX, newMouseY; + mouseEvent->GetScreenX(&newMouseX); + mouseEvent->GetScreenY(&newMouseY); + + // filter out false win32 MouseMove event + if (mMouseScreenX == newMouseX && mMouseScreenY == newMouseY) + return; + + // filter out minor movements due to crappy optical mice and shaky hands + // to prevent tooltips from hiding prematurely. + nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); + + if ((currentTooltip) && + (abs(mMouseScreenX - newMouseX) <= kTooltipMouseMoveTolerance) && + (abs(mMouseScreenY - newMouseY) <= kTooltipMouseMoveTolerance)) + return; + mMouseScreenX = newMouseX; + mMouseScreenY = newMouseY; + + nsCOMPtr<nsIContent> sourceContent = do_QueryInterface( + aEvent->InternalDOMEvent()->GetCurrentTarget()); + mSourceNode = do_GetWeakReference(sourceContent); +#ifdef MOZ_XUL + mIsSourceTree = sourceContent->Tag() == nsGkAtoms::treechildren; + if (mIsSourceTree) + CheckTreeBodyMove(mouseEvent); +#endif + + // as the mouse moves, we want to make sure we reset the timer to show it, + // so that the delay is from when the mouse stops moving, not when it enters + // the node. + KillTooltipTimer(); + + // If the mouse moves while the tooltip is up, hide it. If nothing is + // showing and the tooltip hasn't been displayed since the mouse entered + // the node, then start the timer to show the tooltip. + if (!currentTooltip && !mTooltipShownOnce) { + nsCOMPtr<EventTarget> eventTarget = aEvent->InternalDOMEvent()->GetTarget(); + + // don't show tooltips attached to elements outside of a menu popup + // when hovering over an element inside it. The popupsinherittooltip + // attribute may be used to disable this behaviour, which is useful for + // large menu hierarchies such as bookmarks. + if (!sourceContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::popupsinherittooltip, + nsGkAtoms::_true, eCaseMatters)) { + nsCOMPtr<nsIContent> targetContent = do_QueryInterface(eventTarget); + while (targetContent && targetContent != sourceContent) { + nsIAtom* tag = targetContent->Tag(); + if (targetContent->GetNameSpaceID() == kNameSpaceID_XUL && + (tag == nsGkAtoms::menupopup || + tag == nsGkAtoms::panel || + tag == nsGkAtoms::tooltip)) { + mSourceNode = nullptr; + return; + } + + targetContent = targetContent->GetParent(); + } + } + + mTooltipTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mTooltipTimer) { + mTargetNode = do_GetWeakReference(eventTarget); + if (mTargetNode) { + nsresult rv = + mTooltipTimer->InitWithFuncCallback(sTooltipCallback, this, + LookAndFeel::GetInt(LookAndFeel::eIntID_TooltipDelay, 500), + nsITimer::TYPE_ONE_SHOT); + if (NS_FAILED(rv)) { + mTargetNode = nullptr; + mSourceNode = nullptr; + } + } + } + return; + } + +#ifdef MOZ_XUL + if (mIsSourceTree) + return; +#endif + + HideTooltip(); + // set a flag so that the tooltip is only displayed once until the mouse + // leaves the node + mTooltipShownOnce = true; +} + +NS_IMETHODIMP +nsXULTooltipListener::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString type; + aEvent->GetType(type); + if (type.EqualsLiteral("DOMMouseScroll") || + type.EqualsLiteral("keydown") || + type.EqualsLiteral("mousedown") || + type.EqualsLiteral("mouseup") || + type.EqualsLiteral("dragstart")) { + HideTooltip(); + return NS_OK; + } + + if (type.EqualsLiteral("popuphiding")) { + DestroyTooltip(); + return NS_OK; + } + + // Note that mousemove, mouseover and mouseout might be + // fired even during dragging due to widget's bug. + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + NS_ENSURE_TRUE(dragService, NS_OK); + nsCOMPtr<nsIDragSession> dragSession; + dragService->GetCurrentSession(getter_AddRefs(dragSession)); + if (dragSession) { + return NS_OK; + } + + // Not dragging. + + if (type.EqualsLiteral("mousemove")) { + MouseMove(aEvent); + return NS_OK; + } + + if (type.EqualsLiteral("mouseout")) { + MouseOut(aEvent); + return NS_OK; + } + + return NS_OK; +} + +////////////////////////////////////////////////////////////////////////// +//// nsXULTooltipListener + +// static +int +nsXULTooltipListener::ToolbarTipsPrefChanged(const char *aPref, + void *aClosure) +{ + sShowTooltips = + Preferences::GetBool("browser.chrome.toolbar_tips", sShowTooltips); + + return 0; +} + +////////////////////////////////////////////////////////////////////////// +//// nsXULTooltipListener + +bool nsXULTooltipListener::sShowTooltips = false; +uint32_t nsXULTooltipListener::sTooltipListenerCount = 0; + +nsresult +nsXULTooltipListener::AddTooltipSupport(nsIContent* aNode) +{ + if (!aNode) + return NS_ERROR_NULL_POINTER; + + aNode->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), this, + false, false); + aNode->AddSystemEventListener(NS_LITERAL_STRING("mousemove"), this, + false, false); + aNode->AddSystemEventListener(NS_LITERAL_STRING("mousedown"), this, + false, false); + aNode->AddSystemEventListener(NS_LITERAL_STRING("mouseup"), this, + false, false); + aNode->AddSystemEventListener(NS_LITERAL_STRING("dragstart"), this, + true, false); + + return NS_OK; +} + +nsresult +nsXULTooltipListener::RemoveTooltipSupport(nsIContent* aNode) +{ + if (!aNode) + return NS_ERROR_NULL_POINTER; + + aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"), this, false); + aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"), this, false); + aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), this, false); + aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"), this, false); + aNode->RemoveSystemEventListener(NS_LITERAL_STRING("dragstart"), this, true); + + return NS_OK; +} + +#ifdef MOZ_XUL +void +nsXULTooltipListener::CheckTreeBodyMove(nsIDOMMouseEvent* aMouseEvent) +{ + nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode); + if (!sourceNode) + return; + + // get the boxObject of the documentElement of the document the tree is in + nsCOMPtr<nsIBoxObject> bx; + nsIDocument* doc = sourceNode->GetDocument(); + if (doc) { + ErrorResult ignored; + bx = doc->GetBoxObjectFor(doc->GetRootElement(), ignored); + } + + nsCOMPtr<nsITreeBoxObject> obx; + GetSourceTreeBoxObject(getter_AddRefs(obx)); + if (bx && obx) { + int32_t x, y; + aMouseEvent->GetScreenX(&x); + aMouseEvent->GetScreenY(&y); + + int32_t row; + nsCOMPtr<nsITreeColumn> col; + nsAutoCString obj; + + // subtract off the documentElement's boxObject + int32_t boxX, boxY; + bx->GetScreenX(&boxX); + bx->GetScreenY(&boxY); + x -= boxX; + y -= boxY; + + obx->GetCellAt(x, y, &row, getter_AddRefs(col), obj); + + // determine if we are going to need a titletip + // XXX check the disabletitletips attribute on the tree content + mNeedTitletip = false; + if (row >= 0 && obj.EqualsLiteral("text")) { + obx->IsCellCropped(row, col, &mNeedTitletip); + } + + nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); + if (currentTooltip && (row != mLastTreeRow || col != mLastTreeCol)) { + HideTooltip(); + } + + mLastTreeRow = row; + mLastTreeCol = col; + } +} +#endif + +nsresult +nsXULTooltipListener::ShowTooltip() +{ + nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode); + + // get the tooltip content designated for the target node + nsCOMPtr<nsIContent> tooltipNode; + GetTooltipFor(sourceNode, getter_AddRefs(tooltipNode)); + if (!tooltipNode || sourceNode == tooltipNode) + return NS_ERROR_FAILURE; // the target node doesn't need a tooltip + + // set the node in the document that triggered the tooltip and show it + nsCOMPtr<nsIDOMXULDocument> xulDoc(do_QueryInterface(tooltipNode->GetDocument())); + if (xulDoc) { + // Make sure the target node is still attached to some document. + // It might have been deleted. + if (sourceNode->GetDocument()) { +#ifdef MOZ_XUL + if (!mIsSourceTree) { + mLastTreeRow = -1; + mLastTreeCol = nullptr; + } +#endif + + mCurrentTooltip = do_GetWeakReference(tooltipNode); + LaunchTooltip(); + mTargetNode = nullptr; + + nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); + if (!currentTooltip) + return NS_OK; + + // listen for popuphidden on the tooltip node, so that we can + // be sure DestroyPopup is called even if someone else closes the tooltip + currentTooltip->AddSystemEventListener(NS_LITERAL_STRING("popuphiding"), + this, false, false); + + // listen for mousedown, mouseup, keydown, and DOMMouseScroll events at document level + nsIDocument* doc = sourceNode->GetDocument(); + if (doc) { + // Probably, we should listen to untrusted events for hiding tooltips + // on content since tooltips might disturb something of web + // applications. If we don't specify the aWantsUntrusted of + // AddSystemEventListener(), the event target sets it to TRUE if the + // target is in content. + doc->AddSystemEventListener(NS_LITERAL_STRING("DOMMouseScroll"), + this, true); + doc->AddSystemEventListener(NS_LITERAL_STRING("mousedown"), + this, true); + doc->AddSystemEventListener(NS_LITERAL_STRING("mouseup"), + this, true); + doc->AddSystemEventListener(NS_LITERAL_STRING("keydown"), + this, true); + } + mSourceNode = nullptr; + } + } + + return NS_OK; +} + +#ifdef MOZ_XUL +// XXX: "This stuff inside DEBUG_crap could be used to make tree tooltips work +// in the future." +#ifdef DEBUG_crap +static void +GetTreeCellCoords(nsITreeBoxObject* aTreeBox, nsIContent* aSourceNode, + int32_t aRow, nsITreeColumn* aCol, int32_t* aX, int32_t* aY) +{ + int32_t junk; + aTreeBox->GetCoordsForCellItem(aRow, aCol, EmptyCString(), aX, aY, &junk, &junk); + nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(aSourceNode)); + nsCOMPtr<nsIBoxObject> bx; + xulEl->GetBoxObject(getter_AddRefs(bx)); + int32_t myX, myY; + bx->GetX(&myX); + bx->GetY(&myY); + *aX += myX; + *aY += myY; +} +#endif + +static void +SetTitletipLabel(nsITreeBoxObject* aTreeBox, nsIContent* aTooltip, + int32_t aRow, nsITreeColumn* aCol) +{ + nsCOMPtr<nsITreeView> view; + aTreeBox->GetView(getter_AddRefs(view)); + if (view) { + nsAutoString label; +#ifdef DEBUG + nsresult rv = +#endif + view->GetCellText(aRow, aCol, label); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Couldn't get the cell text!"); + aTooltip->SetAttr(kNameSpaceID_None, nsGkAtoms::label, label, true); + } +} +#endif + +void +nsXULTooltipListener::LaunchTooltip() +{ + nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); + if (!currentTooltip) + return; + +#ifdef MOZ_XUL + if (mIsSourceTree && mNeedTitletip) { + nsCOMPtr<nsITreeBoxObject> obx; + GetSourceTreeBoxObject(getter_AddRefs(obx)); + + SetTitletipLabel(obx, currentTooltip, mLastTreeRow, mLastTreeCol); + if (!(currentTooltip = do_QueryReferent(mCurrentTooltip))) { + // Because of mutation events, currentTooltip can be null. + return; + } + currentTooltip->SetAttr(kNameSpaceID_None, nsGkAtoms::titletip, NS_LITERAL_STRING("true"), true); + } else { + currentTooltip->UnsetAttr(kNameSpaceID_None, nsGkAtoms::titletip, true); + } + if (!(currentTooltip = do_QueryReferent(mCurrentTooltip))) { + // Because of mutation events, currentTooltip can be null. + return; + } + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsCOMPtr<nsIContent> target = do_QueryReferent(mTargetNode); + pm->ShowTooltipAtScreen(currentTooltip, target, mMouseScreenX, mMouseScreenY); + + // Clear the current tooltip if the popup was not opened successfully. + if (!pm->IsPopupOpen(currentTooltip)) + mCurrentTooltip = nullptr; + } +#endif + +} + +nsresult +nsXULTooltipListener::HideTooltip() +{ +#ifdef MOZ_XUL + nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); + if (currentTooltip) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + pm->HidePopup(currentTooltip, false, false, false); + } +#endif + + DestroyTooltip(); + return NS_OK; +} + +static void +GetImmediateChild(nsIContent* aContent, nsIAtom *aTag, nsIContent** aResult) +{ + *aResult = nullptr; + uint32_t childCount = aContent->GetChildCount(); + for (uint32_t i = 0; i < childCount; i++) { + nsIContent *child = aContent->GetChildAt(i); + + if (child->Tag() == aTag) { + *aResult = child; + NS_ADDREF(*aResult); + return; + } + } + + return; +} + +nsresult +nsXULTooltipListener::FindTooltip(nsIContent* aTarget, nsIContent** aTooltip) +{ + if (!aTarget) + return NS_ERROR_NULL_POINTER; + + // before we go on, make sure that target node still has a window + nsIDocument *document = aTarget->GetDocument(); + if (!document) { + NS_WARNING("Unable to retrieve the tooltip node document."); + return NS_ERROR_FAILURE; + } + nsPIDOMWindow *window = document->GetWindow(); + if (!window) { + return NS_OK; + } + + bool closed; + window->GetClosed(&closed); + + if (closed) { + return NS_OK; + } + + nsAutoString tooltipText; + aTarget->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, tooltipText); + if (!tooltipText.IsEmpty()) { + // specifying tooltiptext means we will always use the default tooltip + nsIRootBox* rootBox = nsIRootBox::GetRootBox(document->GetShell()); + NS_ENSURE_STATE(rootBox); + *aTooltip = rootBox->GetDefaultTooltip(); + if (*aTooltip) { + NS_ADDREF(*aTooltip); + (*aTooltip)->SetAttr(kNameSpaceID_None, nsGkAtoms::label, tooltipText, true); + } + return NS_OK; + } + + nsAutoString tooltipId; + aTarget->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltip, tooltipId); + + // if tooltip == _child, look for first <tooltip> child + if (tooltipId.EqualsLiteral("_child")) { + GetImmediateChild(aTarget, nsGkAtoms::tooltip, aTooltip); + return NS_OK; + } + + if (!tooltipId.IsEmpty()) { + // tooltip must be an id, use getElementById to find it + nsCOMPtr<nsIContent> tooltipEl = document->GetElementById(tooltipId); + + if (tooltipEl) { +#ifdef MOZ_XUL + mNeedTitletip = false; +#endif + tooltipEl.forget(aTooltip); + return NS_OK; + } + } + +#ifdef MOZ_XUL + // titletips should just use the default tooltip + if (mIsSourceTree && mNeedTitletip) { + nsIRootBox* rootBox = nsIRootBox::GetRootBox(document->GetShell()); + NS_ENSURE_STATE(rootBox); + NS_IF_ADDREF(*aTooltip = rootBox->GetDefaultTooltip()); + } +#endif + + return NS_OK; +} + + +nsresult +nsXULTooltipListener::GetTooltipFor(nsIContent* aTarget, nsIContent** aTooltip) +{ + *aTooltip = nullptr; + nsCOMPtr<nsIContent> tooltip; + nsresult rv = FindTooltip(aTarget, getter_AddRefs(tooltip)); + if (NS_FAILED(rv) || !tooltip) { + return rv; + } + +#ifdef MOZ_XUL + // Submenus can't be used as tooltips, see bug 288763. + nsIContent* parent = tooltip->GetParent(); + if (parent) { + nsMenuFrame* menu = do_QueryFrame(parent->GetPrimaryFrame()); + if (menu) { + NS_WARNING("Menu cannot be used as a tooltip"); + return NS_ERROR_FAILURE; + } + } +#endif + + tooltip.swap(*aTooltip); + return rv; +} + +nsresult +nsXULTooltipListener::DestroyTooltip() +{ + nsCOMPtr<nsIDOMEventListener> kungFuDeathGrip(this); + nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); + if (currentTooltip) { + // release tooltip before removing listener to prevent our destructor from + // being called recursively (bug 120863) + mCurrentTooltip = nullptr; + + // clear out the tooltip node on the document + nsCOMPtr<nsIDocument> doc = currentTooltip->GetDocument(); + if (doc) { + // remove the mousedown and keydown listener from document + doc->RemoveSystemEventListener(NS_LITERAL_STRING("DOMMouseScroll"), this, + true); + doc->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), this, + true); + doc->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"), this, true); + doc->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), this, true); + } + + // remove the popuphidden listener from tooltip + currentTooltip->RemoveSystemEventListener(NS_LITERAL_STRING("popuphiding"), this, false); + } + + // kill any ongoing timers + KillTooltipTimer(); + mSourceNode = nullptr; +#ifdef MOZ_XUL + mLastTreeCol = nullptr; +#endif + + return NS_OK; +} + +void +nsXULTooltipListener::KillTooltipTimer() +{ + if (mTooltipTimer) { + mTooltipTimer->Cancel(); + mTooltipTimer = nullptr; + mTargetNode = nullptr; + } +} + +void +nsXULTooltipListener::sTooltipCallback(nsITimer *aTimer, void *aListener) +{ + nsRefPtr<nsXULTooltipListener> instance = mInstance; + if (instance) + instance->ShowTooltip(); +} + +#ifdef MOZ_XUL +nsresult +nsXULTooltipListener::GetSourceTreeBoxObject(nsITreeBoxObject** aBoxObject) +{ + *aBoxObject = nullptr; + + nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode); + if (mIsSourceTree && sourceNode) { + nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(sourceNode->GetParent())); + if (xulEl) { + nsCOMPtr<nsIBoxObject> bx; + xulEl->GetBoxObject(getter_AddRefs(bx)); + nsCOMPtr<nsITreeBoxObject> obx(do_QueryInterface(bx)); + if (obx) { + *aBoxObject = obx; + NS_ADDREF(*aBoxObject); + return NS_OK; + } + } + } + return NS_ERROR_FAILURE; +} +#endif diff --git a/layout/xul/base/src/nsXULTooltipListener.h b/layout/xul/base/src/nsXULTooltipListener.h new file mode 100644 index 000000000..7cf0c44c4 --- /dev/null +++ b/layout/xul/base/src/nsXULTooltipListener.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsXULTooltipListener_h__ +#define nsXULTooltipListener_h__ + +#include "nsIDOMEventListener.h" +#include "nsIDOMMouseEvent.h" +#include "nsIDOMElement.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#ifdef MOZ_XUL +#include "nsITreeBoxObject.h" +#include "nsITreeColumns.h" +#endif +#include "nsWeakPtr.h" +#include "mozilla/Attributes.h" + +class nsIContent; + +class nsXULTooltipListener MOZ_FINAL : public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + void MouseOut(nsIDOMEvent* aEvent); + void MouseMove(nsIDOMEvent* aEvent); + + nsresult AddTooltipSupport(nsIContent* aNode); + nsresult RemoveTooltipSupport(nsIContent* aNode); + static nsXULTooltipListener* GetInstance() { + if (!mInstance) + mInstance = new nsXULTooltipListener(); + return mInstance; + } + static void ClearTooltipCache() { mInstance = nullptr; } + +protected: + + nsXULTooltipListener(); + ~nsXULTooltipListener(); + + // pref callback for when the "show tooltips" pref changes + static bool sShowTooltips; + static uint32_t sTooltipListenerCount; + + void KillTooltipTimer(); + +#ifdef MOZ_XUL + void CheckTreeBodyMove(nsIDOMMouseEvent* aMouseEvent); + nsresult GetSourceTreeBoxObject(nsITreeBoxObject** aBoxObject); +#endif + + nsresult ShowTooltip(); + void LaunchTooltip(); + nsresult HideTooltip(); + nsresult DestroyTooltip(); + // This method tries to find a tooltip for aTarget. + nsresult FindTooltip(nsIContent* aTarget, nsIContent** aTooltip); + // This method calls FindTooltip and checks that the tooltip + // can be really used (i.e. tooltip is not a menu). + nsresult GetTooltipFor(nsIContent* aTarget, nsIContent** aTooltip); + + static nsXULTooltipListener* mInstance; + static int ToolbarTipsPrefChanged(const char *aPref, void *aClosure); + + nsWeakPtr mSourceNode; + nsWeakPtr mTargetNode; + nsWeakPtr mCurrentTooltip; + + // a timer for showing the tooltip + nsCOMPtr<nsITimer> mTooltipTimer; + static void sTooltipCallback (nsITimer* aTimer, void* aListener); + + // screen coordinates of the last mousemove event, stored so that the + // tooltip can be opened at this location. + int32_t mMouseScreenX, mMouseScreenY; + + // various constants for tooltips + enum { + kTooltipMouseMoveTolerance = 7 // 7 pixel tolerance for mousemove event + }; + + // flag specifying if the tooltip has already been displayed by a MouseMove + // event. The flag is reset on MouseOut so that the tooltip will display + // the next time the mouse enters the node (bug #395668). + bool mTooltipShownOnce; + +#ifdef MOZ_XUL + // special members for handling trees + bool mIsSourceTree; + bool mNeedTitletip; + int32_t mLastTreeRow; + nsCOMPtr<nsITreeColumn> mLastTreeCol; +#endif +}; + +#endif // nsXULTooltipListener diff --git a/layout/xul/base/test/Makefile.in b/layout/xul/base/test/Makefile.in new file mode 100644 index 000000000..c9fe036a8 --- /dev/null +++ b/layout/xul/base/test/Makefile.in @@ -0,0 +1,30 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = @relativesrcdir@ + +include $(DEPTH)/config/autoconf.mk + +MOCHITEST_FILES = test_bug511075.html \ + test_splitter.xul \ + test_resizer_incontent.xul \ + $(NULL) + +MOCHITEST_CHROME_FILES = test_bug381167.xhtml \ + test_bug393970.xul \ + test_bug477754.xul \ + test_popupSizeTo.xul \ + test_stack.xul \ + test_resizer.xul \ + window_resizer.xul \ + window_resizer_element.xul \ + test_windowminmaxsize.xul \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/layout/xul/base/test/moz.build b/layout/xul/base/test/moz.build new file mode 100644 index 000000000..895d11993 --- /dev/null +++ b/layout/xul/base/test/moz.build @@ -0,0 +1,6 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + diff --git a/layout/xul/base/test/test_bug381167.xhtml b/layout/xul/base/test/test_bug381167.xhtml new file mode 100644 index 000000000..1b056dcfb --- /dev/null +++ b/layout/xul/base/test/test_bug381167.xhtml @@ -0,0 +1,49 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=381167 +--> +<head> + <title>Test for Bug 381167</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=381167">Mozilla Bug 381167</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<xul:tree> + <xul:tree> + <xul:treechildren/> + <xul:treecol/> + </xul:tree> +</xul:tree> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 381167 **/ + +SimpleTest.waitForExplicitFinish(); + +function closeit() { + var evt = document.createEvent('KeyEvents'); + evt.initKeyEvent('keypress', true, true, + window, + true, false, false, false, + 'W'.charCodeAt(0), 0); + window.dispatchEvent(evt); + + setTimeout(finish, 200); +} +window.addEventListener('load', closeit, false); + +function finish() +{ + ok(true, "This is a mochikit version of a crash test. To complete is to pass."); + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/layout/xul/base/test/test_bug393970.xul b/layout/xul/base/test/test_bug393970.xul new file mode 100644 index 000000000..fcb0f9e43 --- /dev/null +++ b/layout/xul/base/test/test_bug393970.xul @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<?xml-stylesheet href="data:text/css,description {min-width: 1px; padding: 2px;}" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=393970 +--> +<window title="Mozilla Bug 393970" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=393970" + target="_blank">Mozilla Bug 393970</a> + </body> + + <hbox flex="1" pack="start" style="visibility: hidden;"> + <grid min-width="1000" width="1000"> + <columns> + <column flex="1"/> + <column flex="2"/> + <column flex="3"/> + </columns> + <rows id="rows1"> + <row> + <description id="cell11">test1</description> + <description id="cell12">test2</description> + <description id="cell13">test3</description> + </row> + <rows id="rows2" flex="1"> + <row> + <description id="cell21">test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1</description> + <description id="cell22">test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2</description> + <description id="cell23">test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3</description> + </row> + <rows id="rows3"> + <row> + <description id="cell31">test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1 test1</description> + <description id="cell32">test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2 test2</description> + <description id="cell33">test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3 test3</description> + </row> + </rows> + </rows> + </rows> + </grid> + </hbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /** Test for Bug 393970 **/ + + if (navigator.platform.startsWith("Linux")) { + SimpleTest.expectAssertions(0, 24); + } + + var tests = [ + 'overflow-x: hidden; overflow-y: hidden;', + 'overflow-x: scroll; overflow-y: hidden;', + 'overflow-x: hidden; overflow-y: scroll;', + 'overflow-x: scroll; overflow-y: scroll;', + ]; + var currentTest = -1; + + function runNextTest() { + currentTest++; + if (currentTest >= tests.length) { + SimpleTest.finish(); + return; + } + + $("rows2").setAttribute("style", tests[currentTest]); + setTimeout(checkPositions, 0, tests[currentTest]); + } + + function checkPositions(variant) { + for (var col = 1; col <= 3; col++) { + is($('cell1' + col).boxObject.x, $('cell2' + col).boxObject.x, "Cells (1," + col + ") and (2," + col + ") line up horizontally (with " + variant + ")"); + is($('cell2' + col).boxObject.x, $('cell3' + col).boxObject.x, "Cells (2," + col + ") and (3," + col + ") line up horizontally (with " + variant + ")"); + } + for (var row = 1; row <= 3; row++) { + is($('cell' + row + '1').boxObject.y, $('cell' + row + '2').boxObject.y, "Cells (" + row + ",1) and (" + row + ",2) line up vertically (with " + variant + ")"); + is($('cell' + row + '2').boxObject.y, $('cell' + row + '3').boxObject.y, "Cells (" + row + ",2) and (" + row + ",3) line up vertically (with " + variant + ")"); + } + runNextTest(); + } + + addLoadEvent(runNextTest); + SimpleTest.waitForExplicitFinish() + ]]></script> +</window> diff --git a/layout/xul/base/test/test_bug477754.xul b/layout/xul/base/test/test_bug477754.xul new file mode 100644 index 000000000..111c5b805 --- /dev/null +++ b/layout/xul/base/test/test_bug477754.xul @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=477754 +--> +<window title="Mozilla Bug 477754" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=477754" + target="_blank">Mozilla Bug 477754</a> + </body> + + <hbox pack="center"> + <label id="anchor" style="direction: rtl;" value="Anchor"/> + </hbox> + <panel id="testPopup" onpopupshown="doTest();"> + <label value="I am a popup"/> + </panel> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /** Test for Bug 477754 **/ + SimpleTest.waitForExplicitFinish(); + + let testPopup, testAnchor; + + addEventListener("load", function () { + removeEventListener("load", arguments.callee, false); + + testPopup = document.getElementById("testPopup"); + testAnchor = document.getElementById("anchor"); + + testPopup.openPopup(testAnchor, "after_start", 10, 0, false, false); + }, false); + + function doTest() { + is(Math.round(testAnchor.getBoundingClientRect().right) - + Math.round(testPopup.getBoundingClientRect().right), 10, + "RTL popup's right offset should be equal to the x offset passed to openPopup"); + testPopup.hidePopup(); + SimpleTest.finish(); + } + + ]]></script> +</window> diff --git a/layout/xul/base/test/test_bug511075.html b/layout/xul/base/test/test_bug511075.html new file mode 100644 index 000000000..313d140a3 --- /dev/null +++ b/layout/xul/base/test/test_bug511075.html @@ -0,0 +1,120 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=511075 +--> +<head> + <title>Test for Bug 511075</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + #scroller { + border: 1px solid black; + } + </style> +</head> +<body onload="runTests()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511075">Mozilla Bug 511075</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 511075 **/ + +SimpleTest.waitForExplicitFinish(); + +var tests = [ + function() { + ok(true, "Setting location.hash should scroll."); + nextTest(); + // Click the top scroll arrow. + var x = scroller.getBoundingClientRect().width - 5; + var y = 5; + // On MacOSX the top scroll arrow can be below the slider just above + // the bottom scroll arrow. + if (navigator.platform.indexOf("Mac") >= 0) + y = scroller.getBoundingClientRect().height - 40; + synthesizeMouse(scroller, x, y, { type : "mousedown" }, window); + synthesizeMouse(scroller, x, y, { type: "mouseup" }, window); + }, + function() { + ok(true, "Clicking the top scroll arrow should scroll."); + nextTest(); + // Click the bottom scroll arrow. + var x = scroller.getBoundingClientRect().width - 5; + var y = scroller.getBoundingClientRect().height - 25; + synthesizeMouse(scroller, x, y, { type : "mousedown" }, window); + synthesizeMouse(scroller, x, y, { type: "mouseup" }, window); + }, + function() { + ok(true, "Clicking the bottom scroll arrow should scroll."); + nextTest(); + // Click the scrollbar. + var x = scroller.getBoundingClientRect().width - 5; + synthesizeMouse(scroller, x, 40, { type : "mousedown" }, window); + synthesizeMouse(scroller, x, 40, { type: "mouseup" }, window); + }, + function() { + ok(true, "Clicking the scrollbar should scroll"); + nextTest(); + // Click the scrollbar. + var x = scroller.getBoundingClientRect().width - 5; + var y = scroller.getBoundingClientRect().height - 50; + synthesizeMouse(scroller, x, y, { type : "mousedown" }, window); + synthesizeMouse(scroller, x, y, { type: "mouseup" }, window); + }, + function() { + scroller.onscroll = null; + ok(true, "Clicking the scrollbar should scroll"); + finish(); + } +]; + +document.onmousedown = function () { return false; }; +document.onmouseup = function () { return true; }; + + +var scroller; +var timer = 0; + +function failure() { + ok(false, scroller.onscroll + " did not run!"); + scroller.onscroll = null; + finish(); +} + +function nextTest() { + clearTimeout(timer); + scroller.onscroll = tests.shift(); + timer = setTimeout(failure, 2000); +} + +function runTests() { + scroller = document.getElementById("scroller"); + nextTest(); + window.location.hash = "initialPosition"; +} + +function finish() { + document.onmousedown = null; + document.onmouseup = null; + clearTimeout(timer); + window.location.hash = "topPosition"; + SimpleTest.finish(); +} + + +</script> +</pre> +<div id="scroller" style="overflow: scroll; width: 100px; height: 150px;"> +<a id="topPosition" name="topPosition">top</a> +<div style="width: 20000px; height: 20000px;"></div> +<a id="initialPosition" name="initialPosition">initialPosition</a> +<div style="width: 20000px; height: 20000px;"></div> +</div> +</body> +</html> diff --git a/layout/xul/base/test/test_popupSizeTo.xul b/layout/xul/base/test/test_popupSizeTo.xul new file mode 100644 index 000000000..a135e1980 --- /dev/null +++ b/layout/xul/base/test/test_popupSizeTo.xul @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +XUL Panel sizeTo tests +--> +<window title="XUL Panel sizeTo tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function openPopup() + { + document.getElementById("panel"). + openPopupAtScreen(Math.round(window.mozInnerScreenX) + window.innerWidth - 130, + Math.round(window.mozInnerScreenY) + window.innerHeight - 130); + } + + function sizeAndCheck(width, height) { + var panel = document.getElementById("panel"); + panel.sizeTo(width, height); + is(panel.getBoundingClientRect().width, width, "width is correct"); + is(panel.getBoundingClientRect().height, height, "height is correct"); + + } + function popupShown(event) + { + var panel = document.getElementById("panel"); + var bcr = panel.getBoundingClientRect(); + // resize to 10px bigger in both dimensions. + sizeAndCheck(bcr.width+10, bcr.height+10); + // Same width, different height (based on *new* size from last sizeAndCheck) + sizeAndCheck(bcr.width+10, bcr.height); + // Same height, different width (also based on *new* size from last sizeAndCheck) + sizeAndCheck(bcr.width, bcr.height); + event.target.hidePopup(); + } + + SimpleTest.waitForFocus(openPopup); + ]]></script> + +<panel id="panel" onpopupshown="popupShown(event)" onpopuphidden="SimpleTest.finish()"> + <resizer id="resizer" dir="bottomend" width="16" height="16"/> + <hbox width="50" height="50" flex="1"/> +</panel> + +</window> diff --git a/layout/xul/base/test/test_resizer.xul b/layout/xul/base/test/test_resizer.xul new file mode 100644 index 000000000..2ba971d05 --- /dev/null +++ b/layout/xul/base/test/test_resizer.xul @@ -0,0 +1,94 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +XUL <resizer> tests +--> +<window title="XUL resizer tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + SimpleTest.ignoreAllUncaughtExceptions(); + + function openPopup() + { + document.getElementById("panel"). + openPopupAtScreen(Math.round(window.mozInnerScreenX) + window.innerWidth - 130, + Math.round(window.mozInnerScreenY) + window.innerHeight - 130); + } + + var step = 0; + function popupShown(event) + { + if (step == 0) { + // check to make sure that the popup cannot be resized past the edges of + // the content area + var resizerrect = document.getElementById("resizer").getBoundingClientRect(); + synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mousedown" }); + synthesizeMouse(document.documentElement, resizerrect.left + 2000, resizerrect.top + 2000, { type:"mousemove" }); + + // allow a one pixel variance as rounding is always done to the inside + // of a rectangle. + var popuprect = document.getElementById("panel").getBoundingClientRect(); + ok(Math.round(popuprect.right) == window.innerWidth || + Math.round(popuprect.right) == window.innerWidth - 1, + "resized to content edge width"); + ok(Math.round(popuprect.bottom) == window.innerHeight || + Math.round(popuprect.bottom) == window.innerHeight - 1, + "resized to content edge height"); + + resizerrect = document.getElementById("resizer").getBoundingClientRect(); + synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mouseup" }); + } + else { + // the popup is opened twice. Make sure that for the second time, the + // resized popup opens in the same direction as there should still be + // room for it + var popuprect = document.getElementById("panel").getBoundingClientRect(); + is(Math.round(popuprect.left), window.innerWidth - 130, "reopen popup left"); + is(Math.round(popuprect.top), window.innerHeight - 130, "reopen popup top"); + } + + event.target.hidePopup(); + } + + function doResizerWindowTests() { + step++; + if (step == 1) { + openPopup(); + return; + } + + if (/Mac/.test(navigator.platform)) { + window.open("window_resizer.xul", "_blank", "left=200,top=200,outerWidth=300,outerHeight=300,chrome"); + } + else { + // Skip window_resizer.xul tests. + todo(false, "We can't test GTK and Windows native drag resizing implementations."); + // Run window_resizer_element.xul test only. + lastResizerTest(); + } + } + + function lastResizerTest() + { + window.open("window_resizer_element.xul", "_blank", "left=200,top=200,outerWidth=300,outerHeight=300,chrome"); + } + + SimpleTest.waitForFocus(openPopup); + ]]></script> + +<panel id="panel" onpopupshown="popupShown(event)" onpopuphidden="doResizerWindowTests()"> + <resizer id="resizer" dir="bottomend" width="16" height="16"/> + <hbox width="50" height="50" flex="1"/> +</panel> + +</window> diff --git a/layout/xul/base/test/test_resizer_incontent.xul b/layout/xul/base/test/test_resizer_incontent.xul new file mode 100644 index 000000000..068bd5bb1 --- /dev/null +++ b/layout/xul/base/test/test_resizer_incontent.xul @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> +<!-- +This test ensures that a resizer in content doesn't resize the window. +--> +<window title="XUL resizer in content test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function testResizer() + { + var oldScreenX = window.screenX; + var oldScreenY = window.screenY; + var oldWidth = window.outerWidth; + var oldHeight = window.outerHeight; + var resizer = document.getElementById("resizer"); + synthesizeMouseAtCenter(resizer, { type:"mousedown" }); + synthesizeMouse(resizer, 32, 32, { type:"mousemove" }); + synthesizeMouse(resizer, 32, 32, { type:"mouseup" }); + is(window.screenX, oldScreenX, "window not moved for non-chrome window screenX"); + is(window.screenY, oldScreenY, "window not moved for non-chrome window screenY"); + is(window.outerWidth, oldWidth, "window not moved for non-chrome window outerWidth"); + is(window.outerHeight, oldHeight, "window not moved for non-chrome window outerHeight"); + SimpleTest.finish(); + } + + SimpleTest.waitForFocus(testResizer); + ]]></script> + + <resizer id="resizer" dir="bottomend" width="16" height="16"/> + +</window> diff --git a/layout/xul/base/test/test_splitter.xul b/layout/xul/base/test/test_splitter.xul new file mode 100644 index 000000000..697ad4be8 --- /dev/null +++ b/layout/xul/base/test/test_splitter.xul @@ -0,0 +1,117 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> +<?xml-stylesheet href="data:text/css, hbox { border: 1px solid red; } vbox { border: 1px solid green }" type="text/css"?> +<!-- +XUL <splitter> collapsing tests +--> +<window title="XUL splitter collapsing tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + orient="horizontal"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function dragSplitter(offsetX, callback) { + var splitterWidth = splitter.boxObject.width; + synthesizeMouse(splitter, splitterWidth / 2, 2, {type: "mousedown"}); + synthesizeMouse(splitter, splitterWidth / 2, 1, {type: "mousemove"}); + SimpleTest.executeSoon(function() { + SimpleTest.is(splitter.getAttribute("state"), "dragging", "The splitter should be dragged"); + synthesizeMouse(splitter, offsetX, 1, {type: "mousemove"}); + synthesizeMouse(splitter, offsetX, 1, {type: "mouseup"}); + SimpleTest.executeSoon(callback); + }); + } + + function shouldBeCollapsed(where) { + SimpleTest.is(splitter.getAttribute("state"), "collapsed", "The splitter should be collapsed"); + SimpleTest.is(splitter.getAttribute("substate"), where, "The splitter should be collapsed " + where); + } + + function shouldNotBeCollapsed() { + SimpleTest.is(splitter.getAttribute("state"), "", "The splitter should not be collapsed"); + } + + function runPass(rightCollapsed, leftCollapsed, callback) { + var containerWidth = container.boxObject.width; + var isRTL = getComputedStyle(splitter, null).direction == "rtl"; + dragSplitter(containerWidth, function() { + if (rightCollapsed) { + shouldBeCollapsed(isRTL ? "before" : "after"); + } else { + shouldNotBeCollapsed(); + } + dragSplitter(-containerWidth * 2, function() { + if (leftCollapsed) { + shouldBeCollapsed(isRTL ? "after" : "before"); + } else { + shouldNotBeCollapsed(); + } + dragSplitter(containerWidth / 2, function() { + // the splitter should never be collapsed in the middle + shouldNotBeCollapsed(); + callback(); + }); + }); + }); + } + + var splitter, container; + function runLTRTests(callback) { + splitter = document.getElementById("ltr-splitter"); + container = splitter.parentNode; + splitter.setAttribute("collapse", "before"); + runPass(false, true, function() { + splitter.setAttribute("collapse", "after"); + runPass(true, false, function() { + splitter.setAttribute("collapse", "both"); + runPass(true, true, callback); + }); + }); + } + + function runRTLTests(callback) { + splitter = document.getElementById("rtl-splitter"); + container = splitter.parentNode; + splitter.setAttribute("collapse", "before"); + runPass(true, false, function() { + splitter.setAttribute("collapse", "after"); + runPass(false, true, function() { + splitter.setAttribute("collapse", "both"); + runPass(true, true, callback); + }); + }); + } + + function runTests() { + runLTRTests(function() { + runRTLTests(function() { + SimpleTest.finish(); + }); + }); + } + + addLoadEvent(function() {SimpleTest.executeSoon(runTests);}); + ]]></script> + + <hbox style="max-width: 200px; height: 300px; direction: ltr;"> + <vbox style="width: 100px; height: 300px;" flex="1"/> + <splitter id="ltr-splitter"/> + <vbox style="width: 100px; height: 300px;" flex="1"/> + </hbox> + + <hbox style="max-width: 200px; height: 300px; direction: rtl;"> + <vbox style="width: 100px; height: 300px;" flex="1"/> + <splitter id="rtl-splitter"/> + <vbox style="width: 100px; height: 300px;" flex="1"/> + </hbox> + +</window> diff --git a/layout/xul/base/test/test_stack.xul b/layout/xul/base/test/test_stack.xul new file mode 100644 index 000000000..fdfb6977f --- /dev/null +++ b/layout/xul/base/test/test_stack.xul @@ -0,0 +1,327 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window align="start" title="XUL stack tests" onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- a * before an expected value means an offset from the right or bottom edge --> + <stack id="stack"> + <hbox id="left-top" left="10" top="12" width="20" height="24" + expectedleft="10" expectedtop="12" expectedright="30" expectedbottom="36" + stackwidth="30" stackheight="36"/> + <hbox id="start-top" start="10" top="12" width="20" height="24" + expectedleft="10" expectedtop="12" expectedright="30" expectedbottom="36" + stackwidth="30" stackheight="36"/> + <hbox id="right-bottom" right="10" bottom="12" width="20" height="24" + expectedleft="*30" expectedtop="*36" expectedright="*10" expectedbottom="*12" + stackwidth="30" stackheight="36"/> + <hbox id="end-bottom" end="10" bottom="12" width="20" height="24" + expectedleft="*30" expectedtop="*36" expectedright="*10" expectedbottom="*12" + stackwidth="30" stackheight="36"/> + <hbox id="left-bottom" left="18" bottom="15" width="16" height="19" + expectedleft="18" expectedtop="*34" expectedright="34" expectedbottom="*15" + stackwidth="34" stackheight="34"/> + <hbox id="start-bottom" start="18" bottom="15" width="16" height="19" + expectedleft="18" expectedtop="*34" expectedright="34" expectedbottom="*15" + stackwidth="34" stackheight="34"/> + <hbox id="right-top" right="5" top="8" width="10" height="11" + expectedleft="*15" expectedtop="8" expectedright="*5" expectedbottom="19" + stackwidth="15" stackheight="19"/> + <hbox id="end-top" end="5" top="8" width="10" height="11" + expectedleft="*15" expectedtop="8" expectedright="*5" expectedbottom="19" + stackwidth="15" stackheight="19"/> + <hbox id="left-right" left="12" right="9" width="15" height="6" + expectedleft="12" expectedtop="0" expectedright="*9" expectedbottom="*0" + stackwidth="36" stackheight="6"/> + <hbox id="start-right" start="12" right="9" width="15" height="6" + expectedleft="12" expectedtop="0" expectedright="*9" expectedbottom="*0" + stackwidth="36" stackheight="6"/> + <hbox id="left-end" start="12" end="9" width="15" height="6" + expectedleft="12" expectedtop="0" expectedright="*9" expectedbottom="*0" + stackwidth="36" stackheight="6"/> + <hbox id="start-end" start="12" end="9" width="15" height="6" + expectedleft="12" expectedtop="0" expectedright="*9" expectedbottom="*0" + stackwidth="36" stackheight="6"/> + <hbox id="top-bottom" top="20" bottom="39" width="15" height="6" + expectedleft="0" expectedtop="20" expectedright="*0" expectedbottom="*39" + stackwidth="15" stackheight="65"/> + <hbox id="left-right-top-bottom" style="left: 5px; top: 5px; right: 8px; bottom: 8px;" + left="16" top="20" right="20" bottom="35" width="7" height="8" + expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35" + stackwidth="43" stackheight="63"/> + <hbox id="start-right-top-bottom" style="left: 5px; top: 5px; right: 8px; bottom: 8px;" + start="16" top="20" right="20" bottom="35" width="7" height="8" + expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35" + stackwidth="43" stackheight="63"/> + <hbox id="left-end-top-bottom" style="left: 5px; top: 5px; right: 8px; bottom: 8px;" + left="16" top="20" end="20" bottom="35" width="7" height="8" + expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35" + stackwidth="43" stackheight="63"/> + <hbox id="start-end-top-bottom" style="left: 5px; top: 5px; right: 8px; bottom: 8px;" + start="16" top="20" end="20" bottom="35" width="7" height="8" + expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35" + stackwidth="43" stackheight="63"/> + <hbox id="left-right-top-bottom-nosize" left="16" top="20" right="20" bottom="35" + expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35" + stackwidth="36" stackheight="55"/> + <hbox id="start-right-top-bottom-nosize" start="16" top="20" right="20" bottom="35" + expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35" + stackwidth="36" stackheight="55"/> + <hbox id="left-end-top-bottom-nosize" left="16" top="20" end="20" bottom="35" + expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35" + stackwidth="36" stackheight="55"/> + <hbox id="start-end-top-bottom-nosize" start="16" top="20" end="20" bottom="35" + expectedleft="16" expectedtop="20" expectedright="*20" expectedbottom="*35" + stackwidth="36" stackheight="55"/> + <hbox id="none" width="10" height="12" expectedleft="0" expectedtop="0" expectedright="*0" expectedbottom="*0" + stackwidth="10" stackheight="12"/> + <hbox id="none-nosize" expectedleft="0" expectedtop="0" expectedright="*0" expectedbottom="*0" + stackwidth="0" stackheight="0"/> + <hbox id="style-left-right-top-bottom" + style="left: 17px; top: 20px;" right="20" bottom="35" width="7" height="8" + expectedleft="*27" expectedtop="*43" expectedright="*20" expectedbottom="*35" + stackwidth="27" stackheight="43"/> + <hbox id="style-left-right-top-bottom-nosize" + style="left: 16px; top: 20px; right: 20px; bottom: 35px;" + expectedleft="0" expectedtop="0" expectedright="*0" expectedbottom="*0" + stackwidth="0" stackheight="0"/> + <hbox id="left-large-right" left="20" right="1000" height="6" + expectedleft="20" expectedtop="0" expectedright="20" expectedbottom="*0" + stackwidth="1020" stackheight="6"/> + <hbox id="left-top-with-margin" left="8" top="17" width="20" height="24" + style="margin: 1px 2px 3px 4px;" + expectedleft="12" expectedtop="18" expectedright="32" expectedbottom="42" + stackwidth="34" stackheight="45"/> + <hbox id="right-bottom-with-margin" right="6" bottom="15" width="10" height="14" + style="margin: 1px 2px 3px 4px;" + expectedleft="*18" expectedtop="*32" expectedright="*8" expectedbottom="*18" + stackwidth="22" stackheight="33"/> + <hbox id="left-top-right-bottom-with-margin" left="14" right="6" top="8" bottom="15" width="10" height="14" + style="margin: 1px 2px 3px 4px;" + expectedleft="18" expectedtop="9" expectedright="*8" expectedbottom="*18" + stackwidth="36" stackheight="41"/> + <hbox id="none-with-margin" + style="margin: 1px 2px 3px 4px;" + expectedleft="4" expectedtop="1" expectedright="*2" expectedbottom="*3" + stackwidth="6" stackheight="4"/> + </stack> + + <stack id="stack-with-size" width="12" height="14"> + <hbox id="left-top-with-stack-size" left="10" top="12" width="20" height="24" + expectedleft="10" expectedtop="12" expectedright="30" expectedbottom="36"/> + </stack> + + <stack id="stack-with-start-end" width="30"> + <hbox id="start-with-start-end" start="10" top="12" width="20" height="24" + expectedstart="10" expectedend="30"/> + <hbox id="end-width-start-end" end="5" top="12" width="20" height="24" + expectedstart="5" expectedend="25"/> + <hbox id="start-end-width-start-end" start="12" end="9" width="20" top="12" height="24" + expectedstart="12" expectedend="21"/> + </stack> + + <stack id="stack-with-border" + style="border-left: 4px solid black; border-top: 2px solid black; border-right: 1px solid black; border-bottom: 3px solid black;"> + <hbox id="left-top-with-border" left="10" top="14" width="20" height="24" + expectedleft="14" expectedtop="16" expectedright="34" expectedbottom="40"/> + <hbox id="start-top-with-border" start="10" top="14" width="20" height="24" + expectedleft="14" expectedtop="16" expectedright="34" expectedbottom="40"/> + <hbox id="right-bottom-with-border" right="5" bottom="8" width="6" height="10" + expectedleft="*12" expectedtop="*21" expectedright="*6" expectedbottom="*11"/> + <hbox id="end-bottom-with-border" end="5" bottom="8" width="6" height="10" + expectedleft="*12" expectedtop="*21" expectedright="*6" expectedbottom="*11"/> + <hbox id="left-top-right-bottom-with-border" left="12" right="5" top="18" bottom="8" + expectedleft="16" expectedtop="20" expectedright="*6" expectedbottom="*11"/> + <hbox id="start-top-right-bottom-with-border" start="12" right="5" top="18" bottom="8" + expectedleft="16" expectedtop="20" expectedright="*6" expectedbottom="*11"/> + <hbox id="left-top-end-bottom-with-border" left="12" end="5" top="18" bottom="8" + expectedleft="16" expectedtop="20" expectedright="*6" expectedbottom="*11"/> + <hbox id="start-top-end-bottom-with-border" start="12" end="5" top="18" bottom="8" + expectedleft="16" expectedtop="20" expectedright="*6" expectedbottom="*11"/> + <hbox id="none-with-with-border" + expectedleft="4" expectedtop="2" expectedright="*1" expectedbottom="*3"/> + </stack> + + <stack id="stack-dyn"/> + <stack id="stack-dyn-sized" width="12" height="14"/> + + <body xmlns="http://www.w3.org/1999/xhtml"/> + + <script><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + var stackRect; + var dynStack; + + function compareSide(child, actual, side, dyn, direction) + { + var clientRect = child.getBoundingClientRect(); + var vertical = (side == "top" || side == "bottom"); + var expectedval = child.getAttribute("expected" + side); + if (expectedval.indexOf("*") == 0) + expectedval = (vertical ? stackRect.bottom : stackRect.right) - Number(expectedval.substring(1)); + else if (direction == "rtl") + expectedval = (vertical ? stackRect.top : -stackRect.width + clientRect.right + clientRect.left) + Number(expectedval); + else + expectedval = (vertical ? stackRect.top : stackRect.left) + Number(expectedval); + + is(actual, expectedval, child.id + " " + side + (dyn ? " dynamic" : "")); + } + + function runTest() + { + runTestForStack("stack", false); + runTestForStack("stack-with-size", false); + runTestForStartEndAttributes("stack-with-start-end", "ltr"); + runTestForStartEndAttributes("stack-with-start-end", "rtl"); + + var stackWithSize = $("stack-with-size"); + + var sizedStackRect = stackWithSize.getBoundingClientRect(); + is(sizedStackRect.width, 30, "stack size stretched width"); + is(sizedStackRect.height, 36, "stack size stretched height"); + + // set -moz-stack-sizing: ignore and ensure that the stack does not grow + // to include the child + var item = $("left-top-with-stack-size"); + item.style.MozStackSizing = "ignore"; + var parent = item.parentNode; + parent.removeChild(item); + parent.appendChild(item); + + sizedStackRect = stackWithSize.getBoundingClientRect(); + is(sizedStackRect.width, 12, "stack size not stretched width"); + is(sizedStackRect.height, 14, "stack size not stretched height"); + + testPositionChanges(stackWithSize, true); + item.style.MozStackSizing = ""; + testPositionChanges(stackWithSize, false); + + // now test adding stack children dynamically to ensure that + // the size of the stack adjusts accordingly + dynStack = $("stack-dyn"); + runTestForStack("stack", true); + + SimpleTest.finish(); + } + + function runTestForStartEndAttributes(stackid, aDirection) + { + // Change the direction of the layout to RTL to ensure start/end are + // working as expected + var stack = $(stackid); + stack.style.direction = aDirection; + + var stackRect = stack.getBoundingClientRect(); + var children = stack.childNodes; + for (var c = children.length - 1; c >= 0; c--) { + var child = children[c]; + + // do tests only for elements that have a rtl-enabled mode + if (!child.hasAttribute("start") && !child.hasAttribute("end")) + continue; + + var childrect = child.getBoundingClientRect(); + compareSide(child, childrect.right, "end", false, aDirection); + compareSide(child, childrect.left, "start", false, aDirection); + } + + // Reset the direction + stack.style.direction = "ltr"; + } + + function runTestForStack(stackid, dyn) + { + var stack = $(stackid); + if (!dyn) + stackRect = stack.getBoundingClientRect(); + var children = stack.childNodes; + for (var c = children.length - 1; c >= 0; c--) { + var child = children[c]; + if (dyn) { + // for dynamic tests, get the size after appending the child as the + // stack size will be effected by it + dynStack.appendChild(child); + stackRect = dynStack.getBoundingClientRect(); + is(stackRect.width, child.getAttribute("stackwidth"), child.id + " stack width" + (dyn ? " dynamic" : "")); + is(stackRect.height, child.getAttribute("stackheight"), child.id + " stack height" + (dyn ? " dynamic" : "")); + } + + var childrect = child.getBoundingClientRect(); + compareSide(child, childrect.left, "left", dyn); + compareSide(child, childrect.top, "top", dyn); + compareSide(child, childrect.right, "right", dyn); + compareSide(child, childrect.bottom, "bottom", dyn); + if (dyn) + dynStack.removeChild(child); + } + } + + function testPositionChanges(stack, ignoreStackSizing) + { + var add = ignoreStackSizing ? " ignore stack sizing" : ""; + + // ensure that changing left/top/right/bottom/start/end works + var stackchild = document.getElementById("left-top-with-stack-size"); + stackchild.left = 18; + is(stackchild.getBoundingClientRect().left, stack.getBoundingClientRect().left + 18, "left changed" + add); + is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 38, "left changed stack width" + add); + + stackchild.left = ""; + stackchild.setAttribute("start", "19"); + is(stackchild.getBoundingClientRect().left, stack.getBoundingClientRect().left + 19, "left changed" + add); + is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 39, "left changed stack width" + add); + stackchild.removeAttribute("start"); + stackchild.left = 18; + + stackchild.top = 22; + is(stackchild.getBoundingClientRect().top, stack.getBoundingClientRect().top + 22, "top changed" + add); + is(stack.getBoundingClientRect().height, ignoreStackSizing ? 14 : 46, "left changed stack height" + add); + + stackchild.setAttribute("right", "6"); + is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().left + 18, "right changed" + add); + // the width is only 12 pixels in ignoreStackSizing mode, so don't check the offset + // from the right edge in this case + if (!ignoreStackSizing) + is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().right - 6, + "right changed from right edge" + add); + is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 24, "right changed stack width" + add); + + stackchild.removeAttribute("right"); + stackchild.setAttribute("end", "7"); + is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().left + 18, "right changed" + add); + // the width is only 12 pixels in ignoreStackSizing mode, so don't check the offset + // from the right edge in this case + if (!ignoreStackSizing) + is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().right - 7, + "right changed from right edge" + add); + is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 25, "right changed stack width" + add); + stackchild.removeAttribute("end"); + stackchild.setAttribute("right", "6"); + + stackchild.setAttribute("bottom", "9"); + is(stackchild.getBoundingClientRect().bottom, stack.getBoundingClientRect().top + 22, "bottom changed" + add); + is(stack.getBoundingClientRect().height, ignoreStackSizing ? 14 : 31, "bottom changed stack height" + add); + if (!ignoreStackSizing) + is(stackchild.getBoundingClientRect().bottom, stack.getBoundingClientRect().bottom - 9, + "right changed from bottom edge" + add); + + stackchild.left = ""; + is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().right - 6, "right changed" + add); + is(stack.getBoundingClientRect().width, ignoreStackSizing ? 12 : 26, "right changed no left stack width" + add); + + stackchild.removeAttribute("right"); + is(stackchild.getBoundingClientRect().right, stack.getBoundingClientRect().right, "right cleared" + add); + is(stack.getBoundingClientRect().width, 12, "right cleared stack height" + add); + + // reset the values + stackchild.removeAttribute("bottom"); + stackchild.left = 10; + stackchild.top = 12; + } + + + ]]></script> +</window> diff --git a/layout/xul/base/test/test_windowminmaxsize.xul b/layout/xul/base/test/test_windowminmaxsize.xul new file mode 100644 index 000000000..d41f6a5f5 --- /dev/null +++ b/layout/xul/base/test/test_windowminmaxsize.xul @@ -0,0 +1,240 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Window Minimum and Maximum Size Tests" onload="nextTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<panel id="panel" onpopupshown="doPanelTest(this)" onpopuphidden="nextPopupTest(this)" + align="start" pack="start" style="-moz-appearance: none; margin: 0; border: 0; padding: 0;"> + <resizer id="popupresizer" dir="bottomright" flex="1" width="60" height="60" + style="-moz-appearance: none; margin: 0; border: 0; padding: 0;"/> +</panel> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gTestId = -1; + +var prefix = "data:application/vnd.mozilla.xul+xml,<?xml-stylesheet href='chrome://global/skin' type='text/css'?><window " + + "xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' " + + "align='start' pack='start' style='-moz-appearance: none; margin: 0; padding: 0; border: 0; "; +var suffix = "><resizer dir='bottomright' flex='1' width='150' height='150' style='-moz-appearance: none; margin: 0; border: 0; padding: 0;'/></window>"; +var titledpanel = "<panel noautohide='true' titlebar='normal' minwidth='120' minheight='140'/><label value='Test'/>"; + +// width and height in the tests below specify the expected size of the window. +// note, win8 has a minimum inner window size of around 122 pixels. Don't go below this on min-width tests. +var tests = [ + { testname: "unconstrained", + style: "", attrs: "", + width: 150, height: 150 }, + { testname: "constraint min style", + style: "min-width: 180px; min-height: 210px;", attrs: "", + width: 180, height: 210 }, + { testname: "constraint max style", + style: "max-width: 125px; max-height: 140px;", attrs: "", + width: 125, height: 140 }, + { testname: "constraint min attributes", + style: "", attrs: "minwidth='240' minheight='220'", + width: 240, height: 220 }, + { testname: "constraint min attributes with width and height set", + style: "", attrs: "width='190' height='220' minwidth='215' minheight='235'", + width: 215, height: 235 }, + { testname: "constraint max attributes", + style: "", attrs: "maxwidth='125' maxheight='95'", + width: 125, height: 95 }, + // this gets the inner width as <window minwidth='210'> makes the box 210 pixels wide + { testname: "constraint min width attribute only", + style: "", attrs: "minwidth='210'", + width: 210, height: 150 }, + { testname: "constraint max width attribute only", + style: "", attrs: "maxwidth='128'", + width: 128, height: 150 }, + { testname: "constraint max width attribute with minheight", + style: "", attrs: "maxwidth='195' width='230' height='120' minheight='180'", + width: 195, height: 180 }, + { testname: "constraint minwidth, minheight, maxwidth and maxheight set", + style: "", attrs: "minwidth='120' maxwidth='480' minheight='110' maxheight='470'", + width: 150, height: 150, last: true } +]; + +var popupTests = [ + { testname: "popup unconstrained", + width: 60, height: 60 + }, + { testname: "popup with minimum size", + minwidth: 150, minheight: 180, + width: 150, height: 180 + }, + { testname: "popup with maximum size", + maxwidth: 50, maxheight: 45, + width: 50, height: 45, + }, + { testname: "popup with minimum and size", + minwidth: 80, minheight: 70, maxwidth: 250, maxheight: 220, + width: 80, height: 70, last: true + } +]; + +function nextTest() +{ + // Run through each of the tests above by opening a simple window with + // the attributes or style defined for that test. The comparisons will be + // done by windowOpened. gTestId holds the index into the tests array. + if (++gTestId >= tests.length) { + // Now do the popup tests + gTestId = -1; + SimpleTest.waitForFocus(function () { nextPopupTest(document.getElementById("panel")) } ); + } + else { + tests[gTestId].window = window.open(prefix + tests[gTestId].style + "' " + tests[gTestId].attrs + suffix, "_blank", "chrome,resizable=yes"); + SimpleTest.waitForFocus(windowOpened, tests[gTestId].window); + } +} + +function windowOpened(otherWindow) +{ + // Check the width and the width plus one due to bug 696746. + ok(otherWindow.innerWidth == tests[gTestId].width || + otherWindow.innerWidth == tests[gTestId].width + 1, + tests[gTestId].testname + " width of " + otherWindow.innerWidth + " matches " + tests[gTestId].width); + is(otherWindow.innerHeight, tests[gTestId].height, tests[gTestId].testname + " height"); + + // On the last test, try moving the resizer to a size larger than the maximum + // and smaller than the minimum. This test is only done on Mac as the other + // platforms use native resizing. + if ('last' in tests[gTestId] && (navigator.platform.indexOf("Mac") == 0)) { + var resizer = otherWindow.document.documentElement.firstChild; + synthesizeMouse(resizer, 4, 4, { type:"mousedown" }, otherWindow); + synthesizeMouse(resizer, 800, 800, { type:"mousemove" }, otherWindow); + is(otherWindow.innerWidth, 480, "Width after maximum resize"); + is(otherWindow.innerHeight, 470, "Height after maximum resize"); + + synthesizeMouse(resizer, -100, -100, { type:"mousemove" }, otherWindow); + is(otherWindow.innerWidth, 120, "Width after minimum resize"); + is(otherWindow.innerHeight, 110, "Height after minimum resize"); + + synthesizeMouse(resizer, 4, 4, { type:"mouseup" }, otherWindow); + + // Change the minimum and maximum size and try resizing the window again. + otherWindow.document.documentElement.minWidth = 140; + otherWindow.document.documentElement.minHeight = 130; + otherWindow.document.documentElement.maxWidth = 380; + otherWindow.document.documentElement.maxHeight = 360; + + synthesizeMouse(resizer, 4, 4, { type:"mousedown" }, otherWindow); + synthesizeMouse(resizer, 800, 800, { type:"mousemove" }, otherWindow); + is(otherWindow.innerWidth, 380, "Width after changed maximum resize"); + is(otherWindow.innerHeight, 360, "Height after changed maximum resize"); + + synthesizeMouse(resizer, -100, -100, { type:"mousemove" }, otherWindow); + is(otherWindow.innerWidth, 140, "Width after changed minimum resize"); + is(otherWindow.innerHeight, 130, "Height after changed minimum resize"); + + synthesizeMouse(resizer, 4, 4, { type:"mouseup" }, otherWindow); + } + + otherWindow.close(); + nextTest(); +} + +function doPanelTest(panel) +{ + var rect = panel.getBoundingClientRect(); + is(rect.width, popupTests[gTestId].width, popupTests[gTestId].testname + " width"); + is(rect.height, popupTests[gTestId].height, popupTests[gTestId].testname + " height"); + + if ('last' in popupTests[gTestId]) { + var resizer = document.getElementById("popupresizer"); + synthesizeMouse(resizer, 4, 4, { type:"mousedown" }); + synthesizeMouse(resizer, 800, 800, { type:"mousemove" }); + + rect = panel.getBoundingClientRect(); + is(rect.width, 250, "Popup width after maximum resize"); + is(rect.height, 220, "Popup height after maximum resize"); + + synthesizeMouse(resizer, -100, -100, { type:"mousemove" }); + + rect = panel.getBoundingClientRect(); + is(rect.width, 80, "Popup width after minimum resize"); + is(rect.height, 70, "Popup height after minimum resize"); + + synthesizeMouse(resizer, 4, 4, { type:"mouseup" }); + } + + panel.hidePopup(); +} + +function nextPopupTest(panel) +{ + if (++gTestId >= popupTests.length) { + // Next, check a panel that has a titlebar to ensure that it is accounted for + // properly in the size. + var titledPanelWindow = window.open(prefix + "'>" + titledpanel + "</window>", "_blank", "chrome,resizable=yes"); + SimpleTest.waitForFocus(titledPanelWindowOpened, titledPanelWindow); + } + else { + function setattr(attr) { + if (attr in popupTests[gTestId]) + panel.setAttribute(attr, popupTests[gTestId][attr]); + else + panel.removeAttribute(attr); + } + setattr("minwidth"); + setattr("minheight"); + setattr("maxwidth"); + setattr("maxheight"); + + // Remove the flexibility as it causes the resizer to not shrink down + // when resizing. + if ("last" in popupTests[gTestId]) + document.getElementById("popupresizer").removeAttribute("flex"); + + panel.openPopup(); + } +} + +function titledPanelWindowOpened(panelwindow) +{ + var panel = panelwindow.document.documentElement.firstChild; + panel.openPopup(); + panel.addEventListener("popupshown", function() doTitledPanelTest(panel), false); + panel.addEventListener("popuphidden", function() done(panelwindow), false); +} + +function doTitledPanelTest(panel) +{ + var rect = panel.getBoundingClientRect(); + is(rect.width, 120, "panel with titlebar width"); + is(rect.height, 140, "panel with titlebar height"); + panel.hidePopup(); +} + +function done(panelwindow) +{ + panelwindow.close(); + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/layout/xul/base/test/window_resizer.xul b/layout/xul/base/test/window_resizer.xul new file mode 100644 index 000000000..4e349d125 --- /dev/null +++ b/layout/xul/base/test/window_resizer.xul @@ -0,0 +1,113 @@ +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + screenX="200" screenY="200" width="300" height="300" + onload="setTimeout(doTest, 0)"> +<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<script><![CDATA[ +var is = window.opener.SimpleTest.is; + +function doTest() { + // from test_resizer.xul + var expectX = 200; + var expectY = 200; + var expectXMost = 500; + var expectYMost = 500; + var screenScale = expectX/window.screenX; + var root = document.documentElement; + + var oldScreenX = window.screenX; + var oldScreenY = window.screenY; + var oldWidth = window.outerWidth; + var oldHeight = window.outerHeight; + + function testResizer(dx, dy) { + var offset = 20; + var scale = 5; + // target the centre of the resizer + var offsetX = window.innerWidth/2 + (window.innerWidth/3)*dx; + var offsetY = window.innerHeight/2 + (window.innerHeight/3)*dy; + + for (var mouseX = -1; mouseX <= 1; ++mouseX) { + for (var mouseY = -1; mouseY <= 1; ++mouseY) { + var newExpectX = expectX; + var newExpectXMost = expectXMost; + var newExpectY = expectY; + var newExpectYMost = expectYMost; + if (dx < 0) { + newExpectX += mouseX*scale; + } else if (dx > 0) { + newExpectXMost += mouseX*scale; + } + if (dy < 0) { + newExpectY += mouseY*scale; + } else if (dy > 0) { + newExpectYMost += mouseY*scale; + } + + synthesizeMouse(root, offsetX, offsetY, { type:"mousedown" }); + synthesizeMouse(root, offsetX + mouseX*scale, offsetY + mouseY*scale, { type:"mousemove" }); + is(window.screenX*screenScale, newExpectX, + "Bad x for " + dx + "," + dy + " moving " + mouseX + "," + mouseY); + is(window.screenY*screenScale, newExpectY, + "Bad y for " + dx + "," + dy + " moving " + mouseX + "," + mouseY); + is(window.outerWidth, newExpectXMost - newExpectX, + "Bad width for " + dx + "," + dy + " moving " + mouseX + "," + mouseY); + is(window.outerHeight, newExpectYMost - newExpectY, + "Bad height for " + dx + "," + dy + " moving " + mouseX + "," + mouseY); + + // move it back before we release! Adjust for any window movement + synthesizeMouse(root, offsetX - (newExpectX - expectX), + offsetY - (newExpectY - expectY), { type:"mousemove" }); + synthesizeMouse(root, offsetX, offsetY, { type:"mouseup" }); + } + } + } + + testResizer(-1, -1); + testResizer(-1, 0); + testResizer(-1, 1); + testResizer(0, -1); + testResizer(0, 1); + testResizer(1, -1); + testResizer(1, 0); + testResizer(1, 1); + + var resizers = document.getElementsByTagName("resizer"); + Array.forEach(resizers, function (element) { + is(getComputedStyle(element, "").cursor, + element.getAttribute("expectedcursor"), + "cursor for " + element.dir); + }); + + // now check the cursors in rtl. The bottomend resizer + // should be reversed + document.getElementById("bottomend").setAttribute("rtl", "true"); + Array.forEach(resizers, function (element) { + is(getComputedStyle(element, "").cursor, + element.dir == "bottomend" ? "sw-resize" : + element.getAttribute("expectedcursor"), + "cursor for " + element.dir); + }); + + window.close(); + window.opener.lastResizerTest(); +} +]]></script> + <hbox id="container" flex="1"> + <vbox flex="1"> + <resizer dir="topleft" expectedcursor="nw-resize" flex="1"/> + <resizer dir="left" expectedcursor="ew-resize" flex="1"/> + <resizer dir="bottomleft" expectedcursor="sw-resize" flex="1"/> + </vbox> + <vbox flex="1"> + <resizer dir="top" expectedcursor="ns-resize" flex="1"/> + <resizer id="bottomend" dir="bottomend" expectedcursor="se-resize" flex="1"/> + <resizer dir="bottom" expectedcursor="ns-resize" flex="1"/> + </vbox> + <vbox flex="1"> + <resizer dir="topright" expectedcursor="ne-resize" flex="1"/> + <resizer dir="right" expectedcursor="ew-resize" flex="1"/> + <resizer dir="bottomright" expectedcursor="se-resize" flex="1"/> + </vbox> + </hbox> +</window> diff --git a/layout/xul/base/test/window_resizer_element.xul b/layout/xul/base/test/window_resizer_element.xul new file mode 100644 index 000000000..b0c58d1a1 --- /dev/null +++ b/layout/xul/base/test/window_resizer_element.xul @@ -0,0 +1,188 @@ +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + align="start"> +<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<script><![CDATA[ +var is = window.opener.SimpleTest.is; +window.onerror = window.opener.onerror; + +const anchorPositions = + [ "before_start", "before_end", "after_start", "after_end", + "start_before", "start_after", "end_before", "end_after", "overlap", "screen"]; +var currentPosition; + +function testResizer(resizerid, noShrink, hResize, vResize, testid) +{ + var rect = document.getElementById(resizerid + "-container").getBoundingClientRect(); + var resizer = document.getElementById(resizerid); + var resizerrect = resizer.getBoundingClientRect(); + + var originalX = resizerrect.left; + var originalY = resizerrect.top; + + const scale = 20; + for (var mouseX = -1; mouseX <= 1; ++mouseX) { + for (var mouseY = -1; mouseY <= 1; ++mouseY) { + var expectedWidth = rect.width + hResize * mouseX * scale; + var expectedHeight = rect.height + vResize * mouseY * scale; + + if (noShrink) { + if (mouseX == -1) + expectedWidth = rect.width; + if (mouseY == -1) + expectedHeight = rect.height; + } + + synthesizeMouse(document.documentElement, originalX + 5, originalY + 5, { type:"mousedown" }); + synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale, + originalY + 5 + mouseY * scale, { type:"mousemove" }); + + var newrect = document.getElementById(resizerid + "-container").getBoundingClientRect(); + is(Math.round(newrect.width), Math.round(expectedWidth), "resize element " + resizerid + + " " + testid + " width moving " + mouseX + "," + mouseY + ",,," + hResize); + is(Math.round(newrect.height), Math.round(expectedHeight), "resize element " + resizerid + + " " + testid + " height moving " + mouseX + "," + mouseY); + // release + synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale, + originalY + 5 + mouseY * scale, { type:"mouseup" }); + // return to the original size + synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale, + originalY + 5 + mouseY * scale, { type:"dblclick" }); + var newrect = document.getElementById(resizerid + "-container").getBoundingClientRect(); + is(Math.round(newrect.width), Math.round(rect.width), "resize element " + resizerid + + " " + testid + " doubleclicking to restore original size"); + is(Math.round(newrect.height), Math.round(rect.height), "resize element " + resizerid + + " " + testid + " doubleclicking to restore original size"); + } + } +} + +function doTest() { + // first, check if a resizer with a element attribute set to an element that + // does not exist does not cause a problem + var resizer = document.getElementById("notfound"); + synthesizeMouse(resizer, 5, 5, { type:"mousedown" }); + synthesizeMouse(resizer, 10, 10, { type:"mousemove" }); + synthesizeMouse(resizer, 5, 5, { type:"mouseup" }); + + testResizer("outside", true, 1, 1, ""); + testResizer("html", true, 1, 1, ""); + testResizer("inside", true, 1, 1, ""); + testResizer("inside-large", false, 1, 1, ""); + testResizer("inside-with-border", true, 1, 1, ""); + + document.getElementById("inside-popup-container"). + openPopupAtScreen(Math.ceil(window.mozInnerScreenX) + 100, Math.ceil(window.mozInnerScreenY) + 100); +} + +function popupShown(event) +{ + testResizer("inside-popup", false, 1, 1, ""); + document.getElementById("inside-popup-container").id = "outside-popup-container"; + testResizer("outside-popup", false, 1, 1, ""); + + var resizerrect = document.getElementById("inside-popup").getBoundingClientRect(); + synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mousedown" }); + synthesizeMouse(document.documentElement, resizerrect.left + 2000, resizerrect.top + 2000, { type:"mousemove" }); + + var isMac = (navigator.platform.indexOf("Mac") >= 0); + var popuprect = document.getElementById("outside-popup-container").getBoundingClientRect(); + // subtract 3 due to space left for panel dropshadow + is(Math.ceil(window.mozInnerScreenX) + popuprect.right, + (isMac ? screen.availLeft + screen.availWidth : screen.left + screen.width) - 3, "resized to edge width"); + is(Math.ceil(window.mozInnerScreenY) + popuprect.bottom, + (isMac ? screen.availTop + screen.availHeight : screen.top + screen.height) - 3, "resized to edge height"); + + resizerrect = document.getElementById("inside-popup").getBoundingClientRect(); + synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mouseup" }); + + event.target.hidePopup(); +} + +function popupHidden() +{ + if (anchorPositions.length == 0) { + window.close(); + window.opener.SimpleTest.finish(); + return; + } + + currentPosition = anchorPositions.shift(); + var anchor = document.getElementById("anchor"); + var popup = document.getElementById("anchored-panel-container"); + + if (currentPosition == "screen") + popup.openPopupAtScreen(window.screenX + 100, window.screenY + 100); + else + popup.openPopup(anchor, currentPosition); +} + +function anchoredPopupShown(event) +{ + var leftAllowed = (currentPosition.indexOf("end_") == -1 && currentPosition.indexOf("_start") == -1); + var rightAllowed = (currentPosition.indexOf("start_") == -1 && currentPosition.indexOf("_end") == -1); + var topAllowed = (currentPosition.indexOf("after_") == -1 && currentPosition.indexOf("_before") == -1); + var bottomAllowed = (currentPosition.indexOf("before_") == -1 && currentPosition.indexOf("_after") == -1); + + if (currentPosition == "overlap") { + leftAllowed = topAllowed = false; + rightAllowed = bottomAllowed = true; + } + + var resizerTypes = [ "topleft", "top", "topright", "left", "right", + "bottomleft", "bottom", "bottomright", "bottomend" ]; + for (var r = 0; r < resizerTypes.length; r++) { + var resizerType = resizerTypes[r]; + var horiz = 0, vert = 0; + if (leftAllowed && resizerType.indexOf("left") >= 0) horiz = -1; + else if (rightAllowed && (resizerType.indexOf("right") >= 0 || resizerType == "bottomend")) horiz = 1; + + if (topAllowed && resizerType.indexOf("top") >= 0) vert = -1; + else if (bottomAllowed && resizerType.indexOf("bottom") >= 0) vert = 1; + + document.getElementById("anchored-panel").dir = resizerType; + testResizer("anchored-panel", false, horiz, vert, currentPosition + " " + resizerType); + } + + event.target.hidePopup(); +} + +window.opener.SimpleTest.waitForFocus(doTest, window); +]]></script> + +<resizer id="outside" dir="bottomend" element="outside-container"/> +<resizer id="notfound" dir="bottomend" element="nothing"/> +<hbox id="outside-container"> + <hbox minwidth="46" minheight="39"/> +</hbox> +<html:div id="html-container" xmlns:html="http://www.w3.org/1999/xhtml"> + <html:button>One</html:button><html:br/> + <resizer id="html" dir="bottomend" element="_parent"/> +</html:div> +<hbox id="anchor" align="start" style="margin-left: 100px;"> + <hbox id="inside-container" align="start"> + <hbox minwidth="45" minheight="41"/> + <resizer id="inside" dir="bottomend" element="_parent"/> + </hbox> + <hbox id="inside-large-container" width="70" height="70" align="start"> + <resizer id="inside-large" dir="bottomend" element="_parent"/> + </hbox> + <hbox id="inside-with-border-container" style="border: 5px solid red; padding: 2px; margin: 2px;" align="start"> + <hbox minwidth="35" minheight="30"/> + <resizer id="inside-with-border" dir="bottomend" element="_parent"/> + </hbox> +</hbox> + +<panel id="inside-popup-container" align="start" onpopupshown="popupShown(event)" onpopuphidden="popupHidden()"> + <resizer id="inside-popup" dir="bottomend"/> + <hbox width="50" height="50" flex="1"/> +</panel> +<resizer id="outside-popup" dir="bottomend" element="outside-popup-container"/> + +<panel id="anchored-panel-container" align="start" onpopupshown="anchoredPopupShown(event)" + onpopuphidden="popupHidden()"> + <hbox width="50" height="50" flex="1"/> + <resizer id="anchored-panel" width="20" height="20"/> +</panel> + +</window> diff --git a/layout/xul/grid/Makefile.in b/layout/xul/grid/Makefile.in new file mode 100644 index 000000000..964a8fb1d --- /dev/null +++ b/layout/xul/grid/Makefile.in @@ -0,0 +1,29 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +LIBXUL_LIBRARY = 1 +FAIL_ON_WARNINGS = 1 + +LOCAL_INCLUDES = \ + -I$(srcdir) \ + -I$(srcdir)/../base/src \ + -I$(srcdir)/../../generic \ + -I$(srcdir)/../../style \ + -I$(srcdir)/../../forms \ + $(NULL) + +# we don't want the shared lib, but we want to force the creation of a static lib. +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/config/rules.mk + +DEFINES += -D_IMPL_NS_LAYOUT diff --git a/layout/xul/grid/crashtests/306911-crash.xul b/layout/xul/grid/crashtests/306911-crash.xul new file mode 100644 index 000000000..cf55dfdf8 --- /dev/null +++ b/layout/xul/grid/crashtests/306911-crash.xul @@ -0,0 +1,4 @@ +<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<listbox id="thelist" flex="1">
<listitem label="Item1" value="item1"> + <listitem label="Item2" value="item2"/> + </listitem>
</listbox>
+</window>
\ No newline at end of file diff --git a/layout/xul/grid/crashtests/306911-grid-testcases.xul b/layout/xul/grid/crashtests/306911-grid-testcases.xul new file mode 100644 index 000000000..bb69f5bcd --- /dev/null +++ b/layout/xul/grid/crashtests/306911-grid-testcases.xul @@ -0,0 +1,99 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <tabbox> + <tabs> + <tab label="full grid" /> + <tab label="grid alone" /> + <tab label="columns alone" /> + <tab label="rows alone" /> + <tab label="column alone" /> + <tab label="row alone" /> + <tab label="wacky" /> + </tabs> + <tabpanels> + <tabpanel> + <grid> + <rows style="color: blue"> + <row> + <label value="row 1,1" /> + <label value="row 1,2" /> + </row> + <row> + <label value="row 2,1" /> + <label value="row 2,2" /> + </row> + </rows> + <columns style="color: fuchsia; opacity: 0.7"> + <column> + <label value="column 1,1" /> + <label value="column 1,2" /> + </column> + <column> + <label value="column 2,1" /> + <label value="column 2,2" /> + </column> + </columns> + </grid> + </tabpanel> + <tabpanel> + <grid> + <label value="Text inside grid" /> + </grid> + </tabpanel> + <tabpanel> + <columns> + <label value="Text inside columns" /> + </columns> + </tabpanel> + <tabpanel> + <rows> + <label value="Text inside rows" /> + </rows> + </tabpanel> + <tabpanel> + <column> + <label value="Text inside column" /> + </column> + </tabpanel> + <tabpanel> + <row> + <label value="Text inside row" /> + </row> + </tabpanel> + <tabpanel> + <grid> + <label value="Text inside grid one" /> + <rows style="color: blue"> + <label value="Text inside rows #1" /> + <row> + <label value="row 1,1" /> + <label value="row 1,2" /> + </row> + <label value="Text inside rows #2" /> + <row> + <label value="row 2,1" /> + <label value="row 2,2" /> + </row> + <label value="Text inside rows #3" /> + </rows> + <label value="Text inside grid two" style="opacity: 0.7" /> + <columns style="color: fuchsia; opacity: 0.7"> + <label value="Text inside columns #1" /> + <column> + <label value="column 1,1" /> + <label value="column 1,2" /> + </column> + <label value="Text inside columns #2" /> + <column> + <label value="column 2,1" /> + <label value="column 2,2" /> + </column> + <label value="Text inside columns #3" /> + </columns> + <label value="Text inside grid three" style="opacity: 0.4" /> + </grid> + </tabpanel> + </tabpanels> + </tabbox> +</window> diff --git a/layout/xul/grid/crashtests/306911-grid-testcases2.xul b/layout/xul/grid/crashtests/306911-grid-testcases2.xul new file mode 100644 index 000000000..c6b4e3849 --- /dev/null +++ b/layout/xul/grid/crashtests/306911-grid-testcases2.xul @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<!-- vim:sw=4:ts=4:noet: + --> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <tabbox> + <tabs> + <tab label="no group" /> + <tab label="wacky orientations" /> + </tabs> + <tabpanels> + <tabpanel> + <grid> + <row> + <label value="row 1,1" /> + <label value="row 1,2" /> + </row> + <row> + <label value="row 2,1" /> + <label value="row 2,2" /> + </row> + <column> + <label value="column 1,1" /> + <label value="column 1,2" /> + </column> + <column> + <label value="column 2,1" /> + <label value="column 2,2" /> + </column> + </grid> + </tabpanel> + <tabpanel> + <grid> + <rows style="color: green"> + <row> + <label value="rows+row 1" /> + <label value="rows+row 2" /> + </row> + <column> + <label value="rows+column 1" /> + <label value="rows+column 2" /> + </column> + <rows style="color: purple"> + <row> + <label value="rows+rows+row 1" /> + <label value="rows+rows+row 2" /> + </row> + <column> + <label value="rows+rows+column 1" /> + <label value="rows+rows+column 2" /> + </column> + </rows> + <columns style="color: blue"> + <row> + <label value="rows+columns+row 1" /> + <label value="rows+columns+row 2" /> + </row> + <column> + <label value="rows+columns+column 1" /> + <label value="rows+columns+column 2" /> + </column> + </columns> + </rows> + <columns style="opacity: 0.7; color: lime"> + <row> + <label value="columns+row 1" /> + <label value="columns+row 2" /> + </row> + <column> + <label value="columns+column 1" /> + <label value="columns+column 2" /> + </column> + <rows style="color: fuchsia"> + <row> + <label value="columns+rows+row 1" /> + <label value="columns+rows+row 2" /> + </row> + <column> + <label value="columns+rows+column 1" /> + <label value="columns+rows+column 2" /> + </column> + </rows> + <columns style="color: aqua"> + <row> + <label value="columns+columns+row 1" /> + <label value="columns+columns+row 2" /> + </row> + <column> + <label value="columns+columns+column 1" /> + <label value="columns+columns+column 2" /> + </column> + </columns> + </columns> + </grid> + </tabpanel> + </tabpanels> + </tabbox> +</window> diff --git a/layout/xul/grid/crashtests/311710-1.xul b/layout/xul/grid/crashtests/311710-1.xul new file mode 100644 index 000000000..403b267e9 --- /dev/null +++ b/layout/xul/grid/crashtests/311710-1.xul @@ -0,0 +1,22 @@ +<window title="Testcase bug 311710 - Evil xul testcase, using display:-moz-grid-group causes crash [@ nsGridRow::IsCollapsed]" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> +<script type="application/x-javascript"> +function clickit() { + var button = document.getElementById('button'); + var evt = document.createEvent("MouseEvents"); + evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + button.dispatchEvent(evt); +} +window.addEventListener('load', clickit, false); +</script> + + <grid> + <rows> + <row> + <separator/> + </row> + </rows> + </grid> +<button id="button" onclick="document.getElementsByTagName('row')[0].style.display='-moz-grid-group'" label="Mozilla should not crash, when clicking this button"/> +</window> diff --git a/layout/xul/grid/crashtests/312784-1.xul b/layout/xul/grid/crashtests/312784-1.xul new file mode 100644 index 000000000..ee4054d80 --- /dev/null +++ b/layout/xul/grid/crashtests/312784-1.xul @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script type="application/x-JavaScript"> +function crash() { + document.getElementById("test").style.display = "none"; +} + +function clickit() { + var button = document.getElementById('button'); + var evt = document.createEvent("MouseEvents"); + evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + button.dispatchEvent(evt); +} + +window.onload = clickit; + +</script> + + <grid> + <columns> + <column/> + </columns> + <rows id="test"> + <row><button label="placeholder"/></row> + </rows> + </grid> +<button id="button" label="Crash me" onclick="crash()"/> +</window> diff --git a/layout/xul/grid/crashtests/313173-1-inner.xul b/layout/xul/grid/crashtests/313173-1-inner.xul new file mode 100644 index 000000000..284d6c1f1 --- /dev/null +++ b/layout/xul/grid/crashtests/313173-1-inner.xul @@ -0,0 +1,41 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window title="Testcase bug - Crash with evil xul testcase, using -moz-grid/table-caption" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<grid flex="1"> + <columns> + <column flex="1"/> + </columns> + + <rows> + <row> + </row> + </rows> +</grid> + +<html:script> +function doe(){ +document.getElementsByTagName('columns')[0].style.display='table-caption'; +setTimeout(doe2,20); +} +function doe2(){ +document.getElementsByTagName('columns')[0].style.display='-moz-grid'; +} + +function clickit() { + var button = document.getElementById('button'); + var evt = document.createEvent("MouseEvents"); + evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + button.dispatchEvent(evt); +setTimeout('clickit();', 20); +} +window.addEventListener('load', clickit, false); + +</html:script> + <html:button id="button" onclick="doe()" label="click">Clicking this should not crash Mozilla</html:button> +</window> + diff --git a/layout/xul/grid/crashtests/313173-1.html b/layout/xul/grid/crashtests/313173-1.html new file mode 100644 index 000000000..8b45339ab --- /dev/null +++ b/layout/xul/grid/crashtests/313173-1.html @@ -0,0 +1,9 @@ +<html class="reftest-wait"> +<head> +<script> +setTimeout('document.documentElement.className = ""', 500); +</script> +<body> +<iframe src="313173-1-inner.xul"></iframe> +</body> +</html> diff --git a/layout/xul/grid/crashtests/321066-1.xul b/layout/xul/grid/crashtests/321066-1.xul new file mode 100644 index 000000000..789c2582c --- /dev/null +++ b/layout/xul/grid/crashtests/321066-1.xul @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <grid> + <rows> + <column/> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/crashtests/321073-1.xul b/layout/xul/grid/crashtests/321073-1.xul new file mode 100644 index 000000000..b92098b62 --- /dev/null +++ b/layout/xul/grid/crashtests/321073-1.xul @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <listcols> + <grid/> + <listitem/> + </listcols> +</window>
\ No newline at end of file diff --git a/layout/xul/grid/crashtests/382750-1.xul b/layout/xul/grid/crashtests/382750-1.xul new file mode 100644 index 000000000..7a9da73ec --- /dev/null +++ b/layout/xul/grid/crashtests/382750-1.xul @@ -0,0 +1,5 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<grid><rows><listbox/></rows></grid> + +</window> diff --git a/layout/xul/grid/crashtests/400790-1.xul b/layout/xul/grid/crashtests/400790-1.xul new file mode 100644 index 000000000..4de709428 --- /dev/null +++ b/layout/xul/grid/crashtests/400790-1.xul @@ -0,0 +1,20 @@ +<xul xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();"> + +<script> + +function boom() +{ + var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + + var newListbox = document.createElementNS(XUL_NS, "listbox"); + document.getElementById("listbox").appendChild(newListbox); + + var newHbox = document.createElementNS(XUL_NS, "hbox"); + document.getElementById("listitem").appendChild(newHbox); +} + +</script> + +<listbox id="listbox"><listitem id="listitem" /></listbox> + +</xul> diff --git a/layout/xul/grid/crashtests/423802-crash.xul b/layout/xul/grid/crashtests/423802-crash.xul new file mode 100644 index 000000000..0ae4eab8f --- /dev/null +++ b/layout/xul/grid/crashtests/423802-crash.xul @@ -0,0 +1,13 @@ +<?xml version="1.0"?> + +<window xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<grid> + <columns> + <column id="col1" flex="1"/> + <column id="col2" flex="1"/> + <column id="col3" flex="P-2"/> + </columns> +</grid> + +</window> diff --git a/layout/xul/grid/crashtests/crashtests.list b/layout/xul/grid/crashtests/crashtests.list new file mode 100644 index 000000000..afe8e1002 --- /dev/null +++ b/layout/xul/grid/crashtests/crashtests.list @@ -0,0 +1,11 @@ +load 306911-crash.xul +load 306911-grid-testcases.xul +load 306911-grid-testcases2.xul +load 311710-1.xul +load 312784-1.xul +load 313173-1.html +load 321066-1.xul +load 321073-1.xul +load 382750-1.xul +load 400790-1.xul +load 423802-crash.xul diff --git a/layout/xul/grid/examples/borderedcolumns.xul b/layout/xul/grid/examples/borderedcolumns.xul new file mode 100644 index 000000000..15ee06911 --- /dev/null +++ b/layout/xul/grid/examples/borderedcolumns.xul @@ -0,0 +1,43 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="border: 2px inset gray;" id="grid"> + <columns> + <column style="border: 10px inset red;"/> + <column/> + <column style="border: 10px inset red;"/> + </columns> + + <rows style="font-size: 20pt;"> + <row> + <text value="Cell 1 "/> + <text value="Cell 2 "/> + <text value="Cell 3 "/> + </row> + <row> + <text value="Cell 4 "/> + <text value="Cell 5 " style="border: 10px inset red;"/> + <text value="Cell 6 "/> + </row> + <row> + <text value="Cell 7 "/> + <text value="Cell 8 "/> + <text value="Cell 9 "/> + </row> + + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/examples/borderedrowscolumns.xul b/layout/xul/grid/examples/borderedrowscolumns.xul new file mode 100644 index 000000000..94d3d8d99 --- /dev/null +++ b/layout/xul/grid/examples/borderedrowscolumns.xul @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="border: 2px inset gray;" id="grid"> + <columns style="border: 0px solid blue"> + <column/> + <column/> + <column/> + </columns> + + <rows style="font-size: 40pt; border: 15px inset blue"> + <row> + <text value="Cell(1)"/> + <text value="Cell(2)"/> + <text value="Cell(3)"/> + </row> + <rows style="border: 10px inset green"> + <row> + <text value="Cell(1)"/> + <text value="Cell(2)"/> + <text value="Cell(3)"/> + </row> + <row> + <text value="Cell(4)"/> + <text value="Cell(5)"/> + <text value="Cell(6)"/> + </row> + <row> + <text value="Cell(7)"/> + <text value="Cell(8)"/> + <text value="Cell(9)"/> + </row> + + </rows> + <row> + <text value="Cell(7)"/> + <text value="Cell(8)"/> + <text value="Cell(9)"/> + </row> + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/examples/borderedrowscolumns2.xul b/layout/xul/grid/examples/borderedrowscolumns2.xul new file mode 100644 index 000000000..96b6ca9e5 --- /dev/null +++ b/layout/xul/grid/examples/borderedrowscolumns2.xul @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="gridsample.css" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="border: 2px inset gray;" id="grid"> + <columns> + <column style="border: 10px solid red;"/> + <column/> + <column/> + </columns> + + <rows style="font-size: 40pt;"> + <row style="border: 10px solid red;"> + <text value="Cell 1 "/> + <text value="Cell 2 "/> + <text value="Cell 3 "/> + </row> + <row> + <text value="Cell 4 "/> + <text value="Cell 5 "/> + <text value="Cell 6 "/> + </row> + <row> + <text value="Cell 7 "/> + <text value="Cell 8 "/> + <text value="Cell 9 "/> + </row> + + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/examples/borderedrowscolumns3.xul b/layout/xul/grid/examples/borderedrowscolumns3.xul new file mode 100644 index 000000000..30a6fcc1b --- /dev/null +++ b/layout/xul/grid/examples/borderedrowscolumns3.xul @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="border: 2px inset gray;" id="grid"> + <columns> + <column/> + <columns style="border: 10px solid red"> + <column/> + </columns> + <column/> + </columns> + + <rows style="font-size: 24pt"> + <row> + <text value="Cell(1)"/> + <text value="Cell(2)"/> + <text value="Cell(3)"/> + </row> + <rows style="border: 10px solid green"> + <row> + <text value="Cell(1)"/> + <text value="Cell(2)"/> + <text value="Cell(3)"/> + </row> + <row> + <text value="Cell(4)"/> + <text value="Cell(5)"/> + <text value="Cell(6)"/> + </row> + <row> + <text value="Cell(7)"/> + <text value="Cell(8)"/> + <text value="Cell(9)"/> + </row> + + </rows> + <row> + <text value="Cell(7)"/> + <text value="Cell(8)"/> + <text value="Cell(9)"/> + </row> + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/examples/bordermargincolumns1.xul b/layout/xul/grid/examples/bordermargincolumns1.xul new file mode 100644 index 000000000..009f932a8 --- /dev/null +++ b/layout/xul/grid/examples/bordermargincolumns1.xul @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="gridsample.css" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="border: 2px inset gray;" id="grid"> + <columns> + <column style="border: 10px inset red; margin: 10px; "/> + <column/> + <column/> + </columns> + + <rows style="font-size: 40pt;"> + <row style="border: 5px solid green"> + <text value="Cell 1"/> + <text value="Cell 2"/> + <text value="Cell 3"/> + </row> + <row> + <text value="Cell 4"/> + <text value="Cell 5"/> + <text value="Cell 6"/> + </row> + <row> + <text value="Cell 7"/> + <text value="Cell 8"/> + <text value="Cell 9"/> + </row> + + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/examples/collapsetest.xul b/layout/xul/grid/examples/collapsetest.xul new file mode 100644 index 000000000..5e1a042f6 --- /dev/null +++ b/layout/xul/grid/examples/collapsetest.xul @@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" style="border: 2px solid green" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script> + + function collapseTag(id) + { + var row = window.document.getElementById(id); + row.setAttribute("collapsed","true"); + } + + function uncollapseTag(id) + { + var row = window.document.getElementById(id); + row.setAttribute("collapsed","false"); + } + + +</script> + + <hbox> + <grid style="border: 2px solid red;" id="grid"> + <columns id="columns1"> + <column id="column1"/> + <column id="column2"/> + <column id="column3"/> + </columns> + + <rows id="rows1" style="font-size: 24pt"> + <row id="row1"> + <text value="cell1"/> + <text value="cell2"/> + <text value="cell3"/> + </row> + <row id="row2"> + <text value="cell4"/> + <text value="cell5"/> + <text value="cell6"/> + </row> + <row id="row3"> + <text value="cell7"/> + <text value="cell8"/> + <text value="cell9"/> + </row> + </rows> + </grid> + </hbox> + <hbox> + <button label="collapse row 2" oncommand="collapseTag('row2');"/> + <button label="uncollapse row 2" oncommand="uncollapseTag('row2');"/> + <button label="collapse column 2" oncommand="collapseTag('column2');"/> + <button label="uncollapse column 2" oncommand="uncollapseTag('column2');"/> + + </hbox> + +</window> diff --git a/layout/xul/grid/examples/divcolumngrid.xul b/layout/xul/grid/examples/divcolumngrid.xul new file mode 100644 index 000000000..2268c302c --- /dev/null +++ b/layout/xul/grid/examples/divcolumngrid.xul @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" style="border: 2px solid green" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <hbox> + <grid style="border: 2px solid red;"> + <columns> + <column/> + <description style="border: 10px inset gray"> + hello + </description> + <column/> + </columns> + + <rows> + <row> + <text style="font-size: 40px" value="foo1"/> + <text style="font-size: 40px" value="foo2"/> + </row> + </rows> + </grid> + <spacer flex="1" style="background-color: white"/> + </hbox> +</window> diff --git a/layout/xul/grid/examples/divrowgrid.xul b/layout/xul/grid/examples/divrowgrid.xul new file mode 100644 index 000000000..657553aab --- /dev/null +++ b/layout/xul/grid/examples/divrowgrid.xul @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" style="border: 2px solid green" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="width: 100px; border: 2px solid red;"> + <rows> + <row style="font-size: 40px"> + <text value="foo1"/> + <text value="foo2"/> + </row> + <description> + this is some html in the row this should wrap if it is big enough. + </description> + <row style="font-size: 40px"> + <text value="foo3"/> + <text value="foo4"/> + </row> + + </rows> + </grid> + <spacer flex="1" style="background-color: white"/> + </hbox> + + +</window> diff --git a/layout/xul/grid/examples/dynamicgrid.xul b/layout/xul/grid/examples/dynamicgrid.xul new file mode 100644 index 000000000..d718df5f9 --- /dev/null +++ b/layout/xul/grid/examples/dynamicgrid.xul @@ -0,0 +1,370 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="gridsample.css" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start()"> + +<script> + + var selected; + var count = 0; + + function isCell(box) + { + if (box.localName == "row" || + box.localName == "column" || + box.localName == "rows" || + box.localName == "columns" || + box.localName == "grid") + return false; + + return true; + } + + function start() + { + selectIt(window.document.getElementById("rows")); + } + + function selectIt(box) + { + if (!box) + return; + + var a = box.getAttribute("selected"); + if (a != "true") { + box.setAttribute("selected","true"); + if (selected) + selected.setAttribute("selected","false"); + + selected = box; + } + } + + function addCellSelectionHandle(box) + { + box.setAttribute("oncommand", "selectIt(this);"); + } + + function addRowColumnSelectionHandle(box) + { + box.setAttribute("onclick", "selectIt(this);"); + } + + function createButton(str) + { + var b = document.createElement("button"); + b.setAttribute("label", str+count); + count++; + addCellSelectionHandle(b); + return b; + } + + function createRow() + { + var b = document.createElement("row"); + b.setAttribute("dynamic","true"); + + addRowColumnSelectionHandle(b); + return b; + } + + function createColumn() + { + var b = document.createElement("column"); + b.setAttribute("dynamic","true"); + addRowColumnSelectionHandle(b); + return b; + } + + function createText(str) + { + var text = document.createElement("text"); + text.setAttribute("value", str+count); + count++; + text.setAttribute("style", "font-size: 40pt"); + addCellSelectionHandle(text); + return text; + } + + function appendElement(element, prepend) + { + if (!selected) + return; + + setUserAttribute(element); + + if (selected.localName == "rows") + appendRow(false); + else if (selected.localName == "columns") + appendColumn(false); + + if (selected.localName == "row" || selected.localName == "column" ) { // is row or column + selected.appendChild(element); + } else { + var parent = selected.parentNode; + if (prepend) + parent.insertBefore(element, selected); + else { + var next = selected.nextSibling; + if (next) + parent.insertBefore(element,next); + else + parent.appendChild(element); + } + } + + selectIt(element); + } + + function getRows(box) + { + return window.document.getElementById("rows"); + } + + function getColumns(box) + { + return window.document.getElementById("columns"); + } + + function setUserAttribute(element) + { + var attributeBox = document.getElementById("attributebox"); + var valueBox = document.getElementById("valuebox"); + var attribute = attributeBox.value; + var value = valueBox.value; + if (attribute != "") + element.setAttribute(attribute,value); + } + + function appendRowColumn(rowColumn, prepend) + { + if (!selected) + return; + + setUserAttribute(rowColumn); + + var row = rowColumn; + + // first see what we are adding. + + if (isCell(selected)) { // if cell then select row/column + selectIt(selected.parentNode); + } + + if (selected.localName == "row" || selected.localName == "rows") + if (row.localName == "column") { + selectIt(getColumns(selected)); + dump("Selecting the column") + dump("Selected="+selected.localName); + } + + if (selected.localName == "column" || selected.localName == "columns") + if (row.localName == "row") + selectIt(getRows(selected)); + + if (selected.localName == "rows" || selected.localName == "columns" ) + { // if rows its easy + selected.appendChild(row); + } else { + var parent = selected.parentNode; + if (prepend) + parent.insertBefore(row, selected); + else { + var next = selected.nextSibling; + if (next) + parent.insertBefore(row,next); + else + parent.appendChild(row); + } + } + + selectIt(row); + } + + function appendRow(prepend) + { + var row = createRow(); + appendRowColumn(row,prepend); + } + + + function appendColumn(prepend) + { + var column = createColumn(); + appendRowColumn(column,prepend); + } + + + function selectRows() + { + var rows = getRows(); + if (rows.firstChild) + selectIt(rows.firstChild); + else + selectIt(rows); + } + + + function selectColumns() + { + var columns = getColumns(); + if (columns.firstChild) + selectIt(columns.firstChild); + else + selectIt(columns); + } + + function nextElement() + { + if (!selected) + return; + + selectIt(selected.nextSibling); + } + + function previousElement() + { + if (!selected) + return; + + selectIt(selected.previousSibling); + } + + function selectRow() + { + if (!selected) + return; + + if (selected.localName == "row") + return; + + if (isCell(selected)) { + if (selected.parentNode.localName == "row") + selectIt(selected.parentNode); + } + } + + function selectColumn() + { + if (!selected) + return; + + if (selected.localName == "column") + return; + + if (isCell(selected)) { + if (selected.parentNode.localName == "column") + selectIt(selected.parentNode); + } + } + + function collapseGrid() + { + var grid = document.getElementById("grid"); + var collapsed = grid.getAttribute("collapsed"); + + if (collapsed == "") + grid.setAttribute("collapsed","true"); + else + grid.setAttribute("collapsed",""); + + } + + function collapseElement() + { + if (selected) { + var collapsed = selected.getAttribute("collapsed"); + + if (collapsed == "") + selected.setAttribute("collapsed","true"); + else + selected.setAttribute("collapsed",""); + } + } + +</script> + + <hbox flex="1" style="border: 2px inset gray; overflow: auto"> + <vbox flex="1"> + <hbox> + <grid id="grid" style="border: 2px solid red;"> + <columns id="columns"> + </columns> + + <rows start="true" id="rows"> + </rows> + </grid> + <spacer flex="1"/> + </hbox> + <spacer flex="1"/> + </vbox> + </hbox> + + <grid style="background-color: blue"> + <columns> + <column flex="1"/> + <column flex="1"/> + <column flex="1"/> + <column flex="1"/> + </columns> + <rows> + + <row> + <button label="append row" oncommand="appendRow(false);"/> + <button label="prepend row" oncommand="appendRow(true);"/> + + <button label="append column" oncommand="appendColumn(false);"/> + <button label="prepend column" oncommand="appendColumn(true);"/> + </row> + + <row> + + <button label="append button" oncommand="appendElement(createButton('button'),false);"/> + <button label="prepend button" oncommand="appendElement(createButton('button'),true);"/> + + <button label="append text" oncommand="appendElement(createText('text'),false);"/> + <button label="prepend text" oncommand="appendElement(createText('text'),true);"/> + + </row> + + <row> + + <button label="select rows" oncommand="selectRows()"/> + <button label="select columns" oncommand="selectColumns()"/> + + <button label="next" oncommand="nextElement()"/> + <button label="previous" oncommand="previousElement()"/> + + </row> + + <hbox align="center"> + <button label="collapse/uncollapse grid" flex="1" oncommand="collapseGrid()"/> + <button label="collapse/uncollapse element" flex="1" oncommand="collapseElement()"/> + </hbox> + + + + <hbox> + + <text value="attribute"/> + <textbox id="attributebox" value="" flex="1"/> + <text value="value"/> + <textbox id="valuebox" value="" flex="2"/> + </hbox> + + + </rows> + </grid> + +</window> diff --git a/layout/xul/grid/examples/flexgroupgrid.xul b/layout/xul/grid/examples/flexgroupgrid.xul new file mode 100644 index 000000000..f4cd6622c --- /dev/null +++ b/layout/xul/grid/examples/flexgroupgrid.xul @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="gridsample.css" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="border: 2px inset gray;" flex="1"> + <columns> + <columns style="border: 10px solid red" flex="1"> + <column flex="2"/> + <column flex="1"/> + </columns> + <column flex="1"/> + </columns> + + <rows style="font-size: 20pt"> + <rows> + <row> + <text class="yellow" value="CellA"/> + <text class="yellow" value="CellAB"/> + <text class="yellow" value="CellABC"/> + </row> + <row> + <text class="yellow" value="CellA"/> + <text class="yellow" value="CellAB"/> + <text class="yellow" value="CellABC"/> + </row> + </rows> + <row> + <text class="yellow" value="CellA"/> + <text class="yellow" value="CellAB"/> + <text class="yellow" value="CellABC"/> + </row> + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/examples/javascriptappend.xul b/layout/xul/grid/examples/javascriptappend.xul new file mode 100644 index 000000000..f2a415cae --- /dev/null +++ b/layout/xul/grid/examples/javascriptappend.xul @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" style="border: 2px solid green" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script> + function start() + { + var row = document.getElementById("row"); + var text = document.createElement("text"); + text.setAttribute("value", "foo"); + row.appendChild(text); + } + + </script> + + <hbox> + <grid style="border: 2px solid red;" id="grid"> + <columns> + </columns> + + <rows> + <row id="row"> + <button label="value"/> + </row> + </rows> + </grid> + <spacer flex="1" style="background-color: white"/> + </hbox> + + <button label="insert" oncommand="start()"/> + +</window> diff --git a/layout/xul/grid/examples/jumpygrid.xul b/layout/xul/grid/examples/jumpygrid.xul new file mode 100644 index 000000000..8bbeb5806 --- /dev/null +++ b/layout/xul/grid/examples/jumpygrid.xul @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="gridsample.css" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" style="border: 2px solid green" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script> + function flip(child) + { + var jump = child.getAttribute("jumpy"); + if (jump != "true") + child.setAttribute("jumpy","true"); + else + child.setAttribute("jumpy","false"); + } + + </script> + <hbox> + <grid style="border: 2px solid yellow;"> + <columns> + </columns> + + <rows> + <row> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + </row> + <row> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + </row> + <row> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + </row> + <row> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + </row> + <row> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + </row> + <row> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + <button label="button" class="jumpy"/> + </row> + + </rows> + </grid> + <spacer style="border: 2px solid white;" flex="1"/> + </hbox> + <spacer style="border: 2px solid white;" flex="1"/> + +</window> diff --git a/layout/xul/grid/examples/nestedrows.xul b/layout/xul/grid/examples/nestedrows.xul new file mode 100644 index 000000000..700f785b3 --- /dev/null +++ b/layout/xul/grid/examples/nestedrows.xul @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox flex="1"> + <grid style="border: 2px solid red" flex="1"> + + <columns> + <column flex="1"/> + <column flex="1"/> + <column flex="1"/> + </columns> + + <rows> + <row> + <text value="out1"/> + <text value="out2"/> + <text value="out3"/> + </row> + + <rows flex="1" style="border: 10px inset yellow; font-size: 20pt"> + <row> + <text value="in1"/> + <text value="in2"/> + <text value="in3"/> + </row> + <row> + <text value="in4"/> + <text value="in5"/> + <text value="in5"/> + </row> + </rows> + + </rows> + + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/examples/rowspan.xul b/layout/xul/grid/examples/rowspan.xul new file mode 100644 index 000000000..266a32229 --- /dev/null +++ b/layout/xul/grid/examples/rowspan.xul @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" style="border: 2px solid green" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="border: 2px solid red;"> + <columns> + <column/> + <column/> + </columns> + + <rows> + <row style="font-size: 40px"> + <text value="foo1"/> + <text value="foo2"/> + </row> + <box width="50" style="border:5px inset grey"> + <text value="hello there. This spans"/> + </box> + <row style="font-size: 40px" > + <text value="foo1"/> + <text value="foo2"/> + </row> + + </rows> + </grid> + <spacer flex="1" style="background-color: white"/> + </hbox> + + +</window> diff --git a/layout/xul/grid/examples/scrollingcolumns.xul b/layout/xul/grid/examples/scrollingcolumns.xul new file mode 100644 index 000000000..f29909624 --- /dev/null +++ b/layout/xul/grid/examples/scrollingcolumns.xul @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox flex="1"> + <grid style="border: 2px solid red" flex="1"> + + <rows> + <row flex="1"/> + <row flex="1"/> + <row flex="1"/> + </rows> + <columns> + + <column> + <button label="left"/> + <button label="left"/> + <button label="left"/> + </column> + + <columns flex="1" style="min-width: 1px; overflow: auto; background-color: green"> + <column> + <button label="cell1"/> + <button label="cell1"/> + <button label="cell1"/> + </column> + <column> + <button label="cell2"/> + <button label="cell2"/> + <button label="cell2"/> + </column> + <column> + <button label="cell3"/> + <button label="cell3"/> + <button label="cell3"/> + </column> + <column> + <button label="cell4"/> + <button label="cell4"/> + <button label="cell4"/> + </column> + <column> + <button label="cell5"/> + <button label="cell5"/> + <button label="cell5"/> + </column> + <column> + <button label="cell6"/> + <button label="cell6"/> + <button label="cell6"/> + </column> + <column> + <button label="cell7"/> + <button label="cell7"/> + <button label="cell7"/> + </column> + </columns> + <column> + <button label="right"/> + <button label="right"/> + <button label="right"/> + </column> + + </columns> + + </grid> + <spacer width="100"/> + </hbox> + <spacer height="100"/> +</window> diff --git a/layout/xul/grid/examples/scrollingrows.xul b/layout/xul/grid/examples/scrollingrows.xul new file mode 100644 index 000000000..fd5077cde --- /dev/null +++ b/layout/xul/grid/examples/scrollingrows.xul @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox flex="1"> + <grid style="border: 2px solid red" flex="1"> + + <columns> + <column flex="1"/> + <column flex="1"/> + <column flex="1"/> + </columns> + + <rows> + <row> + <button label="left"/> + <button label="left"/> + <button label="left"/> + </row> + + <rows flex="1" style="border: 10px inset gray; overflow: auto; background-color: green"> + <row> + <button label="cell1"/> + <button label="cell1"/> + <button label="cell1"/> + </row> + <row> + <button label="cell2"/> + <button label="cell2"/> + <button label="cell2"/> + </row> + <row> + <button label="cell3"/> + <button label="cell3"/> + <button label="cell3"/> + </row> + <row> + <button label="cell4"/> + <button label="cell4"/> + <button label="cell4"/> + </row> + <row> + <button label="cell5"/> + <button label="cell5"/> + <button label="cell5"/> + </row> + <row> + <button label="cell6"/> + <button label="cell6"/> + <button label="cell6"/> + </row> + <row> + <button label="cell7"/> + <button label="cell7"/> + <button label="cell7"/> + </row> + </rows> + <row> + <button label="right"/> + <button label="right"/> + <button label="right"/> + </row> + + </rows> + + </grid> + <spacer width="100"/> + </hbox> + <spacer height="100"/> +</window> diff --git a/layout/xul/grid/examples/splitter.xul b/layout/xul/grid/examples/splitter.xul new file mode 100644 index 000000000..67946d487 --- /dev/null +++ b/layout/xul/grid/examples/splitter.xul @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE window> + + +<window orient="vertical" style="border: 2px solid green" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <grid style="border: 2px solid red;"> + <columns> + <column style="min-width: 1px"/> + <splitter/> + <column style="min-width: 1px"/> + </columns> + + <rows> + <row> + <text style="font-size: 40px" value="foo1"/> + <text style="font-size: 40px" value="foo2"/> + </row> + <label value="this is some text. This is longer"/> + <row> + <text style="font-size: 40px" value="foo1"/> + <text style="font-size: 40px" value="foo2"/> + </row> + + </rows> + </grid> + <spacer flex="1" style="background-color: white"/> + </hbox> + + +</window> diff --git a/layout/xul/grid/moz.build b/layout/xul/grid/moz.build new file mode 100644 index 000000000..d2cfd5d2e --- /dev/null +++ b/layout/xul/grid/moz.build @@ -0,0 +1,34 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MODULE = 'layout' + +EXPORTS += [ + 'nsGrid.h', + 'nsGridCell.h', + 'nsGridLayout2.h', + 'nsGridRow.h', + 'nsGridRowGroupLayout.h', + 'nsGridRowLayout.h', + 'nsGridRowLeafFrame.h', + 'nsGridRowLeafLayout.h', + 'nsIGridPart.h', +] + +CPP_SOURCES += [ + 'nsGrid.cpp', + 'nsGridCell.cpp', + 'nsGridLayout2.cpp', + 'nsGridRow.cpp', + 'nsGridRowGroupFrame.cpp', + 'nsGridRowGroupLayout.cpp', + 'nsGridRowLayout.cpp', + 'nsGridRowLeafFrame.cpp', + 'nsGridRowLeafLayout.cpp', +] + +LIBRARY_NAME = 'gkxulgrid_s' + diff --git a/layout/xul/grid/nsGrid.cpp b/layout/xul/grid/nsGrid.cpp new file mode 100644 index 000000000..0cf96a826 --- /dev/null +++ b/layout/xul/grid/nsGrid.cpp @@ -0,0 +1,1299 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsGrid.h" +#include "nsGridRowGroupLayout.h" +#include "nsBox.h" +#include "nsIScrollableFrame.h" +#include "nsSprocketLayout.h" +#include "nsGridLayout2.h" +#include "nsGridRow.h" +#include "nsGridCell.h" +#include "nsHTMLReflowState.h" + +/* +The grid control expands the idea of boxes from 1 dimension to 2 dimensions. +It works by allowing the XUL to define a collection of rows and columns and then +stacking them on top of each other. Here is and example. + +Example 1: + +<grid> + <columns> + <column/> + <column/> + </columns> + + <rows> + <row/> + <row/> + </rows> +</grid> + +example 2: + +<grid> + <columns> + <column flex="1"/> + <column flex="1"/> + </columns> + + <rows> + <row> + <text value="hello"/> + <text value="there"/> + </row> + </rows> +</grid> + +example 3: + +<grid> + +<rows> + <row> + <text value="hello"/> + <text value="there"/> + </row> + </rows> + + <columns> + <column> + <text value="Hey I'm in the column and I'm on top!"/> + </column> + <column/> + </columns> + +</grid> + +Usually the columns are first and the rows are second, so the rows will be drawn on top of the columns. +You can reverse this by defining the rows first. +Other tags are then placed in the <row> or <column> tags causing the grid to accommodate everyone. +It does this by creating 3 things: A cellmap, a row list, and a column list. The cellmap is a 2 +dimensional array of nsGridCells. Each cell contains 2 boxes. One cell from the column list +and one from the row list. When a cell is asked for its size it returns that smallest size it can +be to accommodate the 2 cells. Row lists and Column lists use the same data structure: nsGridRow. +Essentially a row and column are the same except a row goes alone the x axis and a column the y. +To make things easier and save code everything is written in terms of the x dimension. A flag is +passed in called "isHorizontal" that can flip the calculations to the y axis. + +Usually the number of cells in a row match the number of columns, but not always. +It is possible to define 5 columns for a grid but have 10 cells in one of the rows. +In this case 5 extra columns will be added to the column list to handle the situation. +These are called extraColumns/Rows. +*/ + +nsGrid::nsGrid():mBox(nullptr), + mRows(nullptr), + mColumns(nullptr), + mRowsBox(nullptr), + mColumnsBox(nullptr), + mNeedsRebuild(true), + mRowCount(0), + mColumnCount(0), + mExtraRowCount(0), + mExtraColumnCount(0), + mCellMap(nullptr), + mMarkingDirty(false) +{ + MOZ_COUNT_CTOR(nsGrid); +} + +nsGrid::~nsGrid() +{ + FreeMap(); + MOZ_COUNT_DTOR(nsGrid); +} + +/* + * This is called whenever something major happens in the grid. And example + * might be when many cells or row are added. It sets a flag signaling that + * all the grids caches information should be recalculated. + */ +void +nsGrid::NeedsRebuild(nsBoxLayoutState& aState) +{ + if (mNeedsRebuild) + return; + + // iterate through columns and rows and dirty them + mNeedsRebuild = true; + + // find the new row and column box. They could have + // been changed. + mRowsBox = nullptr; + mColumnsBox = nullptr; + FindRowsAndColumns(&mRowsBox, &mColumnsBox); + + // tell all the rows and columns they are dirty + DirtyRows(mRowsBox, aState); + DirtyRows(mColumnsBox, aState); +} + + + +/** + * If we are marked for rebuild. Then build everything + */ +void +nsGrid::RebuildIfNeeded() +{ + if (!mNeedsRebuild) + return; + + mNeedsRebuild = false; + + // find the row and columns frames + FindRowsAndColumns(&mRowsBox, &mColumnsBox); + + // count the rows and columns + int32_t computedRowCount = 0; + int32_t computedColumnCount = 0; + int32_t rowCount = 0; + int32_t columnCount = 0; + + CountRowsColumns(mRowsBox, rowCount, computedColumnCount); + CountRowsColumns(mColumnsBox, columnCount, computedRowCount); + + // computedRowCount are the actual number of rows as determined by the + // columns children. + // computedColumnCount are the number of columns as determined by the number + // of rows children. + // We can use this information to see how many extra columns or rows we need. + // This can happen if there are are more children in a row that number of columns + // defined. Example: + // + // <columns> + // <column/> + // </columns> + // + // <rows> + // <row> + // <button/><button/> + // </row> + // </rows> + // + // computedColumnCount = 2 // for the 2 buttons in the row tag + // computedRowCount = 0 // there is nothing in the column tag + // mColumnCount = 1 // one column defined + // mRowCount = 1 // one row defined + // + // So in this case we need to make 1 extra column. + // + + // Make sure to update mExtraColumnCount no matter what, since it might + // happen that we now have as many columns as are defined, and we wouldn't + // want to have a positive mExtraColumnCount hanging about in that case! + mExtraColumnCount = computedColumnCount - columnCount; + if (computedColumnCount > columnCount) { + columnCount = computedColumnCount; + } + + // Same for rows. + mExtraRowCount = computedRowCount - rowCount; + if (computedRowCount > rowCount) { + rowCount = computedRowCount; + } + + // build and poplulate row and columns arrays + BuildRows(mRowsBox, rowCount, &mRows, true); + BuildRows(mColumnsBox, columnCount, &mColumns, false); + + // build and populate the cell map + mCellMap = BuildCellMap(rowCount, columnCount); + + mRowCount = rowCount; + mColumnCount = columnCount; + + // populate the cell map from column and row children + PopulateCellMap(mRows, mColumns, mRowCount, mColumnCount, true); + PopulateCellMap(mColumns, mRows, mColumnCount, mRowCount, false); +} + +void +nsGrid::FreeMap() +{ + if (mRows) + delete[] mRows; + + if (mColumns) + delete[] mColumns; + + if (mCellMap) + delete[] mCellMap; + + mRows = nullptr; + mColumns = nullptr; + mCellMap = nullptr; + mColumnCount = 0; + mRowCount = 0; + mExtraColumnCount = 0; + mExtraRowCount = 0; + mRowsBox = nullptr; + mColumnsBox = nullptr; +} + +/** + * finds the first <rows> and <columns> tags in the <grid> tag + */ +void +nsGrid::FindRowsAndColumns(nsIFrame** aRows, nsIFrame** aColumns) +{ + *aRows = nullptr; + *aColumns = nullptr; + + // find the boxes that contain our rows and columns + nsIFrame* child = nullptr; + // if we have <grid></grid> then mBox will be null (bug 125689) + if (mBox) + child = mBox->GetChildBox(); + + while(child) + { + nsIFrame* oldBox = child; + nsIScrollableFrame *scrollFrame = do_QueryFrame(child); + if (scrollFrame) { + nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); + NS_ASSERTION(scrolledFrame,"Error no scroll frame!!"); + child = do_QueryFrame(scrolledFrame); + } + + nsCOMPtr<nsIGridPart> monument = GetPartFromBox(child); + if (monument) + { + nsGridRowGroupLayout* rowGroup = monument->CastToRowGroupLayout(); + if (rowGroup) { + bool isHorizontal = !nsSprocketLayout::IsHorizontal(child); + if (isHorizontal) + *aRows = child; + else + *aColumns = child; + + if (*aRows && *aColumns) + return; + } + } + + if (scrollFrame) { + child = oldBox; + } + + child = child->GetNextBox(); + } +} + +/** + * Count the number of rows and columns in the given box. aRowCount well become the actual number + * rows defined in the xul. aComputedColumnCount will become the number of columns by counting the number + * of cells in each row. + */ +void +nsGrid::CountRowsColumns(nsIFrame* aRowBox, int32_t& aRowCount, int32_t& aComputedColumnCount) +{ + aRowCount = 0; + aComputedColumnCount = 0; + // get the rowboxes layout manager. Then ask it to do the work for us + if (aRowBox) { + nsCOMPtr<nsIGridPart> monument = GetPartFromBox(aRowBox); + if (monument) + monument->CountRowsColumns(aRowBox, aRowCount, aComputedColumnCount); + } +} + + +/** + * Given the number of rows create nsGridRow objects for them and full them out. + */ +void +nsGrid::BuildRows(nsIFrame* aBox, int32_t aRowCount, nsGridRow** aRows, bool aIsHorizontal) +{ + // if no rows then return null + if (aRowCount == 0) { + + // make sure we free up the memory. + if (*aRows) + delete[] (*aRows); + + *aRows = nullptr; + return; + } + + // create the array + nsGridRow* row; + + // only create new rows if we have to. Reuse old rows. + if (aIsHorizontal) + { + if (aRowCount > mRowCount) { + delete[] mRows; + row = new nsGridRow[aRowCount]; + } else { + for (int32_t i=0; i < mRowCount; i++) + mRows[i].Init(nullptr, false); + + row = mRows; + } + } else { + if (aRowCount > mColumnCount) { + delete[] mColumns; + row = new nsGridRow[aRowCount]; + } else { + for (int32_t i=0; i < mColumnCount; i++) + mColumns[i].Init(nullptr, false); + + row = mColumns; + } + } + + // populate it if we can. If not it will contain only dynamic columns + if (aBox) + { + nsCOMPtr<nsIGridPart> monument = GetPartFromBox(aBox); + if (monument) { + monument->BuildRows(aBox, row); + } + } + + *aRows = row; +} + + +/** + * Given the number of rows and columns. Build a cellmap + */ +nsGridCell* +nsGrid::BuildCellMap(int32_t aRows, int32_t aColumns) +{ + int32_t size = aRows*aColumns; + int32_t oldsize = mRowCount*mColumnCount; + if (size == 0) { + delete[] mCellMap; + } + else { + if (size > oldsize) { + delete[] mCellMap; + return new nsGridCell[size]; + } else { + // clear out cellmap + for (int32_t i=0; i < oldsize; i++) + { + mCellMap[i].SetBoxInRow(nullptr); + mCellMap[i].SetBoxInColumn(nullptr); + } + return mCellMap; + } + } + return nullptr; +} + +/** + * Run through all the cells in the rows and columns and populate then with 2 cells. One from the row and one + * from the column + */ +void +nsGrid::PopulateCellMap(nsGridRow* aRows, nsGridRow* aColumns, int32_t aRowCount, int32_t aColumnCount, bool aIsHorizontal) +{ + if (!aRows) + return; + + // look through the columns + int32_t j = 0; + + for(int32_t i=0; i < aRowCount; i++) + { + nsIFrame* child = nullptr; + nsGridRow* row = &aRows[i]; + + // skip bogus rows. They have no cells + if (row->mIsBogus) + continue; + + child = row->mBox; + if (child) { + child = child->GetChildBox(); + + j = 0; + + while(child && j < aColumnCount) + { + // skip bogus column. They have no cells + nsGridRow* column = &aColumns[j]; + if (column->mIsBogus) + { + j++; + continue; + } + + if (aIsHorizontal) + GetCellAt(j,i)->SetBoxInRow(child); + else + GetCellAt(i,j)->SetBoxInColumn(child); + + child = child->GetNextBox(); + + j++; + } + } + } +} + +/** + * Run through the rows in the given box and mark them dirty so they + * will get recalculated and get a layout. + */ +void +nsGrid::DirtyRows(nsIFrame* aRowBox, nsBoxLayoutState& aState) +{ + // make sure we prevent others from dirtying things. + mMarkingDirty = true; + + // if the box is a grid part have it recursively hand it. + if (aRowBox) { + nsCOMPtr<nsIGridPart> part = GetPartFromBox(aRowBox); + if (part) + part->DirtyRows(aRowBox, aState); + } + + mMarkingDirty = false; +} + +nsGridRow* +nsGrid::GetColumnAt(int32_t aIndex, bool aIsHorizontal) +{ + return GetRowAt(aIndex, !aIsHorizontal); +} + +nsGridRow* +nsGrid::GetRowAt(int32_t aIndex, bool aIsHorizontal) +{ + RebuildIfNeeded(); + + if (aIsHorizontal) { + NS_ASSERTION(aIndex < mRowCount && aIndex >= 0, "Index out of range"); + return &mRows[aIndex]; + } else { + NS_ASSERTION(aIndex < mColumnCount && aIndex >= 0, "Index out of range"); + return &mColumns[aIndex]; + } +} + +nsGridCell* +nsGrid::GetCellAt(int32_t aX, int32_t aY) +{ + RebuildIfNeeded(); + + NS_ASSERTION(aY < mRowCount && aY >= 0, "Index out of range"); + NS_ASSERTION(aX < mColumnCount && aX >= 0, "Index out of range"); + return &mCellMap[aY*mColumnCount+aX]; +} + +int32_t +nsGrid::GetExtraColumnCount(bool aIsHorizontal) +{ + return GetExtraRowCount(!aIsHorizontal); +} + +int32_t +nsGrid::GetExtraRowCount(bool aIsHorizontal) +{ + RebuildIfNeeded(); + + if (aIsHorizontal) + return mExtraRowCount; + else + return mExtraColumnCount; +} + + +/** + * These methods return the preferred, min, max sizes for a given row index. + * aIsHorizontal if aIsHorizontal is true. If you pass false you will get the inverse. + * As if you called GetPrefColumnSize(aState, index, aPref) + */ +nsSize +nsGrid::GetPrefRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal) +{ + nsSize size(0,0); + if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal))) + return size; + + nscoord height = GetPrefRowHeight(aState, aRowIndex, aIsHorizontal); + SetLargestSize(size, height, aIsHorizontal); + + return size; +} + +nsSize +nsGrid::GetMinRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal) +{ + nsSize size(0,0); + if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal))) + return size; + + nscoord height = GetMinRowHeight(aState, aRowIndex, aIsHorizontal); + SetLargestSize(size, height, aIsHorizontal); + + return size; +} + +nsSize +nsGrid::GetMaxRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal) +{ + nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE); + if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal))) + return size; + + nscoord height = GetMaxRowHeight(aState, aRowIndex, aIsHorizontal); + SetSmallestSize(size, height, aIsHorizontal); + + return size; +} + +// static +nsIGridPart* +nsGrid::GetPartFromBox(nsIFrame* aBox) +{ + if (!aBox) + return nullptr; + + nsBoxLayout* layout = aBox->GetLayoutManager(); + return layout ? layout->AsGridPart() : nullptr; +} + +nsMargin +nsGrid::GetBoxTotalMargin(nsIFrame* aBox, bool aIsHorizontal) +{ + nsMargin margin(0,0,0,0); + // walk the boxes parent chain getting the border/padding/margin of our parent rows + + // first get the layour manager + nsIGridPart* part = GetPartFromBox(aBox); + if (part) + margin = part->GetTotalMargin(aBox, aIsHorizontal); + + return margin; +} + +/** + * The first and last rows can be affected by <rows> tags with borders or margin + * gets first and last rows and their indexes. + * If it fails because there are no rows then: + * FirstRow is nullptr + * LastRow is nullptr + * aFirstIndex = -1 + * aLastIndex = -1 + */ +void +nsGrid::GetFirstAndLastRow(nsBoxLayoutState& aState, + int32_t& aFirstIndex, + int32_t& aLastIndex, + nsGridRow*& aFirstRow, + nsGridRow*& aLastRow, + bool aIsHorizontal) +{ + aFirstRow = nullptr; + aLastRow = nullptr; + aFirstIndex = -1; + aLastIndex = -1; + + int32_t count = GetRowCount(aIsHorizontal); + + if (count == 0) + return; + + + // We could have collapsed columns either before or after our index. + // they should not count. So if we are the 5th row and the first 4 are + // collaped we become the first row. Or if we are the 9th row and + // 10 up to the last row are collapsed we then become the last. + + // see if we are first + int32_t i; + for (i=0; i < count; i++) + { + nsGridRow* row = GetRowAt(i,aIsHorizontal); + if (!row->IsCollapsed()) { + aFirstIndex = i; + aFirstRow = row; + break; + } + } + + // see if we are last + for (i=count-1; i >= 0; i--) + { + nsGridRow* row = GetRowAt(i,aIsHorizontal); + if (!row->IsCollapsed()) { + aLastIndex = i; + aLastRow = row; + break; + } + + } +} + +/** + * A row can have a top and bottom offset. Usually this is just the top and bottom border/padding. + * However if the row is the first or last it could be affected by the fact a column or columns could + * have a top or bottom margin. + */ +void +nsGrid::GetRowOffsets(nsBoxLayoutState& aState, int32_t aIndex, nscoord& aTop, nscoord& aBottom, bool aIsHorizontal) +{ + + RebuildIfNeeded(); + + nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); + + if (row->IsOffsetSet()) + { + aTop = row->mTop; + aBottom = row->mBottom; + return; + } + + // first get the rows top and bottom border and padding + nsIFrame* box = row->GetBox(); + + // add up all the padding + nsMargin margin(0,0,0,0); + nsMargin border(0,0,0,0); + nsMargin padding(0,0,0,0); + nsMargin totalBorderPadding(0,0,0,0); + nsMargin totalMargin(0,0,0,0); + + // if there is a box and it's not bogus take its + // borders padding into account + if (box && !row->mIsBogus) + { + if (!box->IsCollapsed()) + { + // get real border and padding. GetBorderAndPadding + // is redefined on nsGridRowLeafFrame. If we called it here + // we would be in finite recurson. + box->GetBorder(border); + box->GetPadding(padding); + + totalBorderPadding += border; + totalBorderPadding += padding; + } + + // if we are the first or last row + // take into account <rows> tags around us + // that could have borders or margins. + // fortunately they only affect the first + // and last row inside the <rows> tag + + totalMargin = GetBoxTotalMargin(box, aIsHorizontal); + } + + if (aIsHorizontal) { + row->mTop = totalBorderPadding.top; + row->mBottom = totalBorderPadding.bottom; + row->mTopMargin = totalMargin.top; + row->mBottomMargin = totalMargin.bottom; + } else { + row->mTop = totalBorderPadding.left; + row->mBottom = totalBorderPadding.right; + row->mTopMargin = totalMargin.left; + row->mBottomMargin = totalMargin.right; + } + + // if we are the first or last row take into account the top and bottom borders + // of each columns. + + // If we are the first row then get the largest top border/padding in + // our columns. If that's larger than the rows top border/padding use it. + + // If we are the last row then get the largest bottom border/padding in + // our columns. If that's larger than the rows bottom border/padding use it. + int32_t firstIndex = 0; + int32_t lastIndex = 0; + nsGridRow* firstRow = nullptr; + nsGridRow* lastRow = nullptr; + GetFirstAndLastRow(aState, firstIndex, lastIndex, firstRow, lastRow, aIsHorizontal); + + if (aIndex == firstIndex || aIndex == lastIndex) { + nscoord maxTop = 0; + nscoord maxBottom = 0; + + // run through the columns. Look at each column + // pick the largest top border or bottom border + int32_t count = GetColumnCount(aIsHorizontal); + + for (int32_t i=0; i < count; i++) + { + nsMargin totalChildBorderPadding(0,0,0,0); + + nsGridRow* column = GetColumnAt(i,aIsHorizontal); + nsIFrame* box = column->GetBox(); + + if (box) + { + // ignore collapsed children + if (!box->IsCollapsed()) + { + // include the margin of the columns. To the row + // at this point border/padding and margins all added + // up to more needed space. + margin = GetBoxTotalMargin(box, !aIsHorizontal); + // get real border and padding. GetBorderAndPadding + // is redefined on nsGridRowLeafFrame. If we called it here + // we would be in finite recurson. + box->GetBorder(border); + box->GetPadding(padding); + totalChildBorderPadding += border; + totalChildBorderPadding += padding; + totalChildBorderPadding += margin; + } + + nscoord top; + nscoord bottom; + + // pick the largest top margin + if (aIndex == firstIndex) { + if (aIsHorizontal) { + top = totalChildBorderPadding.top; + } else { + top = totalChildBorderPadding.left; + } + if (top > maxTop) + maxTop = top; + } + + // pick the largest bottom margin + if (aIndex == lastIndex) { + if (aIsHorizontal) { + bottom = totalChildBorderPadding.bottom; + } else { + bottom = totalChildBorderPadding.right; + } + if (bottom > maxBottom) + maxBottom = bottom; + } + + } + + // If the biggest top border/padding the columns is larger than this rows top border/padding + // the use it. + if (aIndex == firstIndex) { + if (maxTop > (row->mTop + row->mTopMargin)) + row->mTop = maxTop - row->mTopMargin; + } + + // If the biggest bottom border/padding the columns is larger than this rows bottom border/padding + // the use it. + if (aIndex == lastIndex) { + if (maxBottom > (row->mBottom + row->mBottomMargin)) + row->mBottom = maxBottom - row->mBottomMargin; + } + } + } + + aTop = row->mTop; + aBottom = row->mBottom; +} + +/** + * These methods return the preferred, min, max coord for a given row index if + * aIsHorizontal is true. If you pass false you will get the inverse. + * As if you called GetPrefColumnHeight(aState, index, aPref). + */ +nscoord +nsGrid::GetPrefRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) +{ + RebuildIfNeeded(); + + nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); + + if (row->IsCollapsed()) + return 0; + + if (row->IsPrefSet()) + return row->mPref; + + nsIFrame* box = row->mBox; + + // set in CSS? + if (box) + { + bool widthSet, heightSet; + nsSize cssSize(-1, -1); + nsIFrame::AddCSSPrefSize(box, cssSize, widthSet, heightSet); + + row->mPref = GET_HEIGHT(cssSize, aIsHorizontal); + + // yep do nothing. + if (row->mPref != -1) + return row->mPref; + } + + // get the offsets so they are cached. + nscoord top; + nscoord bottom; + GetRowOffsets(aState, aIndex, top, bottom, aIsHorizontal); + + // is the row bogus? If so then just ask it for its size + // it should not be affected by cells in the grid. + if (row->mIsBogus) + { + nsSize size(0,0); + if (box) + { + size = box->GetPrefSize(aState); + nsBox::AddMargin(box, size); + nsGridLayout2::AddOffset(aState, box, size); + } + + row->mPref = GET_HEIGHT(size, aIsHorizontal); + return row->mPref; + } + + nsSize size(0,0); + + nsGridCell* child; + + int32_t count = GetColumnCount(aIsHorizontal); + + for (int32_t i=0; i < count; i++) + { + if (aIsHorizontal) + child = GetCellAt(i,aIndex); + else + child = GetCellAt(aIndex,i); + + // ignore collapsed children + if (!child->IsCollapsed()) + { + nsSize childSize = child->GetPrefSize(aState); + + nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal); + } + } + + row->mPref = GET_HEIGHT(size, aIsHorizontal) + top + bottom; + + return row->mPref; +} + +nscoord +nsGrid::GetMinRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) +{ + RebuildIfNeeded(); + + nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); + + if (row->IsCollapsed()) + return 0; + + if (row->IsMinSet()) + return row->mMin; + + nsIFrame* box = row->mBox; + + // set in CSS? + if (box) { + bool widthSet, heightSet; + nsSize cssSize(-1, -1); + nsIFrame::AddCSSMinSize(aState, box, cssSize, widthSet, heightSet); + + row->mMin = GET_HEIGHT(cssSize, aIsHorizontal); + + // yep do nothing. + if (row->mMin != -1) + return row->mMin; + } + + // get the offsets so they are cached. + nscoord top; + nscoord bottom; + GetRowOffsets(aState, aIndex, top, bottom, aIsHorizontal); + + // is the row bogus? If so then just ask it for its size + // it should not be affected by cells in the grid. + if (row->mIsBogus) + { + nsSize size(0,0); + if (box) { + size = box->GetPrefSize(aState); + nsBox::AddMargin(box, size); + nsGridLayout2::AddOffset(aState, box, size); + } + + row->mMin = GET_HEIGHT(size, aIsHorizontal) + top + bottom; + return row->mMin; + } + + nsSize size(0,0); + + nsGridCell* child; + + int32_t count = GetColumnCount(aIsHorizontal); + + for (int32_t i=0; i < count; i++) + { + if (aIsHorizontal) + child = GetCellAt(i,aIndex); + else + child = GetCellAt(aIndex,i); + + // ignore collapsed children + if (!child->IsCollapsed()) + { + nsSize childSize = child->GetMinSize(aState); + + nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal); + } + } + + row->mMin = GET_HEIGHT(size, aIsHorizontal); + + return row->mMin; +} + +nscoord +nsGrid::GetMaxRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) +{ + RebuildIfNeeded(); + + nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); + + if (row->IsCollapsed()) + return 0; + + if (row->IsMaxSet()) + return row->mMax; + + nsIFrame* box = row->mBox; + + // set in CSS? + if (box) { + bool widthSet, heightSet; + nsSize cssSize(-1, -1); + nsIFrame::AddCSSMaxSize(box, cssSize, widthSet, heightSet); + + row->mMax = GET_HEIGHT(cssSize, aIsHorizontal); + + // yep do nothing. + if (row->mMax != -1) + return row->mMax; + } + + // get the offsets so they are cached. + nscoord top; + nscoord bottom; + GetRowOffsets(aState, aIndex, top, bottom, aIsHorizontal); + + // is the row bogus? If so then just ask it for its size + // it should not be affected by cells in the grid. + if (row->mIsBogus) + { + nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE); + if (box) { + size = box->GetPrefSize(aState); + nsBox::AddMargin(box, size); + nsGridLayout2::AddOffset(aState, box, size); + } + + row->mMax = GET_HEIGHT(size, aIsHorizontal); + return row->mMax; + } + + nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE); + + nsGridCell* child; + + int32_t count = GetColumnCount(aIsHorizontal); + + for (int32_t i=0; i < count; i++) + { + if (aIsHorizontal) + child = GetCellAt(i,aIndex); + else + child = GetCellAt(aIndex,i); + + // ignore collapsed children + if (!child->IsCollapsed()) + { + nsSize min = child->GetMinSize(aState); + nsSize childSize = nsBox::BoundsCheckMinMax(min, child->GetMaxSize(aState)); + nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal); + } + } + + row->mMax = GET_HEIGHT(size, aIsHorizontal) + top + bottom; + + return row->mMax; +} + +bool +nsGrid::IsGrid(nsIFrame* aBox) +{ + nsIGridPart* part = GetPartFromBox(aBox); + if (!part) + return false; + + nsGridLayout2* grid = part->CastToGridLayout(); + + if (grid) + return true; + + return false; +} + +/** + * This get the flexibilty of the row at aIndex. It's not trivial. There are a few + * things we need to look at. Specifically we need to see if any <rows> or <columns> + * tags are around us. Their flexibilty will affect ours. + */ +nscoord +nsGrid::GetRowFlex(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) +{ + RebuildIfNeeded(); + + nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); + + if (row->IsFlexSet()) + return row->mFlex; + + nsIFrame* box = row->mBox; + row->mFlex = 0; + + if (box) { + + // We need our flex but a inflexible row could be around us. If so + // neither are we. However if its the row tag just inside the grid it won't + // affect us. We need to do this for this case: + // <grid> + // <rows> + // <rows> // this is not flexible. So our children should not be flexible + // <row flex="1"/> + // <row flex="1"/> + // </rows> + // <row/> + // </rows> + // </grid> + // + // or.. + // + // <grid> + // <rows> + // <rows> // this is not flexible. So our children should not be flexible + // <rows flex="1"> + // <row flex="1"/> + // <row flex="1"/> + // </rows> + // <row/> + // </rows> + // </row> + // </grid> + + + // So here is how it looks + // + // <grid> + // <rows> // parentsParent + // <rows> // parent + // <row flex="1"/> + // <row flex="1"/> + // </rows> + // <row/> + // </rows> + // </grid> + + // so the answer is simple: 1) Walk our parent chain. 2) If we find + // someone who is not flexible and they aren't the rows immediately in + // the grid. 3) Then we are not flexible + + box = GetScrollBox(box); + nsIFrame* parent = box->GetParentBox(); + nsIFrame* parentsParent=nullptr; + + while(parent) + { + parent = GetScrollBox(parent); + parentsParent = parent->GetParentBox(); + + // if our parents parent is not a grid + // the get its flex. If its 0 then we are + // not flexible. + if (parentsParent) { + if (!IsGrid(parentsParent)) { + nscoord flex = parent->GetFlex(aState); + nsIFrame::AddCSSFlex(aState, parent, flex); + if (flex == 0) { + row->mFlex = 0; + return row->mFlex; + } + } else + break; + } + + parent = parentsParent; + } + + // get the row flex. + row->mFlex = box->GetFlex(aState); + nsIFrame::AddCSSFlex(aState, box, row->mFlex); + } + + return row->mFlex; +} + +void +nsGrid::SetLargestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal) +{ + if (aIsHorizontal) { + if (aSize.height < aHeight) + aSize.height = aHeight; + } else { + if (aSize.width < aHeight) + aSize.width = aHeight; + } +} + +void +nsGrid::SetSmallestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal) +{ + if (aIsHorizontal) { + if (aSize.height > aHeight) + aSize.height = aHeight; + } else { + if (aSize.width < aHeight) + aSize.width = aHeight; + } +} + +int32_t +nsGrid::GetRowCount(int32_t aIsHorizontal) +{ + RebuildIfNeeded(); + + if (aIsHorizontal) + return mRowCount; + else + return mColumnCount; +} + +int32_t +nsGrid::GetColumnCount(int32_t aIsHorizontal) +{ + return GetRowCount(!aIsHorizontal); +} + +/* + * A cell in the given row or columns at the given index has had a child added or removed + */ +void +nsGrid::CellAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) +{ + // TBD see if the cell will fit in our current row. If it will + // just add it in. + // but for now rebuild everything. + if (mMarkingDirty) + return; + + NeedsRebuild(aState); +} + +/** + * A row or columns at the given index had been added or removed + */ +void +nsGrid::RowAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) +{ + // TBD see if we have extra room in the table and just add the new row in + // for now rebuild the world + if (mMarkingDirty) + return; + + NeedsRebuild(aState); +} + +/* + * Scrollframes are tranparent. If this is given a scrollframe is will return the + * frame inside. If there is no scrollframe it does nothing. + */ +nsIFrame* +nsGrid::GetScrolledBox(nsIFrame* aChild) +{ + // first see if it is a scrollframe. If so walk down into it and get the scrolled child + nsIScrollableFrame *scrollFrame = do_QueryFrame(aChild); + if (scrollFrame) { + nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); + NS_ASSERTION(scrolledFrame,"Error no scroll frame!!"); + return scrolledFrame; + } + + return aChild; +} + +/* + * Scrollframes are tranparent. If this is given a child in a scrollframe is will return the + * scrollframe ourside it. If there is no scrollframe it does nothing. + */ +nsIFrame* +nsGrid::GetScrollBox(nsIFrame* aChild) +{ + if (!aChild) + return nullptr; + + // get parent + nsIFrame* parent = aChild->GetParentBox(); + + // walk up until we find a scrollframe or a part + // if it's a scrollframe return it. + // if it's a parent then the child passed does not + // have a scroll frame immediately wrapped around it. + while (parent) { + nsIScrollableFrame *scrollFrame = do_QueryFrame(parent); + // scrollframe? Yep return it. + if (scrollFrame) + return parent; + + nsCOMPtr<nsIGridPart> parentGridRow = GetPartFromBox(parent); + // if a part then just return the child + if (parentGridRow) + break; + + parent = parent->GetParentBox(); + } + + return aChild; +} + + + +#ifdef DEBUG_grid +void +nsGrid::PrintCellMap() +{ + + printf("-----Columns------\n"); + for (int x=0; x < mColumnCount; x++) + { + + nsGridRow* column = GetColumnAt(x); + printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin, column->mMax); + } + + printf("\n-----Rows------\n"); + for (x=0; x < mRowCount; x++) + { + nsGridRow* column = GetRowAt(x); + printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin, column->mMax); + } + + printf("\n"); + +} +#endif diff --git a/layout/xul/grid/nsGrid.h b/layout/xul/grid/nsGrid.h new file mode 100644 index 000000000..7be0944c6 --- /dev/null +++ b/layout/xul/grid/nsGrid.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsGrid_h___ +#define nsGrid_h___ + +#include "nsStackLayout.h" +#include "nsIGridPart.h" +#include "nsCOMPtr.h" + +class nsGridRowGroupLayout; +class nsGridRowLayout; +class nsBoxLayoutState; +class nsGridCell; + +//#define DEBUG_grid 1 + +/** + * The grid data structure, i.e., the grid cellmap. + */ +class nsGrid +{ +public: + nsGrid(); + ~nsGrid(); + + nsGridRow* GetColumnAt(int32_t aIndex, bool aIsHorizontal = true); + nsGridRow* GetRowAt(int32_t aIndex, bool aIsHorizontal = true); + nsGridCell* GetCellAt(int32_t aX, int32_t aY); + + void NeedsRebuild(nsBoxLayoutState& aBoxLayoutState); + void RebuildIfNeeded(); + + // For all the methods taking an aIsHorizontal parameter: + // * When aIsHorizontal is true, the words "rows" and (for + // GetColumnCount) "columns" refer to their normal meanings. + // * When aIsHorizontal is false, the meanings are flipped. + // FIXME: Maybe eliminate GetColumnCount and change aIsHorizontal to + // aIsRows? (Calling it horizontal doesn't really make sense because + // row groups and columns have vertical orientation, whereas column + // groups and rows are horizontal.) + + nsSize GetPrefRowSize(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true); + nsSize GetMinRowSize(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true); + nsSize GetMaxRowSize(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true); + nscoord GetRowFlex(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true); + + nscoord GetPrefRowHeight(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true); + nscoord GetMinRowHeight(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true); + nscoord GetMaxRowHeight(nsBoxLayoutState& aBoxLayoutState, int32_t aRowIndex, bool aIsHorizontal = true); + void GetRowOffsets(nsBoxLayoutState& aState, int32_t aIndex, nscoord& aTop, nscoord& aBottom, bool aIsHorizontal = true); + + void RowAddedOrRemoved(nsBoxLayoutState& aBoxLayoutState, int32_t aIndex, bool aIsHorizontal = true); + void CellAddedOrRemoved(nsBoxLayoutState& aBoxLayoutState, int32_t aIndex, bool aIsHorizontal = true); + void DirtyRows(nsIFrame* aRowBox, nsBoxLayoutState& aState); +#ifdef DEBUG_grid + void PrintCellMap(); +#endif + int32_t GetExtraColumnCount(bool aIsHorizontal = true); + int32_t GetExtraRowCount(bool aIsHorizontal = true); + +// accessors + void SetBox(nsIFrame* aBox) { mBox = aBox; } + nsIFrame* GetBox() { return mBox; } + nsIFrame* GetRowsBox() { return mRowsBox; } + nsIFrame* GetColumnsBox() { return mColumnsBox; } + int32_t GetRowCount(int32_t aIsHorizontal = true); + int32_t GetColumnCount(int32_t aIsHorizontal = true); + + static nsIFrame* GetScrolledBox(nsIFrame* aChild); + static nsIFrame* GetScrollBox(nsIFrame* aChild); + static nsIGridPart* GetPartFromBox(nsIFrame* aBox); + void GetFirstAndLastRow(nsBoxLayoutState& aState, + int32_t& aFirstIndex, + int32_t& aLastIndex, + nsGridRow*& aFirstRow, + nsGridRow*& aLastRow, + bool aIsHorizontal); + +private: + + nsMargin GetBoxTotalMargin(nsIFrame* aBox, bool aIsHorizontal = true); + + void FreeMap(); + void FindRowsAndColumns(nsIFrame** aRows, nsIFrame** aColumns); + void BuildRows(nsIFrame* aBox, int32_t aSize, nsGridRow** aColumnsRows, bool aIsHorizontal = true); + nsGridCell* BuildCellMap(int32_t aRows, int32_t aColumns); + void PopulateCellMap(nsGridRow* aRows, nsGridRow* aColumns, int32_t aRowCount, int32_t aColumnCount, bool aIsHorizontal = true); + void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount); + void SetLargestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal = true); + void SetSmallestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal = true); + bool IsGrid(nsIFrame* aBox); + + // the box that implement the <grid> tag + nsIFrame* mBox; + + // an array of row object + nsGridRow* mRows; + + // an array of columns objects. + nsGridRow* mColumns; + + // the first in the <grid> that implements the <rows> tag. + nsIFrame* mRowsBox; + + // the first in the <grid> that implements the <columns> tag. + nsIFrame* mColumnsBox; + + // a flag that is false tells us to rebuild the who grid + bool mNeedsRebuild; + + // number of rows and columns as defined by the XUL + int32_t mRowCount; + int32_t mColumnCount; + + // number of rows and columns that are implied but not + // explicitly defined int he XUL + int32_t mExtraRowCount; + int32_t mExtraColumnCount; + + // x,y array of cells in the rows and columns + nsGridCell* mCellMap; + + // a flag that when true suppresses all other MarkDirties. This + // prevents lots of extra work being done. + bool mMarkingDirty; +}; + +#endif + diff --git a/layout/xul/grid/nsGridCell.cpp b/layout/xul/grid/nsGridCell.cpp new file mode 100644 index 000000000..b9d65e74d --- /dev/null +++ b/layout/xul/grid/nsGridCell.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsGridCell.h" +#include "nsFrame.h" +#include "nsBox.h" +#include "nsGridLayout2.h" + + +nsGridCell::nsGridCell():mBoxInColumn(nullptr),mBoxInRow(nullptr) +{ + MOZ_COUNT_CTOR(nsGridCell); +} + +nsGridCell::~nsGridCell() +{ + MOZ_COUNT_DTOR(nsGridCell); +} + +nsSize +nsGridCell::GetPrefSize(nsBoxLayoutState& aState) +{ + nsSize sum(0,0); + + // take our 2 children and add them up. + // we are as wide as the widest child plus its left offset + // we are tall as the tallest child plus its top offset + + if (mBoxInColumn) { + nsSize pref = mBoxInColumn->GetPrefSize(aState); + + nsBox::AddMargin(mBoxInColumn, pref); + nsGridLayout2::AddOffset(aState, mBoxInColumn, pref); + + nsBoxLayout::AddLargestSize(sum, pref); + } + + if (mBoxInRow) { + nsSize pref = mBoxInRow->GetPrefSize(aState); + + nsBox::AddMargin(mBoxInRow, pref); + nsGridLayout2::AddOffset(aState, mBoxInRow, pref); + + nsBoxLayout::AddLargestSize(sum, pref); + } + + return sum; +} + +nsSize +nsGridCell::GetMinSize(nsBoxLayoutState& aState) +{ + nsSize sum(0, 0); + + // take our 2 children and add them up. + // we are as wide as the widest child plus its left offset + // we are tall as the tallest child plus its top offset + + if (mBoxInColumn) { + nsSize min = mBoxInColumn->GetMinSize(aState); + + nsBox::AddMargin(mBoxInColumn, min); + nsGridLayout2::AddOffset(aState, mBoxInColumn, min); + + nsBoxLayout::AddLargestSize(sum, min); + } + + if (mBoxInRow) { + nsSize min = mBoxInRow->GetMinSize(aState); + + nsBox::AddMargin(mBoxInRow, min); + nsGridLayout2::AddOffset(aState, mBoxInRow, min); + + nsBoxLayout::AddLargestSize(sum, min); + } + + return sum; +} + +nsSize +nsGridCell::GetMaxSize(nsBoxLayoutState& aState) +{ + nsSize sum(NS_INTRINSICSIZE, NS_INTRINSICSIZE); + + // take our 2 children and add them up. + // we are as wide as the smallest child plus its left offset + // we are tall as the shortest child plus its top offset + + if (mBoxInColumn) { + nsSize max = mBoxInColumn->GetMaxSize(aState); + + nsBox::AddMargin(mBoxInColumn, max); + nsGridLayout2::AddOffset(aState, mBoxInColumn, max); + + nsBoxLayout::AddSmallestSize(sum, max); + } + + if (mBoxInRow) { + nsSize max = mBoxInRow->GetMaxSize(aState); + + nsBox::AddMargin(mBoxInRow, max); + nsGridLayout2::AddOffset(aState, mBoxInRow, max); + + nsBoxLayout::AddSmallestSize(sum, max); + } + + return sum; +} + + +bool +nsGridCell::IsCollapsed() +{ + return ((mBoxInColumn && mBoxInColumn->IsCollapsed()) || + (mBoxInRow && mBoxInRow->IsCollapsed())); +} + + diff --git a/layout/xul/grid/nsGridCell.h b/layout/xul/grid/nsGridCell.h new file mode 100644 index 000000000..74769bedd --- /dev/null +++ b/layout/xul/grid/nsGridCell.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Author: + Eric D Vaughan + +**/ + +#ifndef nsGridCell_h___ +#define nsGridCell_h___ + +class nsBoxLayoutState; +struct nsSize; +class nsIFrame; + +/* + * Grid cell is what makes up the cellmap in the grid. Each GridCell contains + * 2 pointers. One to the matching box in the columns and one to the matching box + * in the rows. Remember that you can put content in both rows and columns. + * When asked for preferred/min/max sizes it works like a stack and takes the + * biggest sizes. + */ + +class nsGridCell +{ +public: + nsGridCell(); + virtual ~nsGridCell(); + + nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState); + nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState); + nsSize GetMaxSize(nsBoxLayoutState& aBoxLayoutState); + bool IsCollapsed(); + +// accessors + nsIFrame* GetBoxInColumn() { return mBoxInColumn; } + nsIFrame* GetBoxInRow() { return mBoxInRow; } + void SetBoxInRow(nsIFrame* aBox) { mBoxInRow = aBox; } + void SetBoxInColumn(nsIFrame* aBox) { mBoxInColumn = aBox; } + +private: + nsIFrame* mBoxInColumn; + nsIFrame* mBoxInRow; +}; + +#endif + diff --git a/layout/xul/grid/nsGridLayout2.cpp b/layout/xul/grid/nsGridLayout2.cpp new file mode 100644 index 000000000..3b5d6733a --- /dev/null +++ b/layout/xul/grid/nsGridLayout2.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsGridLayout2.h" +#include "nsGridRowGroupLayout.h" +#include "nsGridRow.h" +#include "nsBox.h" +#include "nsIScrollableFrame.h" +#include "nsSprocketLayout.h" +#include "nsHTMLReflowState.h" + +nsresult +NS_NewGridLayout2( nsIPresShell* aPresShell, nsBoxLayout** aNewLayout) +{ + *aNewLayout = new nsGridLayout2(aPresShell); + NS_IF_ADDREF(*aNewLayout); + + return NS_OK; + +} + +nsGridLayout2::nsGridLayout2(nsIPresShell* aPresShell):nsStackLayout() +{ +} + +// static +void +nsGridLayout2::AddOffset(nsBoxLayoutState& aState, nsIFrame* aChild, nsSize& aSize) +{ + nsMargin offset; + GetOffset(aState, aChild, offset); + aSize.width += offset.left; + aSize.height += offset.top; +} + +NS_IMETHODIMP +nsGridLayout2::Layout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + // XXX This should be set a better way! + mGrid.SetBox(aBox); + NS_ASSERTION(aBox->GetLayoutManager() == this, "setting incorrect box"); + + nsresult rv = nsStackLayout::Layout(aBox, aBoxLayoutState); +#ifdef DEBUG_grid + mGrid.PrintCellMap(); +#endif + return rv; +} + +void +nsGridLayout2::IntrinsicWidthsDirty(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsStackLayout::IntrinsicWidthsDirty(aBox, aBoxLayoutState); + // XXXldb We really don't need to do all the work that NeedsRebuild + // does; we just need to mark intrinsic widths dirty on the + // (row/column)(s/-groups). + mGrid.NeedsRebuild(aBoxLayoutState); +} + +nsGrid* +nsGridLayout2::GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor) +{ + // XXX This should be set a better way! + mGrid.SetBox(aBox); + NS_ASSERTION(aBox->GetLayoutManager() == this, "setting incorrect box"); + return &mGrid; +} + +void +nsGridLayout2::AddWidth(nsSize& aSize, nscoord aSize2, bool aIsHorizontal) +{ + nscoord& size = GET_WIDTH(aSize, aIsHorizontal); + + if (size != NS_INTRINSICSIZE) { + if (aSize2 == NS_INTRINSICSIZE) + size = NS_INTRINSICSIZE; + else + size += aSize2; + } +} + +nsSize +nsGridLayout2::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize = nsStackLayout::GetMinSize(aBox, aState); + + // if there are no <rows> tags that will sum up our columns, + // sum up our columns here. + nsSize total(0,0); + nsIFrame* rowsBox = mGrid.GetRowsBox(); + nsIFrame* columnsBox = mGrid.GetColumnsBox(); + if (!rowsBox || !columnsBox) { + if (!rowsBox) { + // max height is the sum of our rows + int32_t rows = mGrid.GetRowCount(); + for (int32_t i=0; i < rows; i++) + { + nscoord height = mGrid.GetMinRowHeight(aState, i, true); + AddWidth(total, height, false); // AddHeight + } + } + + if (!columnsBox) { + // max height is the sum of our rows + int32_t columns = mGrid.GetColumnCount(); + for (int32_t i=0; i < columns; i++) + { + nscoord width = mGrid.GetMinRowHeight(aState, i, false); + AddWidth(total, width, true); // AddWidth + } + } + + AddMargin(aBox, total); + AddOffset(aState, aBox, total); + AddLargestSize(minSize, total); + } + + return minSize; +} + +nsSize +nsGridLayout2::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize pref = nsStackLayout::GetPrefSize(aBox, aState); + + // if there are no <rows> tags that will sum up our columns, + // sum up our columns here. + nsSize total(0,0); + nsIFrame* rowsBox = mGrid.GetRowsBox(); + nsIFrame* columnsBox = mGrid.GetColumnsBox(); + if (!rowsBox || !columnsBox) { + if (!rowsBox) { + // max height is the sum of our rows + int32_t rows = mGrid.GetRowCount(); + for (int32_t i=0; i < rows; i++) + { + nscoord height = mGrid.GetPrefRowHeight(aState, i, true); + AddWidth(total, height, false); // AddHeight + } + } + + if (!columnsBox) { + // max height is the sum of our rows + int32_t columns = mGrid.GetColumnCount(); + for (int32_t i=0; i < columns; i++) + { + nscoord width = mGrid.GetPrefRowHeight(aState, i, false); + AddWidth(total, width, true); // AddWidth + } + } + + AddMargin(aBox, total); + AddOffset(aState, aBox, total); + AddLargestSize(pref, total); + } + + return pref; +} + +nsSize +nsGridLayout2::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize maxSize = nsStackLayout::GetMaxSize(aBox, aState); + + // if there are no <rows> tags that will sum up our columns, + // sum up our columns here. + nsSize total(NS_INTRINSICSIZE, NS_INTRINSICSIZE); + nsIFrame* rowsBox = mGrid.GetRowsBox(); + nsIFrame* columnsBox = mGrid.GetColumnsBox(); + if (!rowsBox || !columnsBox) { + if (!rowsBox) { + total.height = 0; + // max height is the sum of our rows + int32_t rows = mGrid.GetRowCount(); + for (int32_t i=0; i < rows; i++) + { + nscoord height = mGrid.GetMaxRowHeight(aState, i, true); + AddWidth(total, height, false); // AddHeight + } + } + + if (!columnsBox) { + total.width = 0; + // max height is the sum of our rows + int32_t columns = mGrid.GetColumnCount(); + for (int32_t i=0; i < columns; i++) + { + nscoord width = mGrid.GetMaxRowHeight(aState, i, false); + AddWidth(total, width, true); // AddWidth + } + } + + AddMargin(aBox, total); + AddOffset(aState, aBox, total); + AddSmallestSize(maxSize, total); + } + + return maxSize; +} + +int32_t +nsGridLayout2::BuildRows(nsIFrame* aBox, nsGridRow* aRows) +{ + if (aBox) { + aRows[0].Init(aBox, true); + return 1; + } + return 0; +} + +nsMargin +nsGridLayout2::GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) +{ + nsMargin margin(0,0,0,0); + return margin; +} + +void +nsGridLayout2::ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aPrevBox, + const nsFrameList::Slice& aNewChildren) +{ + mGrid.NeedsRebuild(aState); +} + +void +nsGridLayout2::ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState, + const nsFrameList::Slice& aNewChildren) +{ + mGrid.NeedsRebuild(aState); +} + +void +nsGridLayout2::ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aChildList) +{ + mGrid.NeedsRebuild(aState); +} + +void +nsGridLayout2::ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aChildList) +{ + mGrid.NeedsRebuild(aState); +} + +NS_IMPL_ADDREF_INHERITED(nsGridLayout2, nsStackLayout) +NS_IMPL_RELEASE_INHERITED(nsGridLayout2, nsStackLayout) + +NS_INTERFACE_MAP_BEGIN(nsGridLayout2) + NS_INTERFACE_MAP_ENTRY(nsIGridPart) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGridPart) +NS_INTERFACE_MAP_END_INHERITING(nsStackLayout) diff --git a/layout/xul/grid/nsGridLayout2.h b/layout/xul/grid/nsGridLayout2.h new file mode 100644 index 000000000..6b32aa7dc --- /dev/null +++ b/layout/xul/grid/nsGridLayout2.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsGridLayout2_h___ +#define nsGridLayout2_h___ + +#include "mozilla/Attributes.h" +#include "nsStackLayout.h" +#include "nsIGridPart.h" +#include "nsCoord.h" +#include "nsGrid.h" + +class nsIPresContext; +class nsGridRowGroupLayout; +class nsGridRowLayout; +class nsGridRow; +class nsBoxLayoutState; +class nsGridCell; + +/** + * The nsBoxLayout implementation for a grid. + */ +class nsGridLayout2 : public nsStackLayout, + public nsIGridPart +{ +public: + + friend nsresult NS_NewGridLayout2(nsIPresShell* aPresShell, nsBoxLayout** aNewLayout); + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Layout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void IntrinsicWidthsDirty(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + + virtual nsGridRowGroupLayout* CastToRowGroupLayout() MOZ_OVERRIDE { return nullptr; } + virtual nsGridLayout2* CastToGridLayout() MOZ_OVERRIDE { return this; } + virtual nsGrid* GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor=nullptr) MOZ_OVERRIDE; + virtual nsIGridPart* GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) MOZ_OVERRIDE { + NS_NOTREACHED("Should not be called"); return nullptr; + } + virtual nsSize GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) MOZ_OVERRIDE { aRowCount++; } + virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) MOZ_OVERRIDE { } + virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) MOZ_OVERRIDE; + virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) MOZ_OVERRIDE; + virtual Type GetType() MOZ_OVERRIDE { return eGrid; } + virtual void ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aPrevBox, + const nsFrameList::Slice& aNewChildren) MOZ_OVERRIDE; + virtual void ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState, + const nsFrameList::Slice& aNewChildren) MOZ_OVERRIDE; + virtual void ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aChildList) MOZ_OVERRIDE; + virtual void ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aChildList) MOZ_OVERRIDE; + + virtual nsIGridPart* AsGridPart() MOZ_OVERRIDE { return this; } + + static void AddOffset(nsBoxLayoutState& aState, nsIFrame* aChild, nsSize& aSize); + +protected: + + nsGridLayout2(nsIPresShell* aShell); + nsGrid mGrid; + +private: + void AddWidth(nsSize& aSize, nscoord aSize2, bool aIsHorizontal); + + +}; // class nsGridLayout2 + + +#endif + diff --git a/layout/xul/grid/nsGridRow.cpp b/layout/xul/grid/nsGridRow.cpp new file mode 100644 index 000000000..3e6c18edc --- /dev/null +++ b/layout/xul/grid/nsGridRow.cpp @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsGridRow.h" +#include "nsBoxLayoutState.h" +#include "nsIFrame.h" + +nsGridRow::nsGridRow():mIsBogus(false), + mBox(nullptr), + mFlex(-1), + mPref(-1), + mMin(-1), + mMax(-1), + mTop(-1), + mBottom(-1), + mTopMargin(0), + mBottomMargin(0) + +{ + MOZ_COUNT_CTOR(nsGridRow); +} + +void +nsGridRow::Init(nsIFrame* aBox, bool aIsBogus) +{ + mBox = aBox; + mIsBogus = aIsBogus; + mFlex = -1; + mPref = -1; + mMin = -1; + mMax = -1; + mTop = -1; + mBottom = -1; + mTopMargin = 0; + mBottomMargin = 0; +} + +nsGridRow::~nsGridRow() +{ + MOZ_COUNT_DTOR(nsGridRow); +} + +bool +nsGridRow::IsCollapsed() +{ + return mBox && mBox->IsCollapsed(); +} + diff --git a/layout/xul/grid/nsGridRow.h b/layout/xul/grid/nsGridRow.h new file mode 100644 index 000000000..86b983ba5 --- /dev/null +++ b/layout/xul/grid/nsGridRow.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Author: + Eric D Vaughan + +**/ + +#ifndef nsGridRow_h___ +#define nsGridRow_h___ + +#include "nsCoord.h" + +class nsGridLayout2; +class nsBoxLayoutState; +class nsIFrame; + +/** + * The row (or column) data structure in the grid cellmap. + */ +class nsGridRow +{ +public: + nsGridRow(); + ~nsGridRow(); + + void Init(nsIFrame* aBox, bool aIsBogus); + +// accessors + nsIFrame* GetBox() { return mBox; } + bool IsPrefSet() { return (mPref != -1); } + bool IsMinSet() { return (mMin != -1); } + bool IsMaxSet() { return (mMax != -1); } + bool IsFlexSet() { return (mFlex != -1); } + bool IsOffsetSet() { return (mTop != -1 && mBottom != -1); } + bool IsCollapsed(); + +public: + + bool mIsBogus; + nsIFrame* mBox; + nscoord mFlex; + nscoord mPref; + nscoord mMin; + nscoord mMax; + nscoord mTop; + nscoord mBottom; + nscoord mTopMargin; + nscoord mBottomMargin; + +}; + + +#endif + diff --git a/layout/xul/grid/nsGridRowGroupFrame.cpp b/layout/xul/grid/nsGridRowGroupFrame.cpp new file mode 100644 index 000000000..9faf35e87 --- /dev/null +++ b/layout/xul/grid/nsGridRowGroupFrame.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsGridRowGroupFrame.h" +#include "nsGridRowLeafLayout.h" +#include "nsGridRow.h" +#include "nsBoxLayoutState.h" +#include "nsGridLayout2.h" + +already_AddRefed<nsBoxLayout> NS_NewGridRowGroupLayout(); + +nsIFrame* +NS_NewGridRowGroupFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + nsCOMPtr<nsBoxLayout> layout = NS_NewGridRowGroupLayout(); + if (!layout) { + return nullptr; + } + + return new (aPresShell) nsGridRowGroupFrame(aPresShell, aContext, layout); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsGridRowGroupFrame) + + +/** + * This is redefined because row groups have a funny property. If they are flexible + * then their flex must be equal to the sum of their children's flexes. + */ +nscoord +nsGridRowGroupFrame::GetFlex(nsBoxLayoutState& aState) +{ + // if we are flexible out flexibility is determined by our columns. + // so first get the our flex. If not 0 then our flex is the sum of + // our columns flexes. + + if (!DoesNeedRecalc(mFlex)) + return mFlex; + + if (nsBoxFrame::GetFlex(aState) == 0) + return 0; + + // ok we are flexible add up our children + nscoord totalFlex = 0; + nsIFrame* child = GetChildBox(); + while (child) + { + totalFlex += child->GetFlex(aState); + child = child->GetNextBox(); + } + + mFlex = totalFlex; + + return totalFlex; +} + + diff --git a/layout/xul/grid/nsGridRowGroupFrame.h b/layout/xul/grid/nsGridRowGroupFrame.h new file mode 100644 index 000000000..d1621298b --- /dev/null +++ b/layout/xul/grid/nsGridRowGroupFrame.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Eric D Vaughan + A frame that can have multiple children. Only one child may be displayed at one time. So the + can be flipped though like a deck of cards. + +**/ + +#ifndef nsGridRowGroupFrame_h___ +#define nsGridRowGroupFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +/** + * A frame representing a grid row (or column) group, which is usually + * an element that is a child of a grid and contains all the rows (or + * all the columns). However, multiple levels of groups are allowed, so + * the parent or child could instead be another group. + */ +class nsGridRowGroupFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE + { + return MakeFrameName(NS_LITERAL_STRING("nsGridRowGroup"), aResult); + } +#endif + + nsGridRowGroupFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + nsBoxLayout* aLayoutManager): + nsBoxFrame(aPresShell, aContext, false, aLayoutManager) {} + + virtual nscoord GetFlex(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + +}; // class nsGridRowGroupFrame + + + +#endif + diff --git a/layout/xul/grid/nsGridRowGroupLayout.cpp b/layout/xul/grid/nsGridRowGroupLayout.cpp new file mode 100644 index 000000000..dfbc49478 --- /dev/null +++ b/layout/xul/grid/nsGridRowGroupLayout.cpp @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + + +/* + * The nsGridRowGroupLayout implements the <rows> or <columns> tag in a grid. + */ + +#include "nsGridRowGroupLayout.h" +#include "nsCOMPtr.h" +#include "nsIScrollableFrame.h" +#include "nsBoxLayoutState.h" +#include "nsGridLayout2.h" +#include "nsGridRow.h" +#include "nsHTMLReflowState.h" + +already_AddRefed<nsBoxLayout> NS_NewGridRowGroupLayout() +{ + nsRefPtr<nsBoxLayout> layout = new nsGridRowGroupLayout(); + return layout.forget(); +} + +nsGridRowGroupLayout::nsGridRowGroupLayout():nsGridRowLayout(), mRowCount(0) +{ +} + +nsGridRowGroupLayout::~nsGridRowGroupLayout() +{ +} + +void +nsGridRowGroupLayout::ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsHorizontal(aBox); + + if (grid) + grid->RowAddedOrRemoved(aState, index, isHorizontal); +} + +void +nsGridRowGroupLayout::AddWidth(nsSize& aSize, nscoord aSize2, bool aIsHorizontal) +{ + nscoord& size = GET_WIDTH(aSize, aIsHorizontal); + + if (size == NS_INTRINSICSIZE || aSize2 == NS_INTRINSICSIZE) + size = NS_INTRINSICSIZE; + else + size += aSize2; +} + +nsSize +nsGridRowGroupLayout::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize vpref = nsGridRowLayout::GetPrefSize(aBox, aState); + + + /* It is possible that we could have some extra columns. This is when less columns in XUL were + * defined that needed. And example might be a grid with 3 defined columns but a row with 4 cells in + * it. We would need an extra column to make the grid work. But because that extra column does not + * have a box associated with it we must add its size in manually. Remember we could have extra rows + * as well. + */ + + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + + if (grid) + { + // make sure we add in extra columns sizes as well + bool isHorizontal = IsHorizontal(aBox); + int32_t extraColumns = grid->GetExtraColumnCount(isHorizontal); + int32_t start = grid->GetColumnCount(isHorizontal) - grid->GetExtraColumnCount(isHorizontal); + for (int32_t i=0; i < extraColumns; i++) + { + nscoord pref = + grid->GetPrefRowHeight(aState, i+start, !isHorizontal); // GetPrefColumnWidth + + AddWidth(vpref, pref, isHorizontal); + } + } + + return vpref; +} + +nsSize +nsGridRowGroupLayout::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize maxSize = nsGridRowLayout::GetMaxSize(aBox, aState); + + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + + if (grid) + { + // make sure we add in extra columns sizes as well + bool isHorizontal = IsHorizontal(aBox); + int32_t extraColumns = grid->GetExtraColumnCount(isHorizontal); + int32_t start = grid->GetColumnCount(isHorizontal) - grid->GetExtraColumnCount(isHorizontal); + for (int32_t i=0; i < extraColumns; i++) + { + nscoord max = + grid->GetMaxRowHeight(aState, i+start, !isHorizontal); // GetMaxColumnWidth + + AddWidth(maxSize, max, isHorizontal); + } + } + + return maxSize; +} + +nsSize +nsGridRowGroupLayout::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize = nsGridRowLayout::GetMinSize(aBox, aState); + + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + + if (grid) + { + // make sure we add in extra columns sizes as well + bool isHorizontal = IsHorizontal(aBox); + int32_t extraColumns = grid->GetExtraColumnCount(isHorizontal); + int32_t start = grid->GetColumnCount(isHorizontal) - grid->GetExtraColumnCount(isHorizontal); + for (int32_t i=0; i < extraColumns; i++) + { + nscoord min = + grid->GetMinRowHeight(aState, i+start, !isHorizontal); // GetMinColumnWidth + AddWidth(minSize, min, isHorizontal); + } + } + + return minSize; +} + +/* + * Run down through our children dirtying them recursively. + */ +void +nsGridRowGroupLayout::DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + if (aBox) { + // mark us dirty + // XXXldb We probably don't want to walk up the ancestor chain + // calling MarkIntrinsicWidthsDirty for every row group. + aState.PresShell()->FrameNeedsReflow(aBox, nsIPresShell::eTreeChange, + NS_FRAME_IS_DIRTY); + nsIFrame* child = aBox->GetChildBox(); + + while(child) { + + // walk into scrollframes + nsIFrame* deepChild = nsGrid::GetScrolledBox(child); + + // walk into other monuments + nsIGridPart* monument = nsGrid::GetPartFromBox(deepChild); + if (monument) + monument->DirtyRows(deepChild, aState); + + child = child->GetNextBox(); + } + } +} + + +void +nsGridRowGroupLayout::CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) +{ + if (aBox) { + int32_t startCount = aRowCount; + + nsIFrame* child = aBox->GetChildBox(); + + while(child) { + + // first see if it is a scrollframe. If so walk down into it and get the scrolled child + nsIFrame* deepChild = nsGrid::GetScrolledBox(child); + + nsIGridPart* monument = nsGrid::GetPartFromBox(deepChild); + if (monument) { + monument->CountRowsColumns(deepChild, aRowCount, aComputedColumnCount); + child = child->GetNextBox(); + deepChild = child; + continue; + } + + child = child->GetNextBox(); + + // if not a monument. Then count it. It will be a bogus row + aRowCount++; + } + + mRowCount = aRowCount - startCount; + } +} + + +/** + * Fill out the given row structure recursively + */ +int32_t +nsGridRowGroupLayout::BuildRows(nsIFrame* aBox, nsGridRow* aRows) +{ + int32_t rowCount = 0; + + if (aBox) { + nsIFrame* child = aBox->GetChildBox(); + + while(child) { + + // first see if it is a scrollframe. If so walk down into it and get the scrolled child + nsIFrame* deepChild = nsGrid::GetScrolledBox(child); + + nsIGridPart* monument = nsGrid::GetPartFromBox(deepChild); + if (monument) { + rowCount += monument->BuildRows(deepChild, &aRows[rowCount]); + child = child->GetNextBox(); + deepChild = child; + continue; + } + + aRows[rowCount].Init(child, true); + + child = child->GetNextBox(); + + // if not a monument. Then count it. It will be a bogus row + rowCount++; + } + } + + return rowCount; +} + +nsMargin +nsGridRowGroupLayout::GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) +{ + // group have border and padding added to the total margin + + nsMargin margin = nsGridRowLayout::GetTotalMargin(aBox, aIsHorizontal); + + // make sure we have the scrollframe on the outside if it has one. + // that's where the border is. + aBox = nsGrid::GetScrollBox(aBox); + + // add our border/padding to it + nsMargin borderPadding(0,0,0,0); + aBox->GetBorderAndPadding(borderPadding); + margin += borderPadding; + + return margin; +} + + diff --git a/layout/xul/grid/nsGridRowGroupLayout.h b/layout/xul/grid/nsGridRowGroupLayout.h new file mode 100644 index 000000000..956532e15 --- /dev/null +++ b/layout/xul/grid/nsGridRowGroupLayout.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Author: + Eric D Vaughan + +**/ + +#ifndef nsGridRowGroupLayout_h___ +#define nsGridRowGroupLayout_h___ + +#include "mozilla/Attributes.h" +#include "nsGridRowLayout.h" + +/** + * The nsBoxLayout implementation for nsGridRowGroupFrame. + */ +class nsGridRowGroupLayout : public nsGridRowLayout +{ +public: + + friend already_AddRefed<nsBoxLayout> NS_NewGridRowGroupLayout(); + + virtual nsGridRowGroupLayout* CastToRowGroupLayout() MOZ_OVERRIDE { return this; } + virtual nsSize GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) MOZ_OVERRIDE; + virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) MOZ_OVERRIDE; + virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) MOZ_OVERRIDE; + virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) MOZ_OVERRIDE; + virtual int32_t GetRowCount() MOZ_OVERRIDE { return mRowCount; } + virtual Type GetType() MOZ_OVERRIDE { return eRowGroup; } + +protected: + nsGridRowGroupLayout(); + virtual ~nsGridRowGroupLayout(); + + virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState) MOZ_OVERRIDE; + static void AddWidth(nsSize& aSize, nscoord aSize2, bool aIsHorizontal); + +private: + int32_t mRowCount; +}; + +#endif + diff --git a/layout/xul/grid/nsGridRowLayout.cpp b/layout/xul/grid/nsGridRowLayout.cpp new file mode 100644 index 000000000..2d9461eaf --- /dev/null +++ b/layout/xul/grid/nsGridRowLayout.cpp @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsGridRowLayout.h" +#include "nsBoxLayoutState.h" +#include "nsIScrollableFrame.h" +#include "nsBox.h" +#include "nsStackLayout.h" +#include "nsGrid.h" + +nsGridRowLayout::nsGridRowLayout():nsSprocketLayout() +{ +} + +void +nsGridRowLayout::ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aPrevBox, + const nsFrameList::Slice& aNewChildren) +{ + ChildAddedOrRemoved(aBox, aState); +} + +void +nsGridRowLayout::ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState, + const nsFrameList::Slice& aNewChildren) +{ + ChildAddedOrRemoved(aBox, aState); +} + +void +nsGridRowLayout::ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) +{ + ChildAddedOrRemoved(aBox, aState); +} + +void +nsGridRowLayout::ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) +{ + ChildAddedOrRemoved(aBox, aState); +} + +nsIGridPart* +nsGridRowLayout::GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) +{ + // go up and find our parent gridRow. Skip and non gridRow + // parents. + *aParentBox = nullptr; + + // walk up through any scrollboxes + aBox = nsGrid::GetScrollBox(aBox); + + // get the parent + if (aBox) + aBox = aBox->GetParentBox(); + + if (aBox) + { + nsIGridPart* parentGridRow = nsGrid::GetPartFromBox(aBox); + if (parentGridRow && parentGridRow->CanContain(this)) { + *aParentBox = aBox; + return parentGridRow; + } + } + + return nullptr; +} + + +nsGrid* +nsGridRowLayout::GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor) +{ + + if (aRequestor == nullptr) + { + nsIFrame* parentBox; // nsIFrame is implemented by nsIFrame and is not refcounted. + nsIGridPart* parent = GetParentGridPart(aBox, &parentBox); + if (parent) + return parent->GetGrid(parentBox, aIndex, this); + return nullptr; + } + + int32_t index = -1; + nsIFrame* child = aBox->GetChildBox(); + int32_t count = 0; + while(child) + { + // if there is a scrollframe walk inside it to its child + nsIFrame* childBox = nsGrid::GetScrolledBox(child); + + nsBoxLayout* layout = childBox->GetLayoutManager(); + nsIGridPart* gridRow = nsGrid::GetPartFromBox(childBox); + if (gridRow) + { + if (layout == aRequestor) { + index = count; + break; + } + count += gridRow->GetRowCount(); + } else + count++; + + child = child->GetNextBox(); + } + + // if we didn't find ourselves then the tree isn't properly formed yet + // this could happen during initial construction so lets just + // fail. + if (index == -1) { + *aIndex = -1; + return nullptr; + } + + (*aIndex) += index; + + nsIFrame* parentBox; // nsIFrame is implemented by nsIFrame and is not refcounted. + nsIGridPart* parent = GetParentGridPart(aBox, &parentBox); + if (parent) + return parent->GetGrid(parentBox, aIndex, this); + + return nullptr; +} + +nsMargin +nsGridRowLayout::GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) +{ + // get our parents margin + nsMargin margin(0,0,0,0); + nsIFrame* parent = nullptr; + nsIGridPart* part = GetParentGridPart(aBox, &parent); + if (part && parent) { + // if we are the first or last child walk upward and add margins. + + // make sure we check for a scrollbox + aBox = nsGrid::GetScrollBox(aBox); + + // see if we have a next to see if we are last + nsIFrame* next = aBox->GetNextBox(); + + // get the parent first child to see if we are first + nsIFrame* child = parent->GetChildBox(); + + margin = part->GetTotalMargin(parent, aIsHorizontal); + + // if first or last + if (child == aBox || next == nullptr) { + + // if it's not the first child remove the top margin + // we don't need it. + if (child != aBox) + { + if (aIsHorizontal) + margin.top = 0; + else + margin.left = 0; + } + + // if it's not the last child remove the bottom margin + // we don't need it. + if (next != nullptr) + { + if (aIsHorizontal) + margin.bottom = 0; + else + margin.right = 0; + } + + } + } + + // add ours to it. + nsMargin ourMargin; + aBox->GetMargin(ourMargin); + margin += ourMargin; + + return margin; +} + +NS_IMPL_ADDREF_INHERITED(nsGridRowLayout, nsBoxLayout) +NS_IMPL_RELEASE_INHERITED(nsGridRowLayout, nsBoxLayout) + +NS_INTERFACE_MAP_BEGIN(nsGridRowLayout) + NS_INTERFACE_MAP_ENTRY(nsIGridPart) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGridPart) +NS_INTERFACE_MAP_END_INHERITING(nsBoxLayout) diff --git a/layout/xul/grid/nsGridRowLayout.h b/layout/xul/grid/nsGridRowLayout.h new file mode 100644 index 000000000..187944fd3 --- /dev/null +++ b/layout/xul/grid/nsGridRowLayout.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Author: + Eric D Vaughan + +**/ + +#ifndef nsGridRowLayout_h___ +#define nsGridRowLayout_h___ + +#include "mozilla/Attributes.h" +#include "nsSprocketLayout.h" +#include "nsIGridPart.h" +class nsGridRowGroupLayout; +class nsGridLayout2; +class nsBoxLayoutState; +class nsGrid; + +/** + * A common base class for nsGridRowLeafLayout (the nsBoxLayout object + * for a grid row or column) and nsGridRowGroupLayout (the nsBoxLayout + * object for a grid row group or column group). + */ +// XXXldb This needs a name that indicates that it's a base class for +// both row and rows (row-group). +class nsGridRowLayout : public nsSprocketLayout, + public nsIGridPart +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + virtual nsGridRowGroupLayout* CastToRowGroupLayout() MOZ_OVERRIDE { return nullptr; } + virtual nsGridLayout2* CastToGridLayout() MOZ_OVERRIDE { return nullptr; } + virtual nsGrid* GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor=nullptr) MOZ_OVERRIDE; + virtual nsIGridPart* GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) MOZ_OVERRIDE; + virtual void ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aPrevBox, + const nsFrameList::Slice& aNewChildren) MOZ_OVERRIDE; + virtual void ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState, + const nsFrameList::Slice& aNewChildren) MOZ_OVERRIDE; + virtual void ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) MOZ_OVERRIDE; + virtual void ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) MOZ_OVERRIDE; + virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) MOZ_OVERRIDE; + + virtual nsIGridPart* AsGridPart() MOZ_OVERRIDE { return this; } + +protected: + virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState)=0; + + nsGridRowLayout(); +}; + +#endif + diff --git a/layout/xul/grid/nsGridRowLeafFrame.cpp b/layout/xul/grid/nsGridRowLeafFrame.cpp new file mode 100644 index 000000000..c418c0742 --- /dev/null +++ b/layout/xul/grid/nsGridRowLeafFrame.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsGridRowLeafFrame.h" +#include "nsGridRowLeafLayout.h" +#include "nsGridRow.h" +#include "nsBoxLayoutState.h" +#include "nsGridLayout2.h" + +already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout(); + +nsIFrame* +NS_NewGridRowLeafFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext) +{ + nsCOMPtr<nsBoxLayout> layout = NS_NewGridRowLeafLayout(); + if (!layout) { + return nullptr; + } + + return new (aPresShell) nsGridRowLeafFrame(aPresShell, aContext, false, + layout); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsGridRowLeafFrame) + +/* + * Our border and padding could be affected by our columns or rows. + * Let's go check it out. + */ +NS_IMETHODIMP +nsGridRowLeafFrame::GetBorderAndPadding(nsMargin& aBorderAndPadding) +{ + // if our columns have made our padding larger add it in. + nsresult rv = nsBoxFrame::GetBorderAndPadding(aBorderAndPadding); + + nsIGridPart* part = nsGrid::GetPartFromBox(this); + if (!part) + return rv; + + int32_t index = 0; + nsGrid* grid = part->GetGrid(this, &index); + + if (!grid) + return rv; + + bool isHorizontal = IsHorizontal(); + + nsBoxLayoutState state(PresContext()); + + int32_t firstIndex = 0; + int32_t lastIndex = 0; + nsGridRow* firstRow = nullptr; + nsGridRow* lastRow = nullptr; + grid->GetFirstAndLastRow(state, firstIndex, lastIndex, firstRow, lastRow, isHorizontal); + + // only the first and last rows can be affected. + if (firstRow && firstRow->GetBox() == this) { + + nscoord top = 0; + nscoord bottom = 0; + grid->GetRowOffsets(state, firstIndex, top, bottom, isHorizontal); + + if (isHorizontal) { + if (top > aBorderAndPadding.top) + aBorderAndPadding.top = top; + } else { + if (top > aBorderAndPadding.left) + aBorderAndPadding.left = top; + } + } + + if (lastRow && lastRow->GetBox() == this) { + + nscoord top = 0; + nscoord bottom = 0; + grid->GetRowOffsets(state, lastIndex, top, bottom, isHorizontal); + + if (isHorizontal) { + if (bottom > aBorderAndPadding.bottom) + aBorderAndPadding.bottom = bottom; + } else { + if (bottom > aBorderAndPadding.right) + aBorderAndPadding.right = bottom; + } + + } + + return rv; +} + + diff --git a/layout/xul/grid/nsGridRowLeafFrame.h b/layout/xul/grid/nsGridRowLeafFrame.h new file mode 100644 index 000000000..cd3defc61 --- /dev/null +++ b/layout/xul/grid/nsGridRowLeafFrame.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Eric D Vaughan + A frame that can have multiple children. Only one child may be displayed at one time. So the + can be flipped though like a deck of cards. + +**/ + +#ifndef nsGridRowLeafFrame_h___ +#define nsGridRowLeafFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +/** + * A frame representing a grid row (or column). Grid row (and column) + * elements are the children of row group (or column group) elements, + * and their children are placed one to a cell. + */ +// XXXldb This needs a better name that indicates that it's for any grid +// row. +class nsGridRowLeafFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewGridRowLeafFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + +#ifdef DEBUG + NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE + { + return MakeFrameName(NS_LITERAL_STRING("nsGridRowLeaf"), aResult); + } +#endif + + nsGridRowLeafFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + bool aIsRoot, + nsBoxLayout* aLayoutManager): + nsBoxFrame(aPresShell, aContext, aIsRoot, aLayoutManager) {} + + NS_IMETHOD GetBorderAndPadding(nsMargin& aBorderAndPadding) MOZ_OVERRIDE; + +}; // class nsGridRowLeafFrame + + + +#endif + diff --git a/layout/xul/grid/nsGridRowLeafLayout.cpp b/layout/xul/grid/nsGridRowLeafLayout.cpp new file mode 100644 index 000000000..94ca4b651 --- /dev/null +++ b/layout/xul/grid/nsGridRowLeafLayout.cpp @@ -0,0 +1,329 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsGridRowLeafLayout.h" +#include "nsGridRowGroupLayout.h" +#include "nsGridRow.h" +#include "nsBoxLayoutState.h" +#include "nsBox.h" +#include "nsIScrollableFrame.h" +#include "nsBoxFrame.h" +#include "nsGridLayout2.h" +#include <algorithm> + +already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout() +{ + nsRefPtr<nsBoxLayout> layout = new nsGridRowLeafLayout(); + return layout.forget(); +} + +nsGridRowLeafLayout::nsGridRowLeafLayout():nsGridRowLayout() +{ +} + +nsGridRowLeafLayout::~nsGridRowLeafLayout() +{ +} + +nsSize +nsGridRowLeafLayout::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsHorizontal(aBox); + + // If we are not in a grid. Then we just work like a box. But if we are in a grid + // ask the grid for our size. + if (!grid) { + return nsGridRowLayout::GetPrefSize(aBox, aState); + } + else { + return grid->GetPrefRowSize(aState, index, isHorizontal); + //AddBorderAndPadding(aBox, pref); + } +} + +nsSize +nsGridRowLeafLayout::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsHorizontal(aBox); + + if (!grid) + return nsGridRowLayout::GetMinSize(aBox, aState); + else { + nsSize minSize = grid->GetMinRowSize(aState, index, isHorizontal); + AddBorderAndPadding(aBox, minSize); + return minSize; + } +} + +nsSize +nsGridRowLeafLayout::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsHorizontal(aBox); + + if (!grid) + return nsGridRowLayout::GetMaxSize(aBox, aState); + else { + nsSize maxSize; + maxSize = grid->GetMaxRowSize(aState, index, isHorizontal); + AddBorderAndPadding(aBox, maxSize); + return maxSize; + } +} + +/** If a child is added or removed or changes size + */ +void +nsGridRowLeafLayout::ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsHorizontal(aBox); + + if (grid) + grid->CellAddedOrRemoved(aState, index, isHorizontal); +} + +void +nsGridRowLeafLayout::PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aState, nsBoxSize*& aBoxSizes, nscoord& aMinSize, nscoord& aMaxSize, int32_t& aFlexes) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsHorizontal(aBox); + + // Our base class SprocketLayout is giving us a chance to change the box sizes before layout + // If we are a row lets change the sizes to match our columns. If we are a column then do the opposite + // and make them match or rows. + if (grid) { + nsGridRow* column; + int32_t count = grid->GetColumnCount(isHorizontal); + nsBoxSize* start = nullptr; + nsBoxSize* last = nullptr; + nsBoxSize* current = nullptr; + nsIFrame* child = aBox->GetChildBox(); + for (int i=0; i < count; i++) + { + column = grid->GetColumnAt(i,isHorizontal); + + // make sure the value was computed before we use it. + // !isHorizontal is passed in to invert the behavior of these methods. + nscoord pref = + grid->GetPrefRowHeight(aState, i, !isHorizontal); // GetPrefColumnWidth + nscoord min = + grid->GetMinRowHeight(aState, i, !isHorizontal); // GetMinColumnWidth + nscoord max = + grid->GetMaxRowHeight(aState, i, !isHorizontal); // GetMaxColumnWidth + nscoord flex = + grid->GetRowFlex(aState, i, !isHorizontal); // GetColumnFlex + nscoord left = 0; + nscoord right = 0; + grid->GetRowOffsets(aState, i, left, right, !isHorizontal); // GetColumnOffsets + nsIFrame* box = column->GetBox(); + bool collapsed = false; + nscoord topMargin = column->mTopMargin; + nscoord bottomMargin = column->mBottomMargin; + + if (box) + collapsed = box->IsCollapsed(); + + pref = pref - (left + right); + if (pref < 0) + pref = 0; + + // if this is the first or last column. Take into account that + // our row could have a border that could affect our left or right + // padding from our columns. If the row has padding subtract it. + // would should always be able to garentee that our margin is smaller + // or equal to our left or right + int32_t firstIndex = 0; + int32_t lastIndex = 0; + nsGridRow* firstRow = nullptr; + nsGridRow* lastRow = nullptr; + grid->GetFirstAndLastRow(aState, firstIndex, lastIndex, firstRow, lastRow, !isHorizontal); + + if (i == firstIndex || i == lastIndex) { + nsMargin offset = GetTotalMargin(aBox, isHorizontal); + + nsMargin border(0,0,0,0); + // can't call GetBorderPadding we will get into recursion + aBox->GetBorder(border); + offset += border; + aBox->GetPadding(border); + offset += border; + + // subtract from out left and right + if (i == firstIndex) + { + if (isHorizontal) + left -= offset.left; + else + left -= offset.top; + } + + if (i == lastIndex) + { + if (isHorizontal) + right -= offset.right; + else + right -= offset.bottom; + } + } + + // initialize the box size here + max = std::max(min, max); + pref = nsBox::BoundsCheck(min, pref, max); + + current = new (aState) nsBoxSize(); + current->pref = pref; + current->min = min; + current->max = max; + current->flex = flex; + current->bogus = column->mIsBogus; + current->left = left + topMargin; + current->right = right + bottomMargin; + current->collapsed = collapsed; + + if (!start) { + start = current; + last = start; + } else { + last->next = current; + last = current; + } + + if (child && !column->mIsBogus) + child = child->GetNextBox(); + + } + aBoxSizes = start; + } + + nsSprocketLayout::PopulateBoxSizes(aBox, aState, aBoxSizes, aMinSize, aMaxSize, aFlexes); +} + +void +nsGridRowLeafLayout::ComputeChildSizes(nsIFrame* aBox, + nsBoxLayoutState& aState, + nscoord& aGivenSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize*& aComputedBoxSizes) +{ + // see if we are in a scrollable frame. If we are then there could be scrollbars present + // if so we need to subtract them out to make sure our columns line up. + if (aBox) { + bool isHorizontal = aBox->IsHorizontal(); + + // go up the parent chain looking for scrollframes + nscoord diff = 0; + nsIFrame* parentBox; + (void)GetParentGridPart(aBox, &parentBox); + while (parentBox) { + nsIFrame* scrollbox = nsGrid::GetScrollBox(parentBox); + nsIScrollableFrame *scrollable = do_QueryFrame(scrollbox); + if (scrollable) { + // Don't call GetActualScrollbarSizes here because it's not safe + // to call that while we're reflowing the contents of the scrollframe, + // which we are here. + nsMargin scrollbarSizes = scrollable->GetDesiredScrollbarSizes(&aState); + uint32_t visible = scrollable->GetScrollbarVisibility(); + + if (isHorizontal && (visible & nsIScrollableFrame::VERTICAL)) { + diff += scrollbarSizes.left + scrollbarSizes.right; + } else if (!isHorizontal && (visible & nsIScrollableFrame::HORIZONTAL)) { + diff += scrollbarSizes.top + scrollbarSizes.bottom; + } + } + + (void)GetParentGridPart(parentBox, &parentBox); + } + + if (diff > 0) { + aGivenSize += diff; + + nsSprocketLayout::ComputeChildSizes(aBox, aState, aGivenSize, aBoxSizes, aComputedBoxSizes); + + aGivenSize -= diff; + + nsComputedBoxSize* s = aComputedBoxSizes; + nsComputedBoxSize* last = aComputedBoxSizes; + while(s) + { + last = s; + s = s->next; + } + + if (last) + last->size -= diff; + + return; + } + } + + nsSprocketLayout::ComputeChildSizes(aBox, aState, aGivenSize, aBoxSizes, aComputedBoxSizes); + +} + +NS_IMETHODIMP +nsGridRowLeafLayout::Layout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + return nsGridRowLayout::Layout(aBox, aBoxLayoutState); +} + +void +nsGridRowLeafLayout::DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + if (aBox) { + // mark us dirty + // XXXldb We probably don't want to walk up the ancestor chain + // calling MarkIntrinsicWidthsDirty for every row. + aState.PresShell()->FrameNeedsReflow(aBox, nsIPresShell::eTreeChange, + NS_FRAME_IS_DIRTY); + } +} + +void +nsGridRowLeafLayout::CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) +{ + if (aBox) { + nsIFrame* child = aBox->GetChildBox(); + + // count the children + int32_t columnCount = 0; + while(child) { + child = child->GetNextBox(); + columnCount++; + } + + // if our count is greater than the current column count + if (columnCount > aComputedColumnCount) + aComputedColumnCount = columnCount; + + aRowCount++; + } +} + +int32_t +nsGridRowLeafLayout::BuildRows(nsIFrame* aBox, nsGridRow* aRows) +{ + if (aBox) { + aRows[0].Init(aBox, false); + return 1; + } + + return 0; +} + diff --git a/layout/xul/grid/nsGridRowLeafLayout.h b/layout/xul/grid/nsGridRowLeafLayout.h new file mode 100644 index 000000000..9f35874e4 --- /dev/null +++ b/layout/xul/grid/nsGridRowLeafLayout.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + + Author: + Eric D Vaughan + +**/ + +#ifndef nsGridRowLeafLayout_h___ +#define nsGridRowLeafLayout_h___ + +#include "mozilla/Attributes.h" +#include "nsGridRowLayout.h" +#include "nsCOMPtr.h" + +/** + * The nsBoxLayout implementation for nsGridRowLeafFrame. + */ +// XXXldb This needs a better name that indicates that it's for any grid +// row. +class nsGridRowLeafLayout : public nsGridRowLayout +{ +public: + + friend already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout(); + + virtual nsSize GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual nsSize GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState) MOZ_OVERRIDE; + NS_IMETHOD Layout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) MOZ_OVERRIDE; + virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) MOZ_OVERRIDE; + virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) MOZ_OVERRIDE; + virtual Type GetType() MOZ_OVERRIDE { return eRowLeaf; } + +protected: + + virtual void PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState, + nsBoxSize*& aBoxSizes, nscoord& aMinSize, + nscoord& aMaxSize, int32_t& aFlexes) MOZ_OVERRIDE; + virtual void ComputeChildSizes(nsIFrame* aBox, + nsBoxLayoutState& aState, + nscoord& aGivenSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize*& aComputedBoxSizes) MOZ_OVERRIDE; + + + nsGridRowLeafLayout(); + virtual ~nsGridRowLeafLayout(); + //virtual void AddBorderAndPadding(nsIFrame* aBox, nsSize& aSize); + +private: + +}; // class nsGridRowLeafLayout + +#endif + diff --git a/layout/xul/grid/nsIGridPart.h b/layout/xul/grid/nsIGridPart.h new file mode 100644 index 000000000..202429de6 --- /dev/null +++ b/layout/xul/grid/nsIGridPart.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIGridPart_h___ +#define nsIGridPart_h___ + +#include "nsISupports.h" + +class nsGridRowGroupLayout; +class nsGrid; +class nsGridRowLayout; +class nsGridRow; +class nsGridLayout2; + +// 07373ed7-e947-4a5e-b36c-69f7c195677b +#define NS_IGRIDPART_IID \ +{ 0x07373ed7, 0xe947, 0x4a5e, \ + { 0xb3, 0x6c, 0x69, 0xf7, 0xc1, 0x95, 0x67, 0x7b } } + +/** + * An additional interface implemented by nsBoxLayout implementations + * for parts of a grid (excluding cells, which are not special). + */ +class nsIGridPart : public nsISupports { + +public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IGRIDPART_IID) + + virtual nsGridRowGroupLayout* CastToRowGroupLayout()=0; + virtual nsGridLayout2* CastToGridLayout()=0; + + /** + * @param aBox [IN] The other half of the |this| parameter, i.e., the box + * whose layout manager is |this|. + * @param aIndex [INOUT] For callers not setting aRequestor, the value + * pointed to by aIndex is incremented by the index + * of the row (aBox) within its row group; if aBox + * is not a row/column, it is untouched. + * The implementation does this by doing the aIndex + * incrementing in the call to the parent row group + * when aRequestor is non-null. + * @param aRequestor [IN] Non-null if and only if this is a recursive + * call from the GetGrid method on a child grid part, + * in which case it is a pointer to that grid part. + * (This may only be non-null for row groups and + * grids.) + * @return The grid of which aBox (a row, row group, or grid) is a part. + */ + virtual nsGrid* GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor=nullptr)=0; + + /** + * @param aBox [IN] The other half of the |this| parameter, i.e., the box + * whose layout manager is |this|. + * @param aParentBox [OUT] The box representing the next level up in + * the grid (i.e., row group for a row, grid for a + * row group). + * @returns The layout manager for aParentBox. + */ + virtual nsIGridPart* GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) = 0; + + /** + * @param aBox [IN] The other half of the |this| parameter, i.e., the box + * whose layout manager is |this|. + * @param aRowCount [INOUT] Row count + * @param aComputedColumnCount [INOUT] Column count + */ + virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount)=0; + virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState)=0; + virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows)=0; + virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal)=0; + virtual int32_t GetRowCount() { return 1; } + + /** + * Return the level of the grid hierarchy this grid part represents. + */ + enum Type { eGrid, eRowGroup, eRowLeaf }; + virtual Type GetType()=0; + + /** + * Return whether this grid part is an appropriate parent for the argument. + */ + bool CanContain(nsIGridPart* aPossibleChild) { + Type thisType = GetType(), childType = aPossibleChild->GetType(); + return thisType + 1 == childType || (thisType == eRowGroup && childType == eRowGroup); + } + +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIGridPart, NS_IGRIDPART_IID) + +#endif + diff --git a/layout/xul/grid/reftests/column-sizing-1-ref.xul b/layout/xul/grid/reftests/column-sizing-1-ref.xul new file mode 100644 index 000000000..df0113083 --- /dev/null +++ b/layout/xul/grid/reftests/column-sizing-1-ref.xul @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <hbox> + <vbox style="background:aqua"> + <label value="Left" /> + </vbox> + <vbox style="background:yellow"> + <textbox value="Right" /> + </vbox> + </hbox> +</window> diff --git a/layout/xul/grid/reftests/column-sizing-1.xul b/layout/xul/grid/reftests/column-sizing-1.xul new file mode 100644 index 000000000..2a94569ba --- /dev/null +++ b/layout/xul/grid/reftests/column-sizing-1.xul @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <grid> + <columns> + <column style="background:aqua"> + <label value="Left" /> + </column> + <column style="background:yellow"> + <textbox value="Right" /> + </column> + </columns> + </grid> +</window> diff --git a/layout/xul/grid/reftests/not-full-basic-ref.xhtml b/layout/xul/grid/reftests/not-full-basic-ref.xhtml new file mode 100644 index 000000000..cd233585a --- /dev/null +++ b/layout/xul/grid/reftests/not-full-basic-ref.xhtml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>XUL Grid Test</title> + <style type="text/css"> + html { background: black; } + html, body { margin: 0; padding: 0; height: 100%; } + div { position: absolute; } + </style> +</head> +<body> + +<div style="background: rgb(0, 102, 153); + top: 0px; height: 200px; left: 0px; width: 200px;" /> +<div style="background: rgb(0, 255, 0); + top: 200px; bottom: 0px; left: 0px; width: 100px;" /> +<div style="background: rgb(0, 255, 0); + top: 200px; height: 100px; left: 100px; width: 100px;" /> +<div style="background: rgb(0, 0, 153); + top: 0px; height: 100px; left: 200px; right: 0px;" /> +<div style="background: rgb(0, 0, 153); + top: 100px; height: 100px; left: 200px; width: 100px;" /> + +</body> +</html> diff --git a/layout/xul/grid/reftests/not-full-basic.xul b/layout/xul/grid/reftests/not-full-basic.xul new file mode 100644 index 000000000..5c7fa9123 --- /dev/null +++ b/layout/xul/grid/reftests/not-full-basic.xul @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + ]]></style> + <grid flex="1"> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/not-full-grid-pack-align.xul b/layout/xul/grid/reftests/not-full-grid-pack-align.xul new file mode 100644 index 000000000..3fe6a95cb --- /dev/null +++ b/layout/xul/grid/reftests/not-full-grid-pack-align.xul @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + ]]></style> + <!-- align and pack should be no-ops on grid element (not on columns/rows) --> + <grid flex="1" align="start" pack="end"> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/not-full-row-group-align-ref.xhtml b/layout/xul/grid/reftests/not-full-row-group-align-ref.xhtml new file mode 100644 index 000000000..abef67f87 --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-group-align-ref.xhtml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>XUL Grid Test</title> + <style type="text/css"> + html { background: black; } + html, body { margin: 0; padding: 0; height: 100%; } + div { position: absolute; } + </style> +</head> +<body> + +<div style="background: rgb(0, 102, 153); + top: 100px; height: 100px; left: 100px; width: 100px;" /> +<div style="background: rgb(0, 255, 0); + top: 0px; height: 100px; left: 100px; width: 100px;" /> +<div style="background: rgb(0, 255, 0); + top: 200px; height: 100px; left: 100px; width: 100px;" /> +<div style="background: rgb(0, 0, 153); + top: 100px; height: 100px; left: 0px; width: 100px;" /> +<div style="background: rgb(0, 0, 153); + top: 100px; height: 100px; left: 200px; width: 100px;" /> + +</body> +</html> diff --git a/layout/xul/grid/reftests/not-full-row-group-align.xul b/layout/xul/grid/reftests/not-full-row-group-align.xul new file mode 100644 index 000000000..0037d9fb8 --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-group-align.xul @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + ]]></style> + <grid flex="1"> + <!-- does anybody actually *want* the way columns align="start" behaves here? --> + <columns align="start"> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows align="start"> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/not-full-row-group-direction-ref.xhtml b/layout/xul/grid/reftests/not-full-row-group-direction-ref.xhtml new file mode 100644 index 000000000..b2a92b07b --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-group-direction-ref.xhtml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>XUL Grid Test</title> + <style type="text/css"> + html { background: black; } + html, body { margin: 0; padding: 0; height: 100%; } + div { position: absolute; } + </style> +</head> +<body> + +<div style="background: rgb(0, 102, 153); + bottom: 0px; height: 100px; right: 0px; width: 100px;" /> +<div style="background: rgb(0, 0, 153); + bottom: 0px; height: 100px; left: 0px; right: 100px;" /> +<div style="background: rgb(0, 0, 153); + bottom: 100px; height: 100px; left: 0px; width: 300px;" /> +<div style="background: rgb(0, 255, 0); + top: 0px; bottom: 100px; right: 0px; width: 100px;" /> +<div style="background: rgb(0, 255, 0); + top: 0px; height: 300px; right: 100px; width: 100px;" /> + +</body> +</html> diff --git a/layout/xul/grid/reftests/not-full-row-group-direction.xul b/layout/xul/grid/reftests/not-full-row-group-direction.xul new file mode 100644 index 000000000..c38db40a5 --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-group-direction.xul @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + rows, columns { -moz-box-direction: reverse; } + ]]></style> + <grid flex="1"> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/not-full-row-group-pack-ref.xhtml b/layout/xul/grid/reftests/not-full-row-group-pack-ref.xhtml new file mode 100644 index 000000000..9232f6ba4 --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-group-pack-ref.xhtml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>XUL Grid Test</title> + <style type="text/css"> + html { background: black; } + html, body { margin: 0; padding: 0; height: 100%; } + div { position: absolute; } + </style> +</head> +<body> + +<div style="background: rgb(0, 102, 153); + bottom: 200px; height: 100px; right: 200px; width: 100px;" /> +<div style="background: rgb(0, 0, 153); + bottom: 200px; height: 100px; left: 0px; right: 300px;" /> +<div style="background: rgb(0, 0, 153); + bottom: 100px; height: 100px; left: 0px; width: 300px;" /> +<div style="background: rgb(0, 0, 153); + bottom: 200px; height: 100px; right: 0px; width: 200px;" /> +<div style="background: rgb(0, 255, 0); + top: 0px; bottom: 300px; right: 200px; width: 100px;" /> +<div style="background: rgb(0, 255, 0); + top: 0px; height: 300px; right: 100px; width: 100px;" /> +<div style="background: rgb(0, 255, 0); + bottom: 0px; height: 200px; right: 200px; width: 100px;" /> + +</body> +</html> diff --git a/layout/xul/grid/reftests/not-full-row-group-pack.xul b/layout/xul/grid/reftests/not-full-row-group-pack.xul new file mode 100644 index 000000000..bb8f650ae --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-group-pack.xul @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + rows, columns { -moz-box-pack: end; } + ]]></style> + <grid flex="1"> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/not-full-row-leaf-align.xul b/layout/xul/grid/reftests/not-full-row-leaf-align.xul new file mode 100644 index 000000000..806514ebd --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-leaf-align.xul @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + row, column { -moz-box-align: start; } + ]]></style> + <grid flex="1"> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/not-full-row-leaf-direction.xul b/layout/xul/grid/reftests/not-full-row-leaf-direction.xul new file mode 100644 index 000000000..17c3a6585 --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-leaf-direction.xul @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + row, column { -moz-box-direction: reverse; } + ]]></style> + <grid flex="1"> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/not-full-row-leaf-pack-ref.xhtml b/layout/xul/grid/reftests/not-full-row-leaf-pack-ref.xhtml new file mode 100644 index 000000000..30635313a --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-leaf-pack-ref.xhtml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>XUL Grid Test</title> + <style type="text/css"> + html { background: black; } + html, body { margin: 0; padding: 0; height: 100%; } + div { position: absolute; } + </style> +</head> +<body> + +<div style="background: rgb(0, 102, 153); + top: 0px; height: 100px; left: 0px; width: 100px;" /> +<div style="background: rgb(0, 255, 0); + top: 100px; bottom: 0px; left: 0px; width: 100px;" /> +<div style="background: rgb(0, 255, 0); + bottom: 0px; height: 300px; left: 100px; width: 100px;" /> +<div style="background: rgb(0, 0, 153); + top: 0px; height: 100px; left: 100px; right: 0px;" /> +<div style="background: rgb(0, 0, 153); + top: 100px; height: 100px; right: 0px; width: 300px;" /> + +</body> +</html> diff --git a/layout/xul/grid/reftests/not-full-row-leaf-pack.xul b/layout/xul/grid/reftests/not-full-row-leaf-pack.xul new file mode 100644 index 000000000..8f353c764 --- /dev/null +++ b/layout/xul/grid/reftests/not-full-row-leaf-pack.xul @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + row, column { -moz-box-pack: end; } + ]]></style> + <grid flex="1"> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/reftest.list b/layout/xul/grid/reftests/reftest.list new file mode 100644 index 000000000..80f7925a0 --- /dev/null +++ b/layout/xul/grid/reftests/reftest.list @@ -0,0 +1,18 @@ +== row-sizing-1.xul row-sizing-1-ref.xul +== column-sizing-1.xul column-sizing-1-ref.xul +== row-or-column-sizing-1.xul row-or-column-sizing-2.xul +== row-or-column-sizing-1.xul row-or-column-sizing-3.xul +== row-or-column-sizing-1.xul row-or-column-sizing-4.xul +== z-order-1.xul z-order-1-ref.xul +== z-order-2.xul z-order-2-ref.xul +== not-full-basic.xul not-full-basic-ref.xhtml +== not-full-grid-pack-align.xul not-full-basic-ref.xhtml +== not-full-row-group-align.xul not-full-row-group-align-ref.xhtml # does anyone want/need this behavior? +== not-full-row-group-pack.xul not-full-row-group-pack-ref.xhtml +== not-full-row-group-direction.xul not-full-row-group-direction-ref.xhtml +== not-full-row-leaf-align.xul not-full-basic-ref.xhtml +== not-full-row-leaf-pack.xul not-full-row-leaf-pack-ref.xhtml +== not-full-row-leaf-direction.xul not-full-row-leaf-pack-ref.xhtml +skip-if(B2G) fails-if(Android&&browserIsRemote) == scrollable-columns.xul scrollable-columns-ref.xhtml # bug 650597, 732569 +fails == scrollable-rows.xul scrollable-rows-ref.xhtml +== sizing-2d.xul sizing-2d-ref.xul diff --git a/layout/xul/grid/reftests/row-or-column-sizing-1.xul b/layout/xul/grid/reftests/row-or-column-sizing-1.xul new file mode 100644 index 000000000..6c64eef18 --- /dev/null +++ b/layout/xul/grid/reftests/row-or-column-sizing-1.xul @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <grid> + <columns> + <column /> + <column /> + </columns> + <rows> + <row> + <hbox /> + <label value="Upper right" /> + </row> + <row> + <textbox value="Lower left" /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/row-or-column-sizing-2.xul b/layout/xul/grid/reftests/row-or-column-sizing-2.xul new file mode 100644 index 000000000..008f82fd5 --- /dev/null +++ b/layout/xul/grid/reftests/row-or-column-sizing-2.xul @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <grid> + <columns> + <column> + <hbox /> + <textbox value="Lower left" /> + </column> + <column> + <label value="Upper right" /> + <hbox /> + </column> + </columns> + <rows> + <row /> + <row /> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/row-or-column-sizing-3.xul b/layout/xul/grid/reftests/row-or-column-sizing-3.xul new file mode 100644 index 000000000..1e8e55c29 --- /dev/null +++ b/layout/xul/grid/reftests/row-or-column-sizing-3.xul @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <grid> + <columns> + <column> + <hbox /> + <hbox /> + </column> + <column> + <label value="Upper right" /> + <hbox /> + </column> + </columns> + <rows> + <row> + <hbox /> + <hbox /> + </row> + <row> + <textbox value="Lower left" /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/row-or-column-sizing-4.xul b/layout/xul/grid/reftests/row-or-column-sizing-4.xul new file mode 100644 index 000000000..5a826fd84 --- /dev/null +++ b/layout/xul/grid/reftests/row-or-column-sizing-4.xul @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <grid> + <columns> + <column> + <hbox /> + <textbox value="Lower left" /> + </column> + <column> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row> + <hbox /> + <label value="Upper right" /> + </row> + <row> + <hbox /> + <hbox /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/row-sizing-1-ref.xul b/layout/xul/grid/reftests/row-sizing-1-ref.xul new file mode 100644 index 000000000..b35719052 --- /dev/null +++ b/layout/xul/grid/reftests/row-sizing-1-ref.xul @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + orient="horizontal" + title="XUL Grid Test"> + <vbox> + <hbox style="background:aqua"> + <label value="Top" /> + </hbox> + <hbox style="background:yellow"> + <textbox value="Bottom" /> + </hbox> + </vbox> +</window> diff --git a/layout/xul/grid/reftests/row-sizing-1.xul b/layout/xul/grid/reftests/row-sizing-1.xul new file mode 100644 index 000000000..0455b8da4 --- /dev/null +++ b/layout/xul/grid/reftests/row-sizing-1.xul @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + orient="horizontal" + title="XUL Grid Test"> + <grid> + <rows> + <row style="background:aqua"> + <label value="Top" /> + </row> + <row style="background:yellow"> + <textbox value="Bottom" /> + </row> + </rows> + </grid> +</window> diff --git a/layout/xul/grid/reftests/scrollable-columns-ref.xhtml b/layout/xul/grid/reftests/scrollable-columns-ref.xhtml new file mode 100644 index 000000000..698c5a036 --- /dev/null +++ b/layout/xul/grid/reftests/scrollable-columns-ref.xhtml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>XUL Grid Test</title> + <style type="text/css"> + html { background: black; } + html, body { margin: 0; padding: 0; height: 100%; } + div { position: absolute; } + </style> +</head> +<body> + +<div style="background: rgb(0, 102, 153); + top: 0px; height: 200px; left: 0px; width: 200px;" /> +<div style="background: rgb(0, 255, 0); overflow: auto; + top: 200px; height: 100px; left: 0px; width: 200px;"> + <div style="width: 300px; height: 50px" /> +</div> +<div style="background: rgb(0, 0, 153); + top: 100px; height: 100px; left: 200px; width: 100px;" /> + +</body> +</html> diff --git a/layout/xul/grid/reftests/scrollable-columns.xul b/layout/xul/grid/reftests/scrollable-columns.xul new file mode 100644 index 000000000..661c4412f --- /dev/null +++ b/layout/xul/grid/reftests/scrollable-columns.xul @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + grid { width: 200px; height: 200px; } + columns { overflow: auto; } + ]]></style> + <hbox> + <grid> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/reftests/scrollable-rows-ref.xhtml b/layout/xul/grid/reftests/scrollable-rows-ref.xhtml new file mode 100644 index 000000000..6b5b95f02 --- /dev/null +++ b/layout/xul/grid/reftests/scrollable-rows-ref.xhtml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>XUL Grid Test</title> + <style type="text/css"> + html { background: black; } + html, body { margin: 0; padding: 0; height: 100%; } + div { position: absolute; } + </style> +</head> +<body> + +<div style="background: rgb(0, 102, 153); + top: 0px; height: 200px; left: 0px; width: 200px;" /> +<div style="background: rgb(0, 0, 153); overflow: auto; + top: 0px; height: 200px; left: 200px; width: 100px;"> + <div style="width: 50px; height: 300px" /> +</div> +<div style="background: rgb(0, 255, 0); + top: 200px; height: 100px; left: 100px; width: 100px;" /> + +</body> +</html> diff --git a/layout/xul/grid/reftests/scrollable-rows.xul b/layout/xul/grid/reftests/scrollable-rows.xul new file mode 100644 index 000000000..9fa1f82c5 --- /dev/null +++ b/layout/xul/grid/reftests/scrollable-rows.xul @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + grid { width: 200px; height: 200px; } + rows { overflow: auto; } + ]]></style> + <hbox> + <grid> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/reftests/sizing-2d-ref.xul b/layout/xul/grid/reftests/sizing-2d-ref.xul new file mode 100644 index 000000000..a3bc4ca73 --- /dev/null +++ b/layout/xul/grid/reftests/sizing-2d-ref.xul @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" align="start"> + <hbox> + <box style="background:aqua; width: 50px; height: 100px" /> + <box style="background:fuchsia; width: 100px; height: 100px" /> + </hbox> + <hbox> + <box style="background:yellow; width: 50px; height: 75px" /> + <box style="background:blue; width: 100px; height: 75px" /> + </hbox> +</window> diff --git a/layout/xul/grid/reftests/sizing-2d.xul b/layout/xul/grid/reftests/sizing-2d.xul new file mode 100644 index 000000000..7868f9eca --- /dev/null +++ b/layout/xul/grid/reftests/sizing-2d.xul @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" align="start"> + <grid> + <rows> + <row> + <box style="width: 25px; height: 25px" /> + <box /> + </row> + <row> + <box /> + <box style="width: 75px; height: 75px" /> + </row> + </rows> + <columns> + <column> + <box style="background: aqua" /> + <box style="background: yellow; width: 50px; height: 50px" /> + </column> + <column> + <box style="background: fuchsia; width: 100px; height: 100px" /> + <box style="background: blue" /> + </column> + </columns> + </grid> +</window> diff --git a/layout/xul/grid/reftests/z-order-1-ref.xul b/layout/xul/grid/reftests/z-order-1-ref.xul new file mode 100644 index 000000000..198c4e6c6 --- /dev/null +++ b/layout/xul/grid/reftests/z-order-1-ref.xul @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + ]]></style> + <hbox> + <grid> + <rows> + <row> + <hbox style="background: rgb(0, 102, 153)" /> + <hbox style="background: rgb(0, 102, 153)" /> + <hbox style="background: rgb(0, 0, 153)" /> + </row> + <row> + <hbox style="background: rgb(0, 102, 153)" /> + <hbox style="background: rgb(0, 102, 153)" /> + <hbox style="background: rgb(0, 0, 153)" /> + </row> + <row> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 0, 0)" /> + </row> + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/reftests/z-order-1.xul b/layout/xul/grid/reftests/z-order-1.xul new file mode 100644 index 000000000..d38ef9f4a --- /dev/null +++ b/layout/xul/grid/reftests/z-order-1.xul @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + ]]></style> + <hbox> + <grid> + <columns> + <column style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + <rows> + <row style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/reftests/z-order-2-ref.xul b/layout/xul/grid/reftests/z-order-2-ref.xul new file mode 100644 index 000000000..5b0793d6d --- /dev/null +++ b/layout/xul/grid/reftests/z-order-2-ref.xul @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + ]]></style> + <hbox> + <grid> + <rows> + <row> + <hbox style="background: rgb(0, 102, 153)" /> + <hbox style="background: rgb(0, 102, 153)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </row> + <row> + <hbox style="background: rgb(0, 102, 153)" /> + <hbox style="background: rgb(0, 102, 153)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </row> + <row> + <hbox style="background: rgb(0, 0, 153)" /> + <hbox style="background: rgb(0, 0, 153)" /> + <hbox style="background: rgb(0, 0, 0)" /> + </row> + </rows> + </grid> + </hbox> +</window> diff --git a/layout/xul/grid/reftests/z-order-2.xul b/layout/xul/grid/reftests/z-order-2.xul new file mode 100644 index 000000000..b2c270d6b --- /dev/null +++ b/layout/xul/grid/reftests/z-order-2.xul @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<!DOCTYPE window> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL Grid Test"> + <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ + window { background: black; } + hbox { height: 100px; width: 100px; } + ]]></style> + <hbox> + <grid> + <rows> + <row style="background: rgb(0, 255, 0)"> + <hbox /> + <hbox /> + <hbox /> + </row> + <row> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + <hbox style="background: rgb(0, 255, 0)" /> + </row> + <row> + <hbox /> + <hbox /> + <hbox /> + </row> + </rows> + <columns> + <column style="background: rgba(0, 0, 255, 0.6)"> + <hbox /> + <hbox /> + <hbox /> + </column> + <column> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + <hbox style="background: rgba(0, 0, 255, 0.6)" /> + </column> + <column> + <hbox /> + <hbox /> + <hbox /> + </column> + </columns> + </grid> + </hbox> +</window> diff --git a/layout/xul/test/Makefile.in b/layout/xul/test/Makefile.in new file mode 100644 index 000000000..df82e07ee --- /dev/null +++ b/layout/xul/test/Makefile.in @@ -0,0 +1,34 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = @relativesrcdir@ + +include $(DEPTH)/config/autoconf.mk + +MOCHITEST_FILES =\ + test_bug386386.html \ + test_bug394800.xhtml \ + test_bug563416.html \ + $(NULL) + +MOCHITEST_CHROME_FILES = \ + test_bug159346.xul \ + test_bug372685.xul \ + test_bug398982-1.xul \ + test_bug398982-2.xul \ + test_bug703150.xul \ + $(NULL) + +MOCHITEST_BROWSER_FILES = \ + browser_bug685470.js \ + browser_bug703210.js \ + browser_bug706743.js \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/layout/xul/test/browser_bug685470.js b/layout/xul/test/browser_bug685470.js new file mode 100644 index 000000000..6559f25de --- /dev/null +++ b/layout/xul/test/browser_bug685470.js @@ -0,0 +1,51 @@ +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + + SpecialPowers.setIntPref("ui.tooltipDelay", 0); + + var popup = false; + var doc; + var win; + var p1; + + let onPopupShown = function(aEvent) { + popup = true; + } + + // test that a mouse click prior to the tooltip appearing blocks it + let runTest = function() { + EventUtils.synthesizeMouseAtCenter(p1, { type: "mousemove" }, win); + EventUtils.sendMouseEvent({type:'mousedown'}, p1, win); + EventUtils.sendMouseEvent({type:'mouseup'}, p1, win); + + setTimeout(function() { + is(popup, false, "shouldn't get tooltip after click"); + + document.removeEventListener("popupshown", onPopupShown, true); + SpecialPowers.clearUserPref("ui.tooltipDelay"); + + gBrowser.removeCurrentTab(); + finish(); + }, 200); + } + + let onLoad = function (aEvent) { + doc = gBrowser.contentDocument; + win = gBrowser.contentWindow; + p1 = doc.getElementById("p1"); + + document.addEventListener("popupshown", onPopupShown, true); + + runTest(); + } + + gBrowser.selectedBrowser.addEventListener("load", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("load", loadListener, true); + setTimeout(onLoad, 0); + }, true); + + content.location = "data:text/html," + + "<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>"; +} diff --git a/layout/xul/test/browser_bug703210.js b/layout/xul/test/browser_bug703210.js new file mode 100644 index 000000000..c6a61e7ba --- /dev/null +++ b/layout/xul/test/browser_bug703210.js @@ -0,0 +1,72 @@ +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + + SpecialPowers.setIntPref("ui.tooltipDelay", 0); + + let doStopPropagation = function (aEvent) + { + aEvent.stopPropagation(); + } + + let onPopupShown = function (aEvent) + { + is(aEvent.originalTarget.localName, "tooltip", "tooltip is showing"); + + let doc = gBrowser.contentDocument; + let win = gBrowser.contentWindow; + let p2 = doc.getElementById("p2"); + setTimeout(function () { + EventUtils.synthesizeMouseAtCenter(p2, { type: "mousemove" }, win); }, 0); + } + + let onPopupHiding = function (aEvent) + { + is(aEvent.originalTarget.localName, "tooltip", "tooltip is hiding"); + + let doc = gBrowser.contentDocument; + + doc.removeEventListener("mousemove", doStopPropagation, true); + doc.removeEventListener("mouseenter", doStopPropagation, true); + doc.removeEventListener("mouseleave", doStopPropagation, true); + doc.removeEventListener("mouseover", doStopPropagation, true); + doc.removeEventListener("mouseout", doStopPropagation, true); + document.removeEventListener("popupshown", onPopupShown, true); + document.removeEventListener("popuphiding", onPopupHiding, true); + + SpecialPowers.clearUserPref("ui.tooltipDelay"); + + gBrowser.removeCurrentTab(); + finish(); + } + + let onLoad = function (aEvent) + { + let doc = gBrowser.contentDocument; + let win = gBrowser.contentWindow; + let p1 = doc.getElementById("p1"); + let p2 = doc.getElementById("p2"); + + EventUtils.synthesizeMouseAtCenter(p2, { type: "mousemove" }, win); + + doc.addEventListener("mousemove", doStopPropagation, true); + doc.addEventListener("mouseenter", doStopPropagation, true); + doc.addEventListener("mouseleave", doStopPropagation, true); + doc.addEventListener("mouseover", doStopPropagation, true); + doc.addEventListener("mouseout", doStopPropagation, true); + document.addEventListener("popupshown", onPopupShown, true); + document.addEventListener("popuphiding", onPopupHiding, true); + + EventUtils.synthesizeMouseAtCenter(p1, { type: "mousemove" }, win); + } + + gBrowser.selectedBrowser.addEventListener("load", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("load", loadListener, true); + setTimeout(onLoad, 0); + }, true); + + content.location = "data:text/html," + + "<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>" + + "<p id=\"p2\">This paragraph doesn't have tooltip.</p>"; +} diff --git a/layout/xul/test/browser_bug706743.js b/layout/xul/test/browser_bug706743.js new file mode 100644 index 000000000..6bb3ccce7 --- /dev/null +++ b/layout/xul/test/browser_bug706743.js @@ -0,0 +1,154 @@ +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + + SpecialPowers.setIntPref("ui.tooltipDelay", 0); + + let target; + let doc; + let win; + let callbackOnPopupShown; + let callbackOnPopupHidden; + let dragService = Components.classes["@mozilla.org/widget/dragservice;1"]. + getService(Components.interfaces.nsIDragService); + + let onPopupHidden = function (aEvent) + { + if (aEvent.originalTarget.localName != "tooltip") { + return; + } + if (callbackOnPopupHidden) { + setTimeout(callbackOnPopupHidden, 0); + } + } + + let onPopupShown = function (aEvent) + { + if (aEvent.originalTarget.localName != "tooltip") { + return; + } + if (callbackOnPopupShown) { + setTimeout(callbackOnPopupShown, 0); + } + } + + let finishTest = function () + { + document.removeEventListener("popupshown", onPopupShown, true); + document.removeEventListener("popuphidden", onPopupHidden, true); + + SpecialPowers.clearUserPref("ui.tooltipDelay"); + + gBrowser.removeCurrentTab(); + finish(); + } + + let testHideTooltipByMouseDown = function () + { + callbackOnPopupShown = function () { + callbackOnPopupShown = null; + ok(true, "tooltip is shown before testing mousedown"); + + // hide tooltip by mousemove to outside. + callbackOnPopupHidden = function () { + callbackOnPopupHidden = null; + ok(true, "The tooltip is hidden by mousedown"); + + EventUtils.synthesizeMouse(target, 5, 15, { type: "mouseup" }, win); + EventUtils.synthesizeMouse(target, -5, 15, { type: "mousemove" }, win); + + setTimeout(finishTest, 0); + } + EventUtils.synthesizeMouse(target, 5, 15, { type: "mousedown" }, win); + } + EventUtils.synthesizeMouse(target, 5, 15, { type: "mousemove" }, win); + } + + let testShowTooltipAgain = function () + { + // If tooltip listener used a flag for managing D&D state, we would need + // to test if the tooltip is shown after drag. + callbackOnPopupShown = function () { + callbackOnPopupShown = null; + ok(true, "tooltip is shown after drag"); + + // hide tooltip by mousemove to outside. + callbackOnPopupHidden = function () { + callbackOnPopupHidden = null; + ok(true, "The tooltip is hidden again"); + + setTimeout(testHideTooltipByMouseDown, 0); + } + EventUtils.synthesizeMouse(target, -5, 15, { type: "mousemove" }, win); + } + EventUtils.synthesizeMouse(target, 5, 15, { type: "mousemove" }, win); + } + + let testDuringDnD = function () + { + // mousemove into the target and start drag by emulation via nsIDragService. + // Note that on some platforms, we cannot actually start the drag by + // synthesized events. E.g., Windows waits an actual mousemove event after + // dragstart. + callbackOnPopupShown = function () { + callbackOnPopupShown = null; + ok(false, "tooltip is shown during drag"); + } + dragService.startDragSession(); + // Emulate a buggy mousemove event. widget might dispatch mousemove event + // during drag. + EventUtils.synthesizeMouse(target, 5, 15, { type: "mousemove" }, win); + setTimeout(function () { + callbackOnPopupShown = null; + ok(true, "tooltip isn't shown during drag"); + dragService.endDragSession(true); + EventUtils.synthesizeMouse(target, -5, -5, { type: "mousemove" }, win); + + setTimeout(testShowTooltipAgain, 0); + }, 100); + } + + let onLoad = function (aEvent) + { + doc = gBrowser.contentDocument; + win = gBrowser.contentWindow; + target = doc.getElementById("target"); + + document.addEventListener("popupshown", onPopupShown, true); + document.addEventListener("popuphidden", onPopupHidden, true); + + EventUtils.synthesizeMouse(target, -5, -5, { type: "mousemove" }, win); + + // show tooltip by mousemove into target. + callbackOnPopupShown = function () + { + callbackOnPopupShown = null; + ok(true, "The tooltip is shown"); + + // hide tooltip by mousemove to outside. + callbackOnPopupHidden = function () { + callbackOnPopupHidden = null; + ok(true, "The tooltip is hidden"); + + setTimeout(testDuringDnD, 0); + } + EventUtils.synthesizeMouse(target, -5, 15, { type: "mousemove" }, win); + } + EventUtils.synthesizeMouse(target, 5, 15, { type: "mousemove" }, win); + } + + gBrowser.selectedBrowser.addEventListener("load", + function () { + gBrowser.selectedBrowser. + removeEventListener("load", arguments.callee, true); + setTimeout(onLoad, 0); + }, true); + + content.location = "data:text/html,<html><head></head><body>" + + "<a id=\"target\" href=\"about:blank\" title=\"This is tooltip text\" " + + "style=\"display:block;height:20px;margin:10px;\" " + + "onclick=\"return false;\">here is an anchor element</a></body></html>"; +} + + + diff --git a/layout/xul/test/moz.build b/layout/xul/test/moz.build new file mode 100644 index 000000000..895d11993 --- /dev/null +++ b/layout/xul/test/moz.build @@ -0,0 +1,6 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + diff --git a/layout/xul/test/test_bug159346.xul b/layout/xul/test/test_bug159346.xul new file mode 100644 index 000000000..4ef34fe32 --- /dev/null +++ b/layout/xul/test/test_bug159346.xul @@ -0,0 +1,135 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Test for Bug 159346"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=159346 +--> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<scrollbar id="scrollbar" curpos="0" maxpos="500"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var scrollbar = document.getElementById("scrollbar"); +var downButton = + document.getAnonymousElementByAttribute(scrollbar, "sbattr", + "scrollbar-down-bottom"); + +function init() +{ + downButton.style.display = "-moz-box"; + SimpleTest.executeSoon(doTest1); +} + +function getCurrentPos() +{ + return Number(scrollbar.getAttribute("curpos")); +} + +function doTest1() +{ + var lastPos = 0; + + synthesizeMouseAtCenter(downButton, { type: "mousedown" }); + ok(getCurrentPos() > lastPos, + "scrollbar didn't change curpos by mousedown #1"); + lastPos = getCurrentPos(); + + setTimeout(function () { + ok(getCurrentPos() > lastPos, + "scrollbar didn't change curpos by auto repeat #1"); + synthesizeMouseAtCenter(downButton, { type: "mouseup" }); + lastPos = getCurrentPos(); + + setTimeout(function () { + is(getCurrentPos(), lastPos, + "scrollbar changed curpos after mouseup #1"); + SimpleTest.executeSoon(doTest2); + }, 1000); + }, 1000); +} + +function doTest2() +{ + SpecialPowers.setIntPref("ui.scrollbarButtonAutoRepeatBehavior", 0); + + scrollbar.setAttribute("curpos", 0); + var lastPos = 0; + + synthesizeMouseAtCenter(downButton, { type: "mousedown" }); + ok(getCurrentPos() > lastPos, + "scrollbar didn't change curpos by mousedown #2"); + lastPos = getCurrentPos(); + + synthesizeMouse(downButton, -10, -10, { type: "mousemove" }); + lastPos = getCurrentPos(); + + setTimeout(function () { + is(getCurrentPos(), lastPos, + "scrollbar changed curpos by auto repeat when cursor is outside of scrollbar button #2"); + synthesizeMouseAtCenter(downButton, { type: "mousemove" }); + lastPos = getCurrentPos(); + + setTimeout(function () { + ok(getCurrentPos() > lastPos, + "scrollbar didn't change curpos by mousemove after cursor is back on the scrollbar button #2"); + synthesizeMouseAtCenter(downButton, { type: "mouseup" }); + SimpleTest.executeSoon(doTest3); + }, 1000); + }, 1000); +} + +function doTest3() +{ + SpecialPowers.setIntPref("ui.scrollbarButtonAutoRepeatBehavior", 1); + + scrollbar.setAttribute("curpos", 0); + var lastPos = 0; + + synthesizeMouseAtCenter(downButton, { type: "mousedown" }); + ok(getCurrentPos() > lastPos, + "scrollbar didn't change curpos by mousedown #3"); + synthesizeMouse(downButton, -10, -10, { type: "mousemove" }); + lastPos = getCurrentPos(); + + setTimeout(function () { + ok(getCurrentPos() > lastPos, + "scrollbar didn't change curpos by auto repeat when cursor is outside of scrollbar button #3"); + synthesizeMouseAtCenter(downButton, { type: "mousemove" }); + lastPos = getCurrentPos(); + + setTimeout(function () { + ok(getCurrentPos() > lastPos, + "scrollbar didn't change curpos by mousemove after cursor is back on the scrollbar button #3"); + synthesizeMouseAtCenter(downButton, { type: "mouseup" }); + + SpecialPowers.clearUserPref("ui.scrollbarButtonAutoRepeatBehavior"); + SimpleTest.finish(); + }, 1000); + }, 1000); +} + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +<body id="html_body" xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=159346">Mozilla Bug 159346</a> +<p id="display"></p> + +<pre id="test"> +</pre> +<script> +addLoadEvent(init); +</script> +</body> + + +</window> diff --git a/layout/xul/test/test_bug372685.xul b/layout/xul/test/test_bug372685.xul new file mode 100644 index 000000000..09cc2be20 --- /dev/null +++ b/layout/xul/test/test_bug372685.xul @@ -0,0 +1,49 @@ +<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Test for Bug 372685">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372685
+-->
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<menuitem id="a" style="display: -moz-stack;">
+<box id="b" style="display: -moz-popup; ">
+<box id="c" style="position: fixed;"></box>
+</box>
+</menuitem>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function removestyles(i){
+ document.getElementById('a').removeAttribute('style');
+ var x=document.getElementById('html_body').offsetHeight;
+ is(0, 0, "this is a crash test, so always ok if we survive this far");
+ SimpleTest.finish();
+}
+function do_test() {
+ setTimeout(removestyles,200);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+]]>
+</script>
+
+<body id="html_body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372685">Mozilla Bug 372685</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+<script>
+addLoadEvent(do_test);
+</script>
+</body>
+
+
+</window>
diff --git a/layout/xul/test/test_bug386386.html b/layout/xul/test/test_bug386386.html new file mode 100644 index 000000000..836ce730e --- /dev/null +++ b/layout/xul/test/test_bug386386.html @@ -0,0 +1,33 @@ +<html> +<head><title>Testcase for bug 386386</title> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=386386 +--> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> + +<iframe id="test386386" src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3Cwindow%3E%3C/window%3E"></iframe> + +<script class="testbody" type="application/javascript"> + +function boom() +{ + var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var doc = document.getElementById("test386386").contentDocument; + var observes = doc.createElementNS(XUL_NS, 'observes'); + doc.removeChild(doc.documentElement); + doc.appendChild(observes); + is(0, 0, "Test is successful if we get here without crashing"); + SimpleTest.finish(); +} + +function do_test() { + setTimeout(boom, 200); +} +SimpleTest.waitForExplicitFinish(); +addLoadEvent(do_test); +</script> + +</body> +</html> diff --git a/layout/xul/test/test_bug394800.xhtml b/layout/xul/test/test_bug394800.xhtml new file mode 100644 index 000000000..d2e2250e0 --- /dev/null +++ b/layout/xul/test/test_bug394800.xhtml @@ -0,0 +1,39 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=394800 +--> + <title>Test Mozilla bug 394800</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + +<script class="testbody" type="application/javascript"> + +function do_test() +{ + var x = document.getElementById("x"); + x.parentNode.removeChild(x); + is(0, 0, "this is a crash/assertion test, so we're ok if we survived this far"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +</script> +</head> + +<body> + +<xul:menulist><xul:tooltip/><div><span><xul:hbox id="x"/></span></div></xul:menulist> + +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=394800">Mozilla Bug 394800</a> +<p id="display"></p> + +<pre id="test"> +</pre> + +<script> + addLoadEvent(do_test); +</script> + +</body> +</html> diff --git a/layout/xul/test/test_bug398982-1.xul b/layout/xul/test/test_bug398982-1.xul new file mode 100644 index 000000000..39716ddf4 --- /dev/null +++ b/layout/xul/test/test_bug398982-1.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<menuitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + style="position: absolute; "> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=398982 +--> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<tooltip type="zzz"> +<treecols/> +</tooltip> + +<script xmlns="http://www.w3.org/1999/xhtml" class="testbody" type="application/javascript"> +<![CDATA[ +function doe() { + document.getElementsByTagName('menuitem')[0].removeAttribute('style'); + is(0, 0, "Test is successful if we get here without crashing"); + SimpleTest.finish(); +} +function do_test() { + setTimeout(doe, 200); +} +SimpleTest.waitForExplicitFinish(); +addLoadEvent(do_test); +]]> +</script> +<html:body></html:body> <!-- XXX SimpleTest.showReport() requires a html:body --> +</menuitem> diff --git a/layout/xul/test/test_bug398982-2.xul b/layout/xul/test/test_bug398982-2.xul new file mode 100644 index 000000000..253695c39 --- /dev/null +++ b/layout/xul/test/test_bug398982-2.xul @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Test for Bug 398982"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=398982 +--> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<popupgroup style="position: absolute; "> +<tooltip type="zzz"> +<treecols/> +</tooltip> + +<script xmlns="http://www.w3.org/1999/xhtml" class="testbody" type="application/javascript"> +<![CDATA[ +function doe() { + document.getElementsByTagName('popupgroup')[0].removeAttribute('style'); + is(0, 0, "Test is successful if we get here without crashing"); + SimpleTest.finish(); +} +function do_test() { + setTimeout(doe, 200); +} +SimpleTest.waitForExplicitFinish(); +addLoadEvent(do_test); +]]> +</script> +</popupgroup> +<html:body></html:body> <!-- XXX SimpleTest.showReport() requires a html:body --> +</window> diff --git a/layout/xul/test/test_bug563416.html b/layout/xul/test/test_bug563416.html new file mode 100644 index 000000000..1c87c1fa9 --- /dev/null +++ b/layout/xul/test/test_bug563416.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=563416 +--> +<head> + <title>Test for Bug 563416</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=563416">Mozilla Bug 563416</a> +<p id="display"><iframe id="test" src='data:text/html,<textarea style="-moz-box-sizing:content-box; -moz-appearance:none; height: 0px;" cols="20" rows="10">hsldkjvmshlkkajskdlfksdjflskdjflskdjflskdjflskdjfddddddddd</textarea>'></iframe></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 563416 **/ + +var result = -1; +var expected = -2; +var i = 0; + +function runTest() { + i = 0; + var frame = document.getElementById('test'); + frame.onload = function() { + var t = frame.contentDocument.documentElement.getElementsByTagName("textarea")[0]; + expected = t.clientWidth + 10; + t.style.width = expected + 'px'; + result = t.clientWidth; + if (i == 0) { + i++; + setTimeout(function(){frame.contentWindow.location.reload();},0); + } + else { + is(result, expected, "setting style.width changes clientWidth"); + SimpleTest.finish(); + } + } + frame.contentWindow.location.reload(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(runTest); + + +</script> +</pre> +</body> +</html> diff --git a/layout/xul/test/test_bug703150.xul b/layout/xul/test/test_bug703150.xul new file mode 100644 index 000000000..32e680b17 --- /dev/null +++ b/layout/xul/test/test_bug703150.xul @@ -0,0 +1,69 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Test for Bug 703150"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=703150 +--> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<scrollbar id="scrollbar" curpos="0" maxpos="500"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var scrollbar = document.getElementById("scrollbar"); +var scrollbarThumb = + document.getAnonymousElementByAttribute(scrollbar, "sbattr", + "scrollbar-thumb"); + +function doTest() +{ + function mousedownHandler(aEvent) + { + aEvent.stopPropagation(); + } + window.addEventListener("mousedown", mousedownHandler, true); + + // Wait for finishing reflow... + SimpleTest.executeSoon(function () { + synthesizeMouseAtCenter(scrollbarThumb, { type: "mousedown" }); + + is(scrollbar.getAttribute("curpos"), 0, + "scrollbar thumb has been moved already"); + + synthesizeMouseAtCenter(scrollbar, { type: "mousemove" }); + + ok(scrollbar.getAttribute("curpos") > 0, + "scrollbar thumb hasn't been dragged"); + + synthesizeMouseAtCenter(scrollbarThumb, { type: "mouseup" }); + + window.removeEventListener("mousedown", mousedownHandler, true); + + SimpleTest.finish(); + }); +} + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +<body id="html_body" xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=703150">Mozilla Bug 703150</a> +<p id="display"></p> + +<pre id="test"> +</pre> +<script> +addLoadEvent(doTest); +</script> +</body> + + +</window> diff --git a/layout/xul/tree/Makefile.in b/layout/xul/tree/Makefile.in new file mode 100644 index 000000000..5033f0cd0 --- /dev/null +++ b/layout/xul/tree/Makefile.in @@ -0,0 +1,31 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +LIBXUL_LIBRARY = 1 +FAIL_ON_WARNINGS = 1 + +LOCAL_INCLUDES = \ + -I$(srcdir) \ + -I$(topsrcdir)/content/events/src \ + -I$(srcdir)/../base/src \ + -I$(srcdir)/../../base \ + -I$(srcdir)/../../generic \ + -I$(srcdir)/../../style \ + -I$(srcdir)/../../forms \ + $(NULL) + +# we don't want the shared lib, but we want to force the creation of a static lib. +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/config/rules.mk + +DEFINES += -D_IMPL_NS_LAYOUT diff --git a/layout/xul/tree/crashtests/307298-1.xul b/layout/xul/tree/crashtests/307298-1.xul new file mode 100644 index 000000000..57396755a --- /dev/null +++ b/layout/xul/tree/crashtests/307298-1.xul @@ -0,0 +1,21 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="var tree = document.getElementById('tree'), treeitem = document.getElementById('treeitem'); tree.parentNode.insertBefore(treeitem, tree);"> + +<tree flex="1" id="tree"> + <treecols> + <treecol id="name" label="Name" primary="true" flex="1"/> + </treecols> + + <treechildren> + <treeitem id="treeitem"> + <treerow> + <treecell label="Click the button below to crash"/> + </treerow> + </treeitem> + </treechildren> +</tree> + +</window> diff --git a/layout/xul/tree/crashtests/309732-1.xul b/layout/xul/tree/crashtests/309732-1.xul new file mode 100644 index 000000000..df955e14e --- /dev/null +++ b/layout/xul/tree/crashtests/309732-1.xul @@ -0,0 +1,30 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)"> + + + <script> + function boom() + { + document.documentElement.appendChild(document.getElementById("TC")); + document.documentElement.appendChild(document.getElementById("TI")); + + document.documentElement.removeAttribute("class"); + } + </script> + +<tree flex="1"> + <treecols> + <treecol label="Name"/> + </treecols> + <treechildren id="TC"> + <treeitem id="TI"> + <treerow> + <treecell label="First treecell"/> + </treerow> + </treeitem> + </treechildren> + </tree> +</window> diff --git a/layout/xul/tree/crashtests/309732-2.xul b/layout/xul/tree/crashtests/309732-2.xul new file mode 100644 index 000000000..9c65e7379 --- /dev/null +++ b/layout/xul/tree/crashtests/309732-2.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)"> + + <script> + function boom() + { + document.documentElement.appendChild(document.getElementById('TC')); + document.getElementById('TI').hidden = false; + + document.documentElement.removeAttribute("class"); + } + </script> + + + <tree flex="1"> + <treecols> + <treecol label="Name" flex="1"/> + </treecols> + <treechildren id="TC"> + <treeitem> + <treerow> + <treecell label="First treecell"/> + </treerow> + </treeitem> + </treechildren> + </tree> + <treeitem id="TI" hidden="true"/> +</window> diff --git a/layout/xul/tree/crashtests/366583-1.xul b/layout/xul/tree/crashtests/366583-1.xul new file mode 100644 index 000000000..db37b444e --- /dev/null +++ b/layout/xul/tree/crashtests/366583-1.xul @@ -0,0 +1,43 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="boom1();" + class="reftest-wait"> + +<script> + +var tree; + +function boom1() +{ + tree = document.getElementById("tree"); + tree.style.position = "fixed"; + setTimeout(boom2, 30); +} + +function boom2() +{ + tree.style.overflow = "visible"; + document.documentElement.removeAttribute("class"); +} +</script> + +<tree rows="6" id="tree" style="display: list-item; overflow: auto; visibility: collapse;"> + <treecols> + <treecol id="firstname" label="First Name" primary="true" flex="3"/> + <treecol id="lastname" label="Last Name" flex="7"/> + </treecols> + + <treechildren> + <treeitem container="true" open="true"> + <treerow> + <treecell label="Foo"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + +</window> diff --git a/layout/xul/tree/crashtests/380217-1.xul b/layout/xul/tree/crashtests/380217-1.xul new file mode 100644 index 000000000..b834f7e1f --- /dev/null +++ b/layout/xul/tree/crashtests/380217-1.xul @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="document.documentElement.style.content = '\'a\'';"> + +<html:style> +* { position: fixed; } +</html:style> + +<tree rows="6"> + <treecols> + <treecol id="firstname" label="First Name" primary="true"/> + </treecols> + <treechildren> + <treeitem> + <treerow> + <treecell/> + </treerow> + </treeitem> + </treechildren> +</tree> + +</window> diff --git a/layout/xul/tree/crashtests/382444-1-inner.html b/layout/xul/tree/crashtests/382444-1-inner.html new file mode 100644 index 000000000..d59a2e787 --- /dev/null +++ b/layout/xul/tree/crashtests/382444-1-inner.html @@ -0,0 +1,15 @@ +<html>
+<head>
+<title>Testcase bug - Crash [@ nsINodeInfo::Equals] with underflow event, tree stuff and removing window</title>
+</head>
+<body>
+<iframe src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3Cwindow%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E%0A%3Ctree%20style%3D%22overflow%3A%20auto%3B%20display%3A%20-moz-inline-box%3B%22%3E%0A%3Ctreeitem%20style%3D%22overflow%3A%20scroll%3B%20display%3A%20table-cell%3B%22%3E%0A%3Ctreechildren%20style%3D%22%20display%3A%20table-row%3B%22%3E%0A%3Ctreeitem%20id%3D%22a%22%20style%3D%22display%3A%20table-cell%3B%22%3E%0A%3C/treeitem%3E%0A%3C/treechildren%3E%0A%3C/treeitem%3E%0A%0A%3C/tree%3E%0A%0A%3Cscript%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%3E%0Afunction%20doe%28%29%20%7B%0Adocument.getElementById%28%27a%27%29.parentNode.removeChild%28document.getElementById%28%27a%27%29%29%3B%0A%7D%0AsetTimeout%28doe%2C%20100%29%3B%0Adocument.addEventListener%28%27underflow%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%20%7D%2C%20true%29%3B%0Awindow.addEventListener%28%27underflow%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%20%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3C/window%3E" id="content"></iframe>
+
+<script>
+function doe() {
+window.location.reload();
+}
+setTimeout(doe, 500);
+</script>
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/382444-1.html b/layout/xul/tree/crashtests/382444-1.html new file mode 100644 index 000000000..8926cf16d --- /dev/null +++ b/layout/xul/tree/crashtests/382444-1.html @@ -0,0 +1,9 @@ +<html class="reftest-wait"> +<head> +<script> +setTimeout('document.documentElement.className = ""', 500); +</script> +<body> +<iframe src="382444-1-inner.html"></iframe> +</body> +</html> diff --git a/layout/xul/tree/crashtests/391178-1.xhtml b/layout/xul/tree/crashtests/391178-1.xhtml new file mode 100644 index 000000000..0f4b16cd9 --- /dev/null +++ b/layout/xul/tree/crashtests/391178-1.xhtml @@ -0,0 +1,41 @@ +<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait"> +<head> +<script> + +var ccc; + +function boom() +{ + var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + + var hbox = document.createElementNS(XUL_NS, 'hbox'); + var tree = document.createElementNS(XUL_NS, 'tree'); + var treecol = document.createElementNS(XUL_NS, 'treecol'); + + ccc = document.getElementById("ccc"); + + ccc.style.position = "fixed"; + + hbox.appendChild(treecol); + tree.appendChild(hbox); + ccc.appendChild(tree); + + setTimeout(boom2, 200); +} + +function boom2() +{ + ccc.style.position = ""; + document.documentElement.removeAttribute("class"); +} + +</script> +</head> + +<body onload="boom();"> + +<div id="ccc"> +</div> + +</body> +</html> diff --git a/layout/xul/tree/crashtests/391178-2.xul b/layout/xul/tree/crashtests/391178-2.xul new file mode 100644 index 000000000..491fbe77b --- /dev/null +++ b/layout/xul/tree/crashtests/391178-2.xul @@ -0,0 +1,20 @@ +<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-wait"> + +<tree id="a" style="position: fixed;"> + <box style=" display: -moz-box; position: fixed;"> + <treecol style=" display: -moz-box;"/> + </box> + <box style="position: fixed;"> + <treechildren style="display: -moz-box; position: absolute;"/> + </box> +</tree> + +<script xmlns="http://www.w3.org/1999/xhtml"> +function removestyles(){ + document.getElementById('a').removeAttribute('style'); + document.documentElement.removeAttribute("class"); +} +setTimeout(removestyles, 100); +</script> +</window> diff --git a/layout/xul/tree/crashtests/393665-1.xul b/layout/xul/tree/crashtests/393665-1.xul new file mode 100644 index 000000000..6fb5ec0c9 --- /dev/null +++ b/layout/xul/tree/crashtests/393665-1.xul @@ -0,0 +1,3 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <treechildren style="display: block" /> +</window> diff --git a/layout/xul/tree/crashtests/399227-1.xul b/layout/xul/tree/crashtests/399227-1.xul new file mode 100644 index 000000000..bfc381892 --- /dev/null +++ b/layout/xul/tree/crashtests/399227-1.xul @@ -0,0 +1,44 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)"> + + + <script> + function boom() + { + var tree = document.getElementById("thetree"); + var selection = tree.view.selection; + + selection.select(0); + tree.parentNode.removeChild(tree); + + // This is expected to throw an error (it used to crash). + try { + selection.rangedSelect(1, 1, false); + } + catch (ex) {} + + document.documentElement.removeAttribute("class"); + } + </script> + +<tree flex="1" id="thetree"> + <treecols> + <treecol label="Name"/> + </treecols> + <treechildren id="TC"> + <treeitem id="TI1"> + <treerow> + <treecell label="First treecell"/> + </treerow> + </treeitem> + <treeitem id="TI2"> + <treerow> + <treecell label="Second treecell"/> + </treerow> + </treeitem> + </treechildren> + </tree> +</window> diff --git a/layout/xul/tree/crashtests/399227-2.xul b/layout/xul/tree/crashtests/399227-2.xul new file mode 100644 index 000000000..55665ec47 --- /dev/null +++ b/layout/xul/tree/crashtests/399227-2.xul @@ -0,0 +1,50 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30);"> + + + <script> + function boom() + { + var tree = document.getElementById("thetree"); + var selection = tree.view.selection; + var treecolumn0 = tree.columns[0]; + var treecolumn1 = tree.columns[1]; + + selection.select(0); + selection.currentColumn = treecolumn0; + tree.parentNode.removeChild(tree); + + // This is expected to throw an error (it used to crash). + try { + selection.currentColumn = treecolumn1; + } + catch (ex) {} + + document.documentElement.removeAttribute("class"); + } + </script> + +<tree flex="1" id="thetree" seltype="cell"> + <treecols> + <treecol label="Name"/> + <treecol label="Test"/> + </treecols> + <treechildren id="TC"> + <treeitem id="TI1"> + <treerow> + <treecell label="First treecell"/> + <treecell label="Second treecell"/> + </treerow> + </treeitem> + <treeitem id="TI2"> + <treerow> + <treecell label="Third treecell"/> + <treecell label="Fourth treecell"/> + </treerow> + </treeitem> + </treechildren> + </tree> +</window> diff --git a/layout/xul/tree/crashtests/399692-1.xhtml b/layout/xul/tree/crashtests/399692-1.xhtml new file mode 100644 index 000000000..97eec2674 --- /dev/null +++ b/layout/xul/tree/crashtests/399692-1.xhtml @@ -0,0 +1,10 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> +</head> +<body> + +<xul:treechildren style="display: inline;" /> + +</body> +</html> diff --git a/layout/xul/tree/crashtests/399715-1.xhtml b/layout/xul/tree/crashtests/399715-1.xhtml new file mode 100644 index 000000000..ea0a20cfa --- /dev/null +++ b/layout/xul/tree/crashtests/399715-1.xhtml @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<body style="float: right;" onload="document.body.style.cssFloat = '';"> + +<xul:tree><xul:hbox><xul:treecol /></xul:hbox></xul:tree> + +</body> +</html> diff --git a/layout/xul/tree/crashtests/409807-1.xul b/layout/xul/tree/crashtests/409807-1.xul new file mode 100644 index 000000000..a3af3da41 --- /dev/null +++ b/layout/xul/tree/crashtests/409807-1.xul @@ -0,0 +1,25 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();"> + +<script type="text/javascript"> + +function boom() +{ + var tree = document.getElementById("tree"); + var tc = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "treechildren"); + + document.addEventListener("DOMAttrModified", m, false); + + tree.appendChild(tc); + + function m() + { + document.removeEventListener("DOMAttrModified", m, false); + tree.removeChild(tc); + } +} + +</script> + +<tree id="tree" /> + +</window> diff --git a/layout/xul/tree/crashtests/414170-1.xul b/layout/xul/tree/crashtests/414170-1.xul new file mode 100644 index 000000000..f3bc1d134 --- /dev/null +++ b/layout/xul/tree/crashtests/414170-1.xul @@ -0,0 +1,20 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="boom();"> + +<script type="text/javascript"> + +function boom() +{ + var option = document.createElementNS("http://www.w3.org/1999/xhtml", "option"); + document.getElementById("tc").appendChild(option); +} + +</script> + +<tree><treechildren id="tc"><hbox/></treechildren></tree> + +</window> diff --git a/layout/xul/tree/crashtests/430394-1.xul b/layout/xul/tree/crashtests/430394-1.xul new file mode 100644 index 000000000..63f4c3780 --- /dev/null +++ b/layout/xul/tree/crashtests/430394-1.xul @@ -0,0 +1,8 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<tree id="a" style="content: 't';" rows="-2"> + <menuitem id="b" onoverflow="event.currentTarget.parentNode.removeAttribute('style')"> + <treechildren style="display: block;" onoverflow="event.target.parentNode.removeChild(event.target)" /> + <treechildren style="display: block;" ordinal="0.5"/> + </menuitem> +</tree> +</window>
\ No newline at end of file diff --git a/layout/xul/tree/crashtests/454186-1.xul b/layout/xul/tree/crashtests/454186-1.xul new file mode 100644 index 000000000..edf266e43 --- /dev/null +++ b/layout/xul/tree/crashtests/454186-1.xul @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<tree flex="1"> + <treecols> + <treecol label="test" flex="1" type="progressmeter" /> + </treecols> + <treechildren> + <treeitem> + <treerow> + <treecell value="50" mode="normal" /> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell mode="undetermined" /> + </treerow> + </treeitem> + </treechildren> +</tree> + +</window> diff --git a/layout/xul/tree/crashtests/479931-1.xhtml b/layout/xul/tree/crashtests/479931-1.xhtml new file mode 100644 index 000000000..458a19250 --- /dev/null +++ b/layout/xul/tree/crashtests/479931-1.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> +<script type="text/javascript"> + +function boom() +{ + var o = document.createElementNS("http://www.w3.org/1999/xhtml", "option"); + var q = document.getElementById("q"); + q.appendChild(o); +} + +</script> +</head> +<body onload="boom();"> + +<xul:tree><xul:treechildren id="q"><div/></xul:treechildren></xul:tree> + +</body> +</html> diff --git a/layout/xul/tree/crashtests/509602-1-overlay.xul b/layout/xul/tree/crashtests/509602-1-overlay.xul new file mode 100644 index 000000000..f5cecd40e --- /dev/null +++ b/layout/xul/tree/crashtests/509602-1-overlay.xul @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<box id="b"> +<box onDOMAttrModified="event.target.parentNode.removeChild(event.target)" id="d"/> +<tree/> +</box> + +<tree> +<box id="b" observes="d"/> +<treechildren observes="b"/> +</tree> +</overlay>
\ No newline at end of file diff --git a/layout/xul/tree/crashtests/509602-1.xul b/layout/xul/tree/crashtests/509602-1.xul new file mode 100644 index 000000000..a1cdcf1cc --- /dev/null +++ b/layout/xul/tree/crashtests/509602-1.xul @@ -0,0 +1,3 @@ +<?xul-overlay href="509602-1-overlay.xul"?>
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
\ No newline at end of file diff --git a/layout/xul/tree/crashtests/585815-iframe.xul b/layout/xul/tree/crashtests/585815-iframe.xul new file mode 100644 index 000000000..ec0e5417e --- /dev/null +++ b/layout/xul/tree/crashtests/585815-iframe.xul @@ -0,0 +1,73 @@ +<?xml version="1.0"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="setInterval(run, 25)"> + +<tree flex="1" rows="2"> + <treecols> + <treecol id="sender" label="Sender" flex="1"/> + <treecol id="subject" label="Subject" flex="2"/> + </treecols> + <treechildren> + <treeitem> + <treerow> + <treecell label="joe@somewhere.com"/> + <treecell label="Top secret plans"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="mel@whereever.com"/> + <treecell label="Let's do lunch"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="mel@whereever.com"/> + <treecell label="Let's do lunch"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="mel@whereever.com"/> + <treecell label="Let's do lunch"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="mel@whereever.com"/> + <treecell label="Let's do lunch"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="mel@whereever.com"/> + <treecell label="Let's do lunch"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="mel@whereever.com"/> + <treecell label="Let's do lunch"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="mel@whereever.com"/> + <treecell label="Let's do lunch"/> + </treerow> + </treeitem> + </treechildren> +</tree> + +<script type="text/javascript"><![CDATA[ +function run() { + var tree = document.getElementsByTagName("tree")[0]; + var sel = tree.treeBoxObject.view.selection; + sel.rangedSelect(0, 0, true); + sel.rangedSelect(1000, 1001, true); + sel.adjustSelection(1, 0x7fffffff); +} +]]></script> + +</window> + diff --git a/layout/xul/tree/crashtests/585815.html b/layout/xul/tree/crashtests/585815.html new file mode 100644 index 000000000..0b8b01827 --- /dev/null +++ b/layout/xul/tree/crashtests/585815.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"><head> + <meta charset="utf-8"> + <title>Testcase for bug 585815</title> +<script> +function done() +{ + document.documentElement.removeAttribute("class"); +} +</script> +</head> +<body onload="setTimeout(done,1000)"> + +<iframe src="585815-iframe.xul"></iframe> + + +</body> +</html> diff --git a/layout/xul/tree/crashtests/601427.html b/layout/xul/tree/crashtests/601427.html new file mode 100644 index 000000000..cd9574eef --- /dev/null +++ b/layout/xul/tree/crashtests/601427.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<script> + +var onPaintFunctions = +[ + function() { document.documentElement.style.MozAppearance = "treeheadersortarrow"; }, + function() { document.documentElement.style.position = "fixed"; }, + function() { document.documentElement.removeAttribute("class"); } +]; + +var i = 0; + +function advance() +{ + var f = onPaintFunctions[i++]; + if (f) + f(); +} + +function start() +{ + window.addEventListener("MozAfterPaint", advance, true); + advance(); +} + +window.addEventListener("load", start, false); + +</script> +</html> diff --git a/layout/xul/tree/crashtests/730441-1.xul b/layout/xul/tree/crashtests/730441-1.xul new file mode 100644 index 000000000..293919b0f --- /dev/null +++ b/layout/xul/tree/crashtests/730441-1.xul @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<!-- +Program received signal SIGSEGV, Segmentation fault. +0xb6457185 in nsIContent::SetAttr (this=0x0, aNameSpaceID=0, aName=0xb0cb064c, aValue=..., aNotify=1) at ../../dist/include/nsIContent.h:285 +285 return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify); +(gdb) p this +$6 = (nsIContent * const) 0x0 +(gdb) bt 3 +#0 0xb6457185 in nsIContent::SetAttr (this=0x0, aNameSpaceID=0, aName=0xb0cb064c, aValue=..., aNotify=1) at ../../dist/include/nsIContent.h:285 +#1 0xb6b72072 in nsTreeColumns::RestoreNaturalOrder (this=0xaaf83cc0) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:605 +#2 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/src/md/unix/xptcinvoke_gcc_x86_unix.cpp:69 +(More stack frames follow...) +(gdb) frame 1 +#1 0xb6b72072 in nsTreeColumns::RestoreNaturalOrder (this=0xaaf83cc0) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:605 +605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE); +(gdb) list +600 PRUint32 numChildren = colsContent->GetChildCount(); +601 for (PRUint32 i = 0; i < numChildren; ++i) { +602 nsIContent *child = colsContent->GetChildAt(i); +603 nsAutoString ordinal; +604 ordinal.AppendInt(i); +605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE); +606 } +(gdb) p child +$7 = (nsIContent *) 0x0 + +First loop iteration: |child->SetAttr()| dispatches "DOMAttrModified" event. +Event listener removes next column. Second loop iteration: |colsContent->GetChildAt(i)| +returns null. Then we have |null->SetAttr()|. +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="run();"> +<tree id="tree"> + <treecols> + <treecol id="col1"/> + <treecol id="col2"/> + </treecols> + <treechildren/> +</tree> +<script type="text/javascript"><![CDATA[ +function listener() { + var col2 = document.getElementById("col2"); + col2.parentNode.removeChild(col2); +} + +function run() { + var col1 = document.getElementById("col1"); + col1.addEventListener("DOMAttrModified", listener, true); + var tree = document.getElementById("tree"); + tree.columns.restoreNaturalOrder(); +} +]]></script> +</window> + diff --git a/layout/xul/tree/crashtests/730441-2.xul b/layout/xul/tree/crashtests/730441-2.xul new file mode 100644 index 000000000..0428c89d8 --- /dev/null +++ b/layout/xul/tree/crashtests/730441-2.xul @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<!-- +Program received signal SIGSEGV, Segmentation fault. +0xb6b720a6 in nsTreeColumns::RestoreNaturalOrder (this=0xa947a580) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:610 +610 mTree->Invalidate(); +(gdb) bt 3 +#0 0xb6b720a6 in nsTreeColumns::RestoreNaturalOrder (this=0xa947a580) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:610 +#1 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/src/md/unix/xptcinvoke_gcc_x86_unix.cpp:69 +#2 0xb6171901 in XPCWrappedNative::CallMethod (ccx=..., mode=XPCWrappedNative::CALL_METHOD) + at js/src/xpconnect/src/xpcwrappednative.cpp:2722 +(More stack frames follow...) +(gdb) list +605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE); +606 } +607 +608 nsTreeColumns::InvalidateColumns(); +609 +610 mTree->Invalidate(); +611 +612 return NS_OK; +613 } +614 +(gdb) p mTree +$9 = (nsITreeBoxObject *) 0x0 + +|child->SetAttr()| dispatches "DOMAttrModified" event. Event listener removes +whole tree, |mTree| is being set to null. Then we have |null->Invalidate()|. +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="run();"> +<tree id="tree"> + <treecols> + <treecol id="col"/> + </treecols> + <treechildren/> +</tree> +<script type="text/javascript"><![CDATA[ +var tree = null; + +function listener() { + tree.parentNode.removeChild(tree); +} + +function run() { + col = document.getElementById("col"); + col.addEventListener("DOMAttrModified", listener, true); + tree = document.getElementById("tree"); + tree.columns.restoreNaturalOrder(); +} +]]></script> +</window> + diff --git a/layout/xul/tree/crashtests/730441-3.xul b/layout/xul/tree/crashtests/730441-3.xul new file mode 100644 index 000000000..eb42657f5 --- /dev/null +++ b/layout/xul/tree/crashtests/730441-3.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<!-- +###!!! ASSERTION: You can't dereference a NULL nsCOMPtr with operator->().: 'mRawPtr != 0', file ../../../../dist/include/nsCOMPtr.h, line 796 + +Program received signal SIGSEGV, Segmentation fault. +0xb6b7463a in nsTreeContentView::SetTree (this=0xb0ba2510, aTree=0xaaecece0) at layout/xul/base/src/tree/src/nsTreeContentView.cpp:571 +571 boxObject->GetElement(getter_AddRefs(element)); +(gdb) bt 3 +#0 0xb6b7463a in nsTreeContentView::SetTree (this=0xb0ba2510, aTree=0xaaecece0) at layout/xul/base/src/tree/src/nsTreeContentView.cpp:571 +#1 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/src/md/unix/xptcinvoke_gcc_x86_unix.cpp:69 +#2 0xb6171901 in XPCWrappedNative::CallMethod (ccx=..., mode=XPCWrappedNative::CALL_METHOD) + at js/src/xpconnect/src/xpcwrappednative.cpp:2722 +(More stack frames follow...) +(gdb) list 566 +561 nsTreeContentView::SetTree(nsITreeBoxObject* aTree) +562 { +563 ClearRows(); +564 +565 mBoxObject = aTree; +566 +567 if (aTree && !mRoot) { +568 // Get our root element +569 nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mBoxObject); +570 nsCOMPtr<nsIDOMElement> element; +571 boxObject->GetElement(getter_AddRefs(element)); +(gdb) p boxObject +$16 = {mRawPtr = 0x0} + +|aTree| does not implement |nsIBoxObject|, so |do_QueryInterface(mBoxObject)| +returns null. Then we have |null->GetElement()|. +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="document.getElementById('tree').view.setTree({});"> +<tree id="tree"> + <treechildren/> +</tree> +</window> + diff --git a/layout/xul/tree/crashtests/crashtests.list b/layout/xul/tree/crashtests/crashtests.list new file mode 100644 index 000000000..9064c612f --- /dev/null +++ b/layout/xul/tree/crashtests/crashtests.list @@ -0,0 +1,24 @@ +load 307298-1.xul +load 309732-1.xul +load 309732-2.xul +asserts-if(Android,3) load 366583-1.xul +asserts-if(winWidget,0-4) load 380217-1.xul # bug 616710 +load 382444-1.html +load 391178-1.xhtml +load 391178-2.xul +load 393665-1.xul +load 399227-1.xul +load 399227-2.xul +load 399692-1.xhtml +load 399715-1.xhtml +load 409807-1.xul +load 414170-1.xul +load 430394-1.xul +load 454186-1.xul +load 479931-1.xhtml +load 509602-1.xul +load 585815.html +load 601427.html +load 730441-1.xul +load 730441-2.xul +load 730441-3.xul diff --git a/layout/xul/tree/moz.build b/layout/xul/tree/moz.build new file mode 100644 index 000000000..8add51baf --- /dev/null +++ b/layout/xul/tree/moz.build @@ -0,0 +1,37 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsITreeBoxObject.idl', + 'nsITreeColumns.idl', + 'nsITreeContentView.idl', + 'nsITreeSelection.idl', + 'nsITreeView.idl', +] + +XPIDL_MODULE = 'layout_xul_tree' + +MODULE = 'layout' + +EXPORTS += [ + 'nsTreeColFrame.h', + 'nsTreeUtils.h', +] + +CPP_SOURCES += [ + 'nsTreeBodyFrame.cpp', + 'nsTreeBoxObject.cpp', + 'nsTreeColFrame.cpp', + 'nsTreeColumns.cpp', + 'nsTreeContentView.cpp', + 'nsTreeImageListener.cpp', + 'nsTreeSelection.cpp', + 'nsTreeStyleCache.cpp', + 'nsTreeUtils.cpp', +] + +LIBRARY_NAME = 'gkxultree_s' + diff --git a/layout/xul/tree/nsITreeBoxObject.idl b/layout/xul/tree/nsITreeBoxObject.idl new file mode 100644 index 000000000..05f2e4ac5 --- /dev/null +++ b/layout/xul/tree/nsITreeBoxObject.idl @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "domstubs.idl" + +interface nsITreeView; +interface nsITreeSelection; +interface nsITreeColumn; +interface nsITreeColumns; +interface nsIScriptableRegion; + +[scriptable, uuid(64BA5199-C4F4-4498-BBDC-F8E4C369086C)] +interface nsITreeBoxObject : nsISupports +{ + /** + * Obtain the columns. + */ + readonly attribute nsITreeColumns columns; + + /** + * The view that backs the tree and that supplies it with its data. + * It is dynamically settable, either using a view attribute on the + * tree tag or by setting this attribute to a new value. + */ + attribute nsITreeView view; + + /** + * Whether or not we are currently focused. + */ + attribute boolean focused; + + /** + * Obtain the treebody content node + */ + readonly attribute nsIDOMElement treeBody; + + /** + * Obtain the height of a row. + */ + readonly attribute long rowHeight; + + /** + * Obtain the width of a row. + */ + readonly attribute long rowWidth; + + /** + * Get the pixel position of the horizontal scrollbar. + */ + readonly attribute long horizontalPosition; + + /** + * Return the region for the visible parts of the selection, in device pixels. + */ + readonly attribute nsIScriptableRegion selectionRegion; + + /** + * Get the index of the first visible row. + */ + long getFirstVisibleRow(); + + /** + * Get the index of the last visible row. + */ + long getLastVisibleRow(); + + /** + * Gets the number of possible visible rows. + */ + long getPageLength(); + + /** + * Ensures that a row at a given index is visible. + */ + void ensureRowIsVisible(in long index); + + /** + * Ensures that a given cell in the tree is visible. + */ + void ensureCellIsVisible(in long row, in nsITreeColumn col); + + /** + * Scrolls such that the row at index is at the top of the visible view. + */ + void scrollToRow(in long index); + + /** + * Scroll the tree up or down by numLines lines. Positive + * values move down in the tree. Prevents scrolling off the + * end of the tree. + */ + void scrollByLines(in long numLines); + + /** + * Scroll the tree up or down by numPages pages. A page + * is considered to be the amount displayed by the tree. + * Positive values move down in the tree. Prevents scrolling + * off the end of the tree. + */ + void scrollByPages(in long numPages); + + /** + * Scrolls such that a given cell is visible (if possible) + * at the top left corner of the visible view. + */ + void scrollToCell(in long row, in nsITreeColumn col); + + /** + * Scrolls horizontally so that the specified column is + * at the left of the view (if possible). + */ + void scrollToColumn(in nsITreeColumn col); + + /** + * Scroll to a specific horizontal pixel position. + */ + void scrollToHorizontalPosition(in long horizontalPosition); + + /** + * Invalidation methods for fine-grained painting control. + */ + void invalidate(); + void invalidateColumn(in nsITreeColumn col); + void invalidateRow(in long index); + void invalidateCell(in long row, in nsITreeColumn col); + void invalidateRange(in long startIndex, in long endIndex); + void invalidateColumnRange(in long startIndex, in long endIndex, + in nsITreeColumn col); + + /** + * A hit test that can tell you what row the mouse is over. + * returns -1 for invalid mouse coordinates. + * + * The coordinate system is the client coordinate system for the + * document this boxObject lives in, and the units are CSS pixels. + */ + long getRowAt(in long x, in long y); + + /** + * A hit test that can tell you what cell the mouse is over. Row is the row index + * hit, returns -1 for invalid mouse coordinates. ColID is the column hit. + * ChildElt is the pseudoelement hit: this can have values of + * "cell", "twisty", "image", and "text". + * + * The coordinate system is the client coordinate system for the + * document this boxObject lives in, and the units are CSS pixels. + */ + void getCellAt(in long x, in long y, out long row, out nsITreeColumn col, out ACString childElt); + + /** + * Find the coordinates of an element within a specific cell. + */ + void getCoordsForCellItem(in long row, in nsITreeColumn col, in ACString element, + out long x, out long y, out long width, out long height); + + /** + * Determine if the text of a cell is being cropped or not. + */ + boolean isCellCropped(in long row, in nsITreeColumn col); + + /** + * The view is responsible for calling these notification methods when + * rows are added or removed. Index is the position at which the new + * rows were added or at which rows were removed. For + * non-contiguous additions/removals, this method should be called multiple times. + */ + void rowCountChanged(in long index, in long count); + + /** + * Notify the tree that the view is about to perform a batch + * update, that is, add, remove or invalidate several rows at once. + * This must be followed by calling endUpdateBatch(), otherwise the tree + * will get out of sync. + */ + void beginUpdateBatch(); + + /** + * Notify the tree that the view has completed a batch update. + */ + void endUpdateBatch(); + + /** + * Called on a theme switch to flush out the tree's style and image caches. + */ + void clearStyleAndImageCaches(); +}; diff --git a/layout/xul/tree/nsITreeColumns.idl b/layout/xul/tree/nsITreeColumns.idl new file mode 100644 index 000000000..9b27ce25e --- /dev/null +++ b/layout/xul/tree/nsITreeColumns.idl @@ -0,0 +1,95 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsITreeColumns; +interface nsIDOMElement; +interface nsIAtom; + +[scriptable, uuid(ae835ecf-6b32-4660-9b43-8a270df56e02)] +interface nsITreeColumn : nsISupports +{ + readonly attribute nsIDOMElement element; + + readonly attribute nsITreeColumns columns; + + readonly attribute long x; + readonly attribute long width; + + readonly attribute AString id; + [noscript] void getIdConst([shared] out wstring idConst); + [noscript] readonly attribute nsIAtom atom; + + readonly attribute long index; + + readonly attribute boolean primary; + readonly attribute boolean cycler; + readonly attribute boolean editable; + readonly attribute boolean selectable; + + const short TYPE_TEXT = 1; + const short TYPE_CHECKBOX = 2; + const short TYPE_PROGRESSMETER = 3; + readonly attribute short type; + + nsITreeColumn getNext(); + nsITreeColumn getPrevious(); + + void invalidate(); +}; + +interface nsITreeBoxObject; + +[scriptable, uuid(f8a8d6b4-6788-438d-9009-7142798767ab)] +interface nsITreeColumns : nsISupports +{ + /** + * The tree widget for these columns. + */ + readonly attribute nsITreeBoxObject tree; + + /** + * The number of columns. + */ + readonly attribute long count; + + /** + * An alias for count (for the benefit of scripts which treat this as an + * array). + */ + readonly attribute long length; + + /** + * Get the first/last column. + */ + nsITreeColumn getFirstColumn(); + nsITreeColumn getLastColumn(); + + /** + * Attribute based column getters. + */ + nsITreeColumn getPrimaryColumn(); + nsITreeColumn getSortedColumn(); + nsITreeColumn getKeyColumn(); + + /** + * Get the column for the given element. + */ + nsITreeColumn getColumnFor(in nsIDOMElement element); + + /** + * Parametric column getters. + */ + nsITreeColumn getNamedColumn(in AString id); + nsITreeColumn getColumnAt(in long index); + + /** + * This method is called whenever a treecol is added or removed and + * the column cache needs to be rebuilt. + */ + void invalidateColumns(); + + void restoreNaturalOrder(); +}; diff --git a/layout/xul/tree/nsITreeContentView.idl b/layout/xul/tree/nsITreeContentView.idl new file mode 100644 index 000000000..0856ce381 --- /dev/null +++ b/layout/xul/tree/nsITreeContentView.idl @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIDOMElement.idl" + +[scriptable, uuid(5ef62896-0c0a-41f1-bb3c-44a60f5dfdab)] +interface nsITreeContentView : nsISupports +{ + /** + * Retrieve the content item associated with the specified index. + */ + nsIDOMElement getItemAtIndex(in long index); + + /** + * Retrieve the index associated with the specified content item. + */ + long getIndexOfItem(in nsIDOMElement item); +}; diff --git a/layout/xul/tree/nsITreeSelection.idl b/layout/xul/tree/nsITreeSelection.idl new file mode 100644 index 000000000..6cc137f1e --- /dev/null +++ b/layout/xul/tree/nsITreeSelection.idl @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +interface nsITreeBoxObject; +interface nsITreeColumn; + +#include "nsISupports.idl" + +[scriptable, uuid(ab6fe746-300b-4ab4-abb9-1c0e3977874c)] +interface nsITreeSelection : nsISupports +{ + /** + * The tree widget for this selection. + */ + attribute nsITreeBoxObject tree; + + /** + * This attribute is a boolean indicating single selection. + */ + readonly attribute boolean single; + + /** + * The number of rows currently selected in this tree. + */ + readonly attribute long count; + + /** + * Indicates whether or not the row at the specified index is + * part of the selection. + */ + boolean isSelected(in long index); + + /** + * Deselect all rows and select the row at the specified index. + */ + void select(in long index); + + /** + * Perform a timed select. + */ + void timedSelect(in long index, in long delay); + + /** + * Toggle the selection state of the row at the specified index. + */ + void toggleSelect(in long index); + + /** + * Select the range specified by the indices. If augment is true, + * then we add the range to the selection without clearing out anything + * else. If augment is false, everything is cleared except for the specified range. + */ + void rangedSelect(in long startIndex, in long endIndex, in boolean augment); + + /** + * Clears the range. + */ + void clearRange(in long startIndex, in long endIndex); + + /** + * Clears the selection. + */ + void clearSelection(); + + /** + * Inverts the selection. + */ + void invertSelection(); + + /** + * Selects all rows. + */ + void selectAll(); + + /** + * Iterate the selection using these methods. + */ + long getRangeCount(); + void getRangeAt(in long i, out long min, out long max); + + /** + * Can be used to invalidate the selection. + */ + void invalidateSelection(); + + /** + * Called when the row count changes to adjust selection indices. + */ + void adjustSelection(in long index, in long count); + + /** + * This attribute is a boolean indicating whether or not the + * "select" event should fire when the selection is changed using + * one of our methods. A view can use this to temporarily suppress + * the selection while manipulating all of the indices, e.g., on + * a sort. + * Note: setting this attribute to false will fire a select event. + */ + attribute boolean selectEventsSuppressed; + + /** + * The current item (the one that gets a focus rect in addition to being + * selected). + */ + attribute long currentIndex; + + /** + * The current column. + */ + attribute nsITreeColumn currentColumn; + + /** + * The selection "pivot". This is the first item the user selected as + * part of a ranged select. + */ + readonly attribute long shiftSelectPivot; +}; + +/** + * The following interface is not scriptable and MUST NEVER BE MADE scriptable. + * Native treeselections implement it, and we use this to check whether a + * treeselection is native (and therefore suitable for use by untrusted content). + */ +[uuid(1bd59678-5cb3-4316-b246-31a91b19aabe)] +interface nsINativeTreeSelection : nsITreeSelection +{ + [noscript] void ensureNative(); +}; diff --git a/layout/xul/tree/nsITreeView.idl b/layout/xul/tree/nsITreeView.idl new file mode 100644 index 000000000..22d045fd6 --- /dev/null +++ b/layout/xul/tree/nsITreeView.idl @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsITreeBoxObject.idl" +#include "domstubs.idl" + +interface nsITreeSelection; +interface nsITreeColumn; +interface nsIDOMDataTransfer; + +[scriptable, uuid(091116f0-0bdc-4b32-b9c8-c8d5a37cb088)] +interface nsITreeView : nsISupports +{ + /** + * The total number of rows in the tree (including the offscreen rows). + */ + readonly attribute long rowCount; + + /** + * The selection for this view. + */ + attribute nsITreeSelection selection; + + /** + * A whitespace delimited list of properties. For each property X the view + * gives back will cause the pseudoclasses ::-moz-tree-cell(x), + * ::-moz-tree-row(x), ::-moz-tree-twisty(x), ::-moz-tree-image(x), + * ::-moz-tree-cell-text(x). to be matched on the pseudoelement + * ::moz-tree-row. + */ + AString getRowProperties(in long index); + + /** + * A whitespace delimited list of properties for a given cell. Each + * property, x, that the view gives back will cause the pseudoclasses + * ::-moz-tree-cell(x), ::-moz-tree-row(x), ::-moz-tree-twisty(x), + * ::-moz-tree-image(x), ::-moz-tree-cell-text(x). to be matched on the + * cell. + */ + AString getCellProperties(in long row, in nsITreeColumn col); + + /** + * Called to get properties to paint a column background. For shading the sort + * column, etc. + */ + AString getColumnProperties(in nsITreeColumn col); + + /** + * Methods that can be used to test whether or not a twisty should be drawn, + * and if so, whether an open or closed twisty should be used. + */ + boolean isContainer(in long index); + boolean isContainerOpen(in long index); + boolean isContainerEmpty(in long index); + + /** + * isSeparator is used to determine if the row at index is a separator. + * A value of true will result in the tree drawing a horizontal separator. + * The tree uses the ::moz-tree-separator pseudoclass to draw the separator. + */ + boolean isSeparator(in long index); + + /** + * Specifies if there is currently a sort on any column. Used mostly by dragdrop + * to affect drop feedback. + */ + boolean isSorted(); + + const short DROP_BEFORE = -1; + const short DROP_ON = 0; + const short DROP_AFTER = 1; + /** + * Methods used by the drag feedback code to determine if a drag is allowable at + * the current location. To get the behavior where drops are only allowed on + * items, such as the mailNews folder pane, always return false when + * the orientation is not DROP_ON. + */ + boolean canDrop(in long index, in long orientation, in nsIDOMDataTransfer dataTransfer); + + /** + * Called when the user drops something on this view. The |orientation| param + * specifies before/on/after the given |row|. + */ + void drop(in long row, in long orientation, in nsIDOMDataTransfer dataTransfer); + + /** + * Methods used by the tree to draw thread lines in the tree. + * getParentIndex is used to obtain the index of a parent row. + * If there is no parent row, getParentIndex returns -1. + */ + long getParentIndex(in long rowIndex); + + /** + * hasNextSibling is used to determine if the row at rowIndex has a nextSibling + * that occurs *after* the index specified by afterIndex. Code that is forced + * to march down the view looking at levels can optimize the march by starting + * at afterIndex+1. + */ + boolean hasNextSibling(in long rowIndex, in long afterIndex); + + /** + * The level is an integer value that represents + * the level of indentation. It is multiplied by the width specified in the + * :moz-tree-indentation pseudoelement to compute the exact indendation. + */ + long getLevel(in long index); + + /** + * The image path for a given cell. For defining an icon for a cell. + * If the empty string is returned, the :moz-tree-image pseudoelement + * will be used. + */ + AString getImageSrc(in long row, in nsITreeColumn col); + + /** + * The progress mode for a given cell. This method is only called for + * columns of type |progressmeter|. + */ + const short PROGRESS_NORMAL = 1; + const short PROGRESS_UNDETERMINED = 2; + const short PROGRESS_NONE = 3; + long getProgressMode(in long row, in nsITreeColumn col); + + /** + * The value for a given cell. This method is only called for columns + * of type other than |text|. + */ + AString getCellValue(in long row, in nsITreeColumn col); + + /** + * The text for a given cell. If a column consists only of an image, then + * the empty string is returned. + */ + AString getCellText(in long row, in nsITreeColumn col); + + /** + * Called during initialization to link the view to the front end box object. + */ + void setTree(in nsITreeBoxObject tree); + + /** + * Called on the view when an item is opened or closed. + */ + void toggleOpenState(in long index); + + /** + * Called on the view when a header is clicked. + */ + void cycleHeader(in nsITreeColumn col); + + /** + * Should be called from a XUL onselect handler whenever the selection changes. + */ + void selectionChanged(); + + /** + * Called on the view when a cell in a non-selectable cycling column (e.g., unread/flag/etc.) is clicked. + */ + void cycleCell(in long row, in nsITreeColumn col); + + /** + * isEditable is called to ask the view if the cell contents are editable. + * A value of true will result in the tree popping up a text field when + * the user tries to inline edit the cell. + */ + boolean isEditable(in long row, in nsITreeColumn col); + + /** + * isSelectable is called to ask the view if the cell is selectable. + * This method is only called if the selection style is |cell| or |text|. + * XXXvarga shouldn't this be called isCellSelectable? + */ + boolean isSelectable(in long row, in nsITreeColumn col); + + /** + * setCellValue is called when the value of the cell has been set by the user. + * This method is only called for columns of type other than |text|. + */ + void setCellValue(in long row, in nsITreeColumn col, in AString value); + + /** + * setCellText is called when the contents of the cell have been edited by the user. + */ + void setCellText(in long row, in nsITreeColumn col, in AString value); + + /** + * A command API that can be used to invoke commands on the selection. The tree + * will automatically invoke this method when certain keys are pressed. For example, + * when the DEL key is pressed, performAction will be called with the "delete" string. + */ + void performAction(in wstring action); + + /** + * A command API that can be used to invoke commands on a specific row. + */ + void performActionOnRow(in wstring action, in long row); + + /** + * A command API that can be used to invoke commands on a specific cell. + */ + void performActionOnCell(in wstring action, in long row, in nsITreeColumn col); +}; + +/** + * The following interface is not scriptable and MUST NEVER BE MADE scriptable. + * Native treeviews implement it, and we use this to check whether a treeview + * is native (and therefore suitable for use by untrusted content). + */ +[uuid(46c90265-6553-41ae-8d39-7022e7d09145)] +interface nsINativeTreeView : nsITreeView +{ + [noscript] void ensureNative(); +}; diff --git a/layout/xul/tree/nsTreeBodyFrame.cpp b/layout/xul/tree/nsTreeBodyFrame.cpp new file mode 100644 index 000000000..1cc79bbbc --- /dev/null +++ b/layout/xul/tree/nsTreeBodyFrame.cpp @@ -0,0 +1,4662 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Likely.h" + +#include "nsCOMPtr.h" +#include "nsPresContext.h" +#include "nsINameSpaceManager.h" + +#include "nsTreeBodyFrame.h" +#include "nsTreeSelection.h" +#include "nsTreeImageListener.h" + +#include "nsGkAtoms.h" +#include "nsCSSAnonBoxes.h" + +#include "nsIContent.h" +#include "nsStyleContext.h" +#include "nsIBoxObject.h" +#include "nsGUIEvent.h" +#include "nsAsyncDOMEvent.h" +#include "nsIDOMDataContainerEvent.h" +#include "nsIDOMMouseEvent.h" +#include "nsIDOMElement.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMDocument.h" +#include "nsIDOMXULElement.h" +#include "nsIDocument.h" +#include "mozilla/css/StyleRule.h" +#include "nsCSSRendering.h" +#include "nsIXULTemplateBuilder.h" +#include "nsXPIDLString.h" +#include "nsContainerFrame.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsWidgetsCID.h" +#include "nsBoxFrame.h" +#include "nsBoxObject.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "nsBoxLayoutState.h" +#include "nsTreeContentView.h" +#include "nsTreeUtils.h" +#include "nsChildIterator.h" +#include "nsITheme.h" +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "imgILoader.h" +#include "nsINodeInfo.h" +#include "nsContentUtils.h" +#include "nsLayoutUtils.h" +#include "nsIScrollableFrame.h" +#include "nsEventDispatcher.h" +#include "nsDisplayList.h" +#include "nsTreeBoxObject.h" +#include "nsRenderingContext.h" +#include "nsIScriptableRegion.h" +#include <algorithm> +#include "ScrollbarActivity.h" + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif +#ifdef IBMBIDI +#include "nsBidiUtils.h" +#endif + +using namespace mozilla; +using namespace mozilla::layout; + +// Enumeration function that cancels all the image requests in our cache +static PLDHashOperator +CancelImageRequest(const nsAString& aKey, + nsTreeImageCacheEntry aEntry, void* aData) +{ + + // If our imgIRequest object was registered with the refresh driver, + // then we need to deregister it. + nsTreeBodyFrame* frame = static_cast<nsTreeBodyFrame*>(aData); + + nsLayoutUtils::DeregisterImageRequest(frame->PresContext(), aEntry.request, + nullptr); + + aEntry.request->CancelAndForgetObserver(NS_BINDING_ABORTED); + return PL_DHASH_NEXT; +} + +// +// NS_NewTreeFrame +// +// Creates a new tree frame +// +nsIFrame* +NS_NewTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTreeBodyFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame) + +NS_QUERYFRAME_HEAD(nsTreeBodyFrame) + NS_QUERYFRAME_ENTRY(nsIScrollbarMediator) + NS_QUERYFRAME_ENTRY(nsIScrollbarOwner) + NS_QUERYFRAME_ENTRY(nsTreeBodyFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame) + +// Constructor +nsTreeBodyFrame::nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +:nsLeafBoxFrame(aPresShell, aContext), + mSlots(nullptr), + mTopRowIndex(0), + mPageLength(0), + mHorzPosition(0), + mOriginalHorzWidth(-1), + mHorzWidth(0), + mAdjustWidth(0), + mRowHeight(0), + mIndentation(0), + mStringWidth(-1), + mUpdateBatchNest(0), + mRowCount(0), + mMouseOverRow(-1), + mFocused(false), + mHasFixedRowCount(false), + mVerticalOverflow(false), + mHorizontalOverflow(false), + mReflowCallbackPosted(false) +{ + mColumns = new nsTreeColumns(this); +} + +// Destructor +nsTreeBodyFrame::~nsTreeBodyFrame() +{ + mImageCache.EnumerateRead(CancelImageRequest, this); + DetachImageListeners(); + delete mSlots; +} + +static void +GetBorderPadding(nsStyleContext* aContext, nsMargin& aMargin) +{ + aMargin.SizeTo(0, 0, 0, 0); + if (!aContext->StylePadding()->GetPadding(aMargin)) { + NS_NOTYETIMPLEMENTED("percentage padding"); + } + aMargin += aContext->StyleBorder()->GetComputedBorder(); +} + +static void +AdjustForBorderPadding(nsStyleContext* aContext, nsRect& aRect) +{ + nsMargin borderPadding(0, 0, 0, 0); + GetBorderPadding(aContext, borderPadding); + aRect.Deflate(borderPadding); +} + +void +nsTreeBodyFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow); + + mIndentation = GetIndentation(); + mRowHeight = GetRowHeight(); + + mCreatedListeners.Init(); + + mImageCache.Init(16); + EnsureBoxObject(); + + if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { + mScrollbarActivity = new ScrollbarActivity( + static_cast<nsIScrollbarOwner*>(this)); + } +} + +nsSize +nsTreeBodyFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState) +{ + EnsureView(); + + nsIContent* baseElement = GetBaseElement(); + + nsSize min(0,0); + int32_t desiredRows; + if (MOZ_UNLIKELY(!baseElement)) { + desiredRows = 0; + } + else if (baseElement->Tag() == nsGkAtoms::select && + baseElement->IsHTML()) { + min.width = CalcMaxRowWidth(); + nsAutoString size; + baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::size, size); + if (!size.IsEmpty()) { + nsresult err; + desiredRows = size.ToInteger(&err); + mHasFixedRowCount = true; + mPageLength = desiredRows; + } + else { + desiredRows = 1; + } + } + else { + // tree + nsAutoString rows; + baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows); + if (!rows.IsEmpty()) { + nsresult err; + desiredRows = rows.ToInteger(&err); + mPageLength = desiredRows; + } + else { + desiredRows = 0; + } + } + + min.height = mRowHeight * desiredRows; + + AddBorderAndPadding(min); + bool widthSet, heightSet; + nsIFrame::AddCSSMinSize(aBoxLayoutState, this, min, widthSet, heightSet); + + return min; +} + +nscoord +nsTreeBodyFrame::CalcMaxRowWidth() +{ + if (mStringWidth != -1) + return mStringWidth; + + if (!mView) + return 0; + + nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); + nsMargin rowMargin(0,0,0,0); + GetBorderPadding(rowContext, rowMargin); + + nscoord rowWidth; + nsTreeColumn* col; + + nsRefPtr<nsRenderingContext> rc = + PresContext()->PresShell()->GetReferenceRenderingContext(); + if (!rc) + return 0; + + for (int32_t row = 0; row < mRowCount; ++row) { + rowWidth = 0; + + for (col = mColumns->GetFirstColumn(); col; col = col->GetNext()) { + nscoord desiredWidth, currentWidth; + nsresult rv = GetCellWidth(row, col, rc, desiredWidth, currentWidth); + if (NS_FAILED(rv)) { + NS_NOTREACHED("invalid column"); + continue; + } + rowWidth += desiredWidth; + } + + if (rowWidth > mStringWidth) + mStringWidth = rowWidth; + } + + mStringWidth += rowMargin.left + rowMargin.right; + return mStringWidth; +} + +void +nsTreeBodyFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (mScrollbarActivity) { + mScrollbarActivity->Destroy(); + mScrollbarActivity = nullptr; + } + + mScrollEvent.Revoke(); + // Make sure we cancel any posted callbacks. + if (mReflowCallbackPosted) { + PresContext()->PresShell()->CancelReflowCallback(this); + mReflowCallbackPosted = false; + } + + if (mColumns) + mColumns->SetTree(nullptr); + + // Save off our info into the box object. + nsCOMPtr<nsPIBoxObject> box(do_QueryInterface(mTreeBoxObject)); + if (box) { + if (mTopRowIndex > 0) { + nsAutoString topRowStr; topRowStr.AssignLiteral("topRow"); + nsAutoString topRow; + topRow.AppendInt(mTopRowIndex); + box->SetProperty(topRowStr.get(), topRow.get()); + } + + // Always null out the cached tree body frame. + box->ClearCachedValues(); + + mTreeBoxObject = nullptr; // Drop our ref here. + } + + if (mView) { + nsCOMPtr<nsITreeSelection> sel; + mView->GetSelection(getter_AddRefs(sel)); + if (sel) + sel->SetTree(nullptr); + mView->SetTree(nullptr); + mView = nullptr; + } + + nsLeafBoxFrame::DestroyFrom(aDestructRoot); +} + +void +nsTreeBodyFrame::EnsureBoxObject() +{ + if (!mTreeBoxObject) { + nsIContent* parent = GetBaseElement(); + if (parent) { + nsIDocument* nsDoc = parent->GetDocument(); + if (!nsDoc) // there may be no document, if we're called from Destroy() + return; + ErrorResult ignored; + nsCOMPtr<nsIBoxObject> box = + nsDoc->GetBoxObjectFor(parent->AsElement(), ignored); + // Ensure that we got a native box object. + nsCOMPtr<nsPIBoxObject> pBox = do_QueryInterface(box); + if (pBox) { + nsCOMPtr<nsITreeBoxObject> realTreeBoxObject = do_QueryInterface(pBox); + if (realTreeBoxObject) { + nsTreeBodyFrame* innerTreeBoxObject = + static_cast<nsTreeBoxObject*>(realTreeBoxObject.get()) + ->GetCachedTreeBody(); + ENSURE_TRUE(!innerTreeBoxObject || innerTreeBoxObject == this); + mTreeBoxObject = realTreeBoxObject; + } + } + } + } +} + +void +nsTreeBodyFrame::EnsureView() +{ + if (!mView) { + if (PresContext()->PresShell()->IsReflowLocked()) { + if (!mReflowCallbackPosted) { + mReflowCallbackPosted = true; + PresContext()->PresShell()->PostReflowCallback(this); + } + return; + } + nsCOMPtr<nsIBoxObject> box = do_QueryInterface(mTreeBoxObject); + if (box) { + nsWeakFrame weakFrame(this); + nsCOMPtr<nsITreeView> treeView; + mTreeBoxObject->GetView(getter_AddRefs(treeView)); + if (treeView && weakFrame.IsAlive()) { + nsXPIDLString rowStr; + box->GetProperty(NS_LITERAL_STRING("topRow").get(), + getter_Copies(rowStr)); + nsAutoString rowStr2(rowStr); + nsresult error; + int32_t rowIndex = rowStr2.ToInteger(&error); + + // Set our view. + SetView(treeView); + ENSURE_TRUE(weakFrame.IsAlive()); + + // Scroll to the given row. + // XXX is this optimal if we haven't laid out yet? + ScrollToRow(rowIndex); + ENSURE_TRUE(weakFrame.IsAlive()); + + // Clear out the property info for the top row, but we always keep the + // view current. + box->RemoveProperty(NS_LITERAL_STRING("topRow").get()); + } + } + } +} + +void +nsTreeBodyFrame::ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth) +{ + if (!mReflowCallbackPosted && + (!aRect.IsEqualEdges(mRect) || mHorzWidth != aHorzWidth)) { + PresContext()->PresShell()->PostReflowCallback(this); + mReflowCallbackPosted = true; + mOriginalHorzWidth = mHorzWidth; + } + else if (mReflowCallbackPosted && + mHorzWidth != aHorzWidth && mOriginalHorzWidth == aHorzWidth) { + PresContext()->PresShell()->CancelReflowCallback(this); + mReflowCallbackPosted = false; + mOriginalHorzWidth = -1; + } +} + +void +nsTreeBodyFrame::SetBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, + bool aRemoveOverflowArea) +{ + nscoord horzWidth = CalcHorzWidth(GetScrollParts()); + ManageReflowCallback(aRect, horzWidth); + mHorzWidth = horzWidth; + + nsLeafBoxFrame::SetBounds(aBoxLayoutState, aRect, aRemoveOverflowArea); +} + + +bool +nsTreeBodyFrame::ReflowFinished() +{ + if (!mView) { + nsWeakFrame weakFrame(this); + EnsureView(); + NS_ENSURE_TRUE(weakFrame.IsAlive(), false); + } + if (mView) { + CalcInnerBox(); + ScrollParts parts = GetScrollParts(); + mHorzWidth = CalcHorzWidth(parts); + if (!mHasFixedRowCount) { + mPageLength = mInnerBox.height / mRowHeight; + } + + int32_t lastPageTopRow = std::max(0, mRowCount - mPageLength); + if (mTopRowIndex > lastPageTopRow) + ScrollToRowInternal(parts, lastPageTopRow); + + nsIContent *treeContent = GetBaseElement(); + if (treeContent && + treeContent->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::keepcurrentinview, + nsGkAtoms::_true, eCaseMatters)) { + // make sure that the current selected item is still + // visible after the tree changes size. + nsCOMPtr<nsITreeSelection> sel; + mView->GetSelection(getter_AddRefs(sel)); + if (sel) { + int32_t currentIndex; + sel->GetCurrentIndex(¤tIndex); + if (currentIndex != -1) + EnsureRowIsVisibleInternal(parts, currentIndex); + } + } + + if (!FullScrollbarsUpdate(false)) { + return false; + } + } + + mReflowCallbackPosted = false; + return false; +} + +void +nsTreeBodyFrame::ReflowCallbackCanceled() +{ + mReflowCallbackPosted = false; +} + +nsresult +nsTreeBodyFrame::GetView(nsITreeView * *aView) +{ + *aView = nullptr; + nsWeakFrame weakFrame(this); + EnsureView(); + NS_ENSURE_STATE(weakFrame.IsAlive()); + NS_IF_ADDREF(*aView = mView); + return NS_OK; +} + +nsresult +nsTreeBodyFrame::SetView(nsITreeView * aView) +{ + // First clear out the old view. + if (mView) { + nsCOMPtr<nsITreeSelection> sel; + mView->GetSelection(getter_AddRefs(sel)); + if (sel) + sel->SetTree(nullptr); + mView->SetTree(nullptr); + + // Only reset the top row index and delete the columns if we had an old non-null view. + mTopRowIndex = 0; + } + + // Tree, meet the view. + mView = aView; + + // Changing the view causes us to refetch our data. This will + // necessarily entail a full invalidation of the tree. + Invalidate(); + + nsIContent *treeContent = GetBaseElement(); + if (treeContent) { +#ifdef ACCESSIBILITY + nsAccessibilityService* accService = nsIPresShell::AccService(); + if (accService) + accService->TreeViewChanged(PresContext()->GetPresShell(), treeContent, mView); +#endif + FireDOMEvent(NS_LITERAL_STRING("TreeViewChanged"), treeContent); + } + + if (mView) { + // Give the view a new empty selection object to play with, but only if it + // doesn't have one already. + nsCOMPtr<nsITreeSelection> sel; + mView->GetSelection(getter_AddRefs(sel)); + if (sel) { + sel->SetTree(mTreeBoxObject); + } + else { + NS_NewTreeSelection(mTreeBoxObject, getter_AddRefs(sel)); + mView->SetSelection(sel); + } + + // View, meet the tree. + nsWeakFrame weakFrame(this); + mView->SetTree(mTreeBoxObject); + NS_ENSURE_STATE(weakFrame.IsAlive()); + mView->GetRowCount(&mRowCount); + + if (!PresContext()->PresShell()->IsReflowLocked()) { + // The scrollbar will need to be updated. + FullScrollbarsUpdate(false); + } else if (!mReflowCallbackPosted) { + mReflowCallbackPosted = true; + PresContext()->PresShell()->PostReflowCallback(this); + } + } + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::GetFocused(bool* aFocused) +{ + *aFocused = mFocused; + return NS_OK; +} + +nsresult +nsTreeBodyFrame::SetFocused(bool aFocused) +{ + if (mFocused != aFocused) { + mFocused = aFocused; + if (mView) { + nsCOMPtr<nsITreeSelection> sel; + mView->GetSelection(getter_AddRefs(sel)); + if (sel) + sel->InvalidateSelection(); + } + } + return NS_OK; +} + +nsresult +nsTreeBodyFrame::GetTreeBody(nsIDOMElement** aElement) +{ + //NS_ASSERTION(mContent, "no content, see bug #104878"); + if (!mContent) + return NS_ERROR_NULL_POINTER; + + return mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)aElement); +} + +nsresult +nsTreeBodyFrame::GetRowHeight(int32_t* _retval) +{ + *_retval = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); + return NS_OK; +} + +nsresult +nsTreeBodyFrame::GetRowWidth(int32_t *aRowWidth) +{ + *aRowWidth = nsPresContext::AppUnitsToIntCSSPixels(CalcHorzWidth(GetScrollParts())); + return NS_OK; +} + +nsresult +nsTreeBodyFrame::GetHorizontalPosition(int32_t *aHorizontalPosition) +{ + *aHorizontalPosition = nsPresContext::AppUnitsToIntCSSPixels(mHorzPosition); + return NS_OK; +} + +nsresult +nsTreeBodyFrame::GetSelectionRegion(nsIScriptableRegion **aRegion) +{ + *aRegion = nullptr; + + nsCOMPtr<nsITreeSelection> selection; + mView->GetSelection(getter_AddRefs(selection)); + NS_ENSURE_TRUE(selection, NS_OK); + + nsCOMPtr<nsIScriptableRegion> region = do_CreateInstance("@mozilla.org/gfx/region;1"); + NS_ENSURE_TRUE(region, NS_ERROR_FAILURE); + region->Init(); + + nsRefPtr<nsPresContext> presContext = PresContext(); + nsIntRect rect = mRect.ToOutsidePixels(presContext->AppUnitsPerCSSPixel()); + + nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame(); + nsPoint origin = GetOffsetTo(rootFrame); + + // iterate through the visible rows and add the selected ones to the + // drag region + int32_t x = nsPresContext::AppUnitsToIntCSSPixels(origin.x); + int32_t y = nsPresContext::AppUnitsToIntCSSPixels(origin.y); + int32_t top = y; + int32_t end = LastVisibleRow(); + int32_t rowHeight = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); + for (int32_t i = mTopRowIndex; i <= end; i++) { + bool isSelected; + selection->IsSelected(i, &isSelected); + if (isSelected) + region->UnionRect(x, y, rect.width, rowHeight); + y += rowHeight; + } + + // clip to the tree boundary in case one row extends past it + region->IntersectRect(x, top, rect.width, rect.height); + + NS_ADDREF(*aRegion = region); + return NS_OK; +} + +nsresult +nsTreeBodyFrame::Invalidate() +{ + if (mUpdateBatchNest) + return NS_OK; + + InvalidateFrame(); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::InvalidateColumn(nsITreeColumn* aCol) +{ + if (mUpdateBatchNest) + return NS_OK; + + nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol); + if (!col) + return NS_ERROR_INVALID_ARG; + +#ifdef ACCESSIBILITY + if (nsIPresShell::IsAccessibilityActive()) + FireInvalidateEvent(-1, -1, aCol, aCol); +#endif + + nsRect columnRect; + nsresult rv = col->GetRect(this, mInnerBox.y, mInnerBox.height, &columnRect); + NS_ENSURE_SUCCESS(rv, rv); + + // When false then column is out of view + if (OffsetForHorzScroll(columnRect, true)) + InvalidateFrameWithRect(columnRect); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::InvalidateRow(int32_t aIndex) +{ + if (mUpdateBatchNest) + return NS_OK; + +#ifdef ACCESSIBILITY + if (nsIPresShell::IsAccessibilityActive()) + FireInvalidateEvent(aIndex, aIndex, nullptr, nullptr); +#endif + + aIndex -= mTopRowIndex; + if (aIndex < 0 || aIndex > mPageLength) + return NS_OK; + + nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*aIndex, mInnerBox.width, mRowHeight); + InvalidateFrameWithRect(rowRect); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::InvalidateCell(int32_t aIndex, nsITreeColumn* aCol) +{ + if (mUpdateBatchNest) + return NS_OK; + +#ifdef ACCESSIBILITY + if (nsIPresShell::IsAccessibilityActive()) + FireInvalidateEvent(aIndex, aIndex, aCol, aCol); +#endif + + aIndex -= mTopRowIndex; + if (aIndex < 0 || aIndex > mPageLength) + return NS_OK; + + nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol); + if (!col) + return NS_ERROR_INVALID_ARG; + + nsRect cellRect; + nsresult rv = col->GetRect(this, mInnerBox.y+mRowHeight*aIndex, mRowHeight, + &cellRect); + NS_ENSURE_SUCCESS(rv, rv); + + if (OffsetForHorzScroll(cellRect, true)) + InvalidateFrameWithRect(cellRect); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::InvalidateRange(int32_t aStart, int32_t aEnd) +{ + if (mUpdateBatchNest) + return NS_OK; + + if (aStart == aEnd) + return InvalidateRow(aStart); + + int32_t last = LastVisibleRow(); + if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last) + return NS_OK; + + if (aStart < mTopRowIndex) + aStart = mTopRowIndex; + + if (aEnd > last) + aEnd = last; + +#ifdef ACCESSIBILITY + if (nsIPresShell::IsAccessibilityActive()) { + int32_t end = + mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0; + FireInvalidateEvent(aStart, end, nullptr, nullptr); + } +#endif + + nsRect rangeRect(mInnerBox.x, mInnerBox.y+mRowHeight*(aStart-mTopRowIndex), mInnerBox.width, mRowHeight*(aEnd-aStart+1)); + InvalidateFrameWithRect(rangeRect); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol) +{ + if (mUpdateBatchNest) + return NS_OK; + + nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol); + if (!col) + return NS_ERROR_INVALID_ARG; + + if (aStart == aEnd) + return InvalidateCell(aStart, col); + + int32_t last = LastVisibleRow(); + if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last) + return NS_OK; + + if (aStart < mTopRowIndex) + aStart = mTopRowIndex; + + if (aEnd > last) + aEnd = last; + +#ifdef ACCESSIBILITY + if (nsIPresShell::IsAccessibilityActive()) { + int32_t end = + mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0; + FireInvalidateEvent(aStart, end, aCol, aCol); + } +#endif + + nsRect rangeRect; + nsresult rv = col->GetRect(this, + mInnerBox.y+mRowHeight*(aStart-mTopRowIndex), + mRowHeight*(aEnd-aStart+1), + &rangeRect); + NS_ENSURE_SUCCESS(rv, rv); + + InvalidateFrameWithRect(rangeRect); + + return NS_OK; +} + +static void +FindScrollParts(nsIFrame* aCurrFrame, nsTreeBodyFrame::ScrollParts* aResult) +{ + if (!aResult->mColumnsScrollFrame) { + nsIScrollableFrame* f = do_QueryFrame(aCurrFrame); + if (f) { + aResult->mColumnsFrame = aCurrFrame; + aResult->mColumnsScrollFrame = f; + } + } + + nsScrollbarFrame *sf = do_QueryFrame(aCurrFrame); + if (sf) { + if (!aCurrFrame->IsHorizontal()) { + if (!aResult->mVScrollbar) { + aResult->mVScrollbar = sf; + } + } else { + if (!aResult->mHScrollbar) { + aResult->mHScrollbar = sf; + } + } + // don't bother searching inside a scrollbar + return; + } + + nsIFrame* child = aCurrFrame->GetFirstPrincipalChild(); + while (child && + !child->GetContent()->IsRootOfNativeAnonymousSubtree() && + (!aResult->mVScrollbar || !aResult->mHScrollbar || + !aResult->mColumnsScrollFrame)) { + FindScrollParts(child, aResult); + child = child->GetNextSibling(); + } +} + +nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts() +{ + ScrollParts result = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; + nsIContent* baseElement = GetBaseElement(); + nsIFrame* treeFrame = + baseElement ? baseElement->GetPrimaryFrame() : nullptr; + if (treeFrame) { + // The way we do this, searching through the entire frame subtree, is pretty + // dumb! We should know where these frames are. + FindScrollParts(treeFrame, &result); + if (result.mHScrollbar) { + result.mHScrollbar->SetScrollbarMediatorContent(GetContent()); + nsIFrame* f = do_QueryFrame(result.mHScrollbar); + result.mHScrollbarContent = f->GetContent(); + } + if (result.mVScrollbar) { + result.mVScrollbar->SetScrollbarMediatorContent(GetContent()); + nsIFrame* f = do_QueryFrame(result.mVScrollbar); + result.mVScrollbarContent = f->GetContent(); + } + } + return result; +} + +void +nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts) +{ + nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); + + nsWeakFrame weakFrame(this); + + if (aParts.mVScrollbar) { + nsAutoString curPos; + curPos.AppendInt(mTopRowIndex*rowHeightAsPixels); + aParts.mVScrollbarContent-> + SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true); + // 'this' might be deleted here + } + + if (weakFrame.IsAlive() && aParts.mHScrollbar) { + nsAutoString curPos; + curPos.AppendInt(mHorzPosition); + aParts.mHScrollbarContent-> + SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true); + // 'this' might be deleted here + } + + if (weakFrame.IsAlive() && mScrollbarActivity) { + mScrollbarActivity->ActivityOccurred(); + } +} + +void +nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts) +{ + bool verticalOverflowChanged = false; + bool horizontalOverflowChanged = false; + + if (!mVerticalOverflow && mRowCount > mPageLength) { + mVerticalOverflow = true; + verticalOverflowChanged = true; + } + else if (mVerticalOverflow && mRowCount <= mPageLength) { + mVerticalOverflow = false; + verticalOverflowChanged = true; + } + + if (aParts.mColumnsFrame) { + nsRect bounds = aParts.mColumnsFrame->GetRect(); + if (bounds.width != 0) { + /* Ignore overflows that are less than half a pixel. Yes these happen + all over the place when flex boxes are compressed real small. + Probably a result of a rounding errors somewhere in the layout code. */ + bounds.width += nsPresContext::CSSPixelsToAppUnits(0.5f); + if (!mHorizontalOverflow && bounds.width < mHorzWidth) { + mHorizontalOverflow = true; + horizontalOverflowChanged = true; + } else if (mHorizontalOverflow && bounds.width >= mHorzWidth) { + mHorizontalOverflow = false; + horizontalOverflowChanged = true; + } + } + } + + nsRefPtr<nsPresContext> presContext = PresContext(); + nsCOMPtr<nsIContent> content = mContent; + + if (verticalOverflowChanged) { + nsScrollPortEvent event(true, mVerticalOverflow ? NS_SCROLLPORT_OVERFLOW + : NS_SCROLLPORT_UNDERFLOW, nullptr); + event.orient = nsScrollPortEvent::vertical; + nsEventDispatcher::Dispatch(content, presContext, &event); + } + + if (horizontalOverflowChanged) { + nsScrollPortEvent event(true, + mHorizontalOverflow ? NS_SCROLLPORT_OVERFLOW + : NS_SCROLLPORT_UNDERFLOW, nullptr); + event.orient = nsScrollPortEvent::horizontal; + nsEventDispatcher::Dispatch(content, presContext, &event); + } +} + +void +nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame) +{ + if (mUpdateBatchNest || !mView) + return; + nsWeakFrame weakFrame(this); + + if (aParts.mVScrollbar) { + // Do Vertical Scrollbar + nsAutoString maxposStr; + + nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); + + int32_t size = rowHeightAsPixels * (mRowCount > mPageLength ? mRowCount - mPageLength : 0); + maxposStr.AppendInt(size); + aParts.mVScrollbarContent-> + SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true); + ENSURE_TRUE(weakFrame.IsAlive()); + + // Also set our page increment and decrement. + nscoord pageincrement = mPageLength*rowHeightAsPixels; + nsAutoString pageStr; + pageStr.AppendInt(pageincrement); + aParts.mVScrollbarContent-> + SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true); + ENSURE_TRUE(weakFrame.IsAlive()); + } + + if (aParts.mHScrollbar && aParts.mColumnsFrame && aWeakColumnsFrame.IsAlive()) { + // And now Horizontal scrollbar + nsRect bounds = aParts.mColumnsFrame->GetRect(); + nsAutoString maxposStr; + + maxposStr.AppendInt(mHorzWidth > bounds.width ? mHorzWidth - bounds.width : 0); + aParts.mHScrollbarContent-> + SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true); + ENSURE_TRUE(weakFrame.IsAlive()); + + nsAutoString pageStr; + pageStr.AppendInt(bounds.width); + aParts.mHScrollbarContent-> + SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true); + ENSURE_TRUE(weakFrame.IsAlive()); + + pageStr.Truncate(); + pageStr.AppendInt(nsPresContext::CSSPixelsToAppUnits(16)); + aParts.mHScrollbarContent-> + SetAttr(kNameSpaceID_None, nsGkAtoms::increment, pageStr, true); + } + + if (weakFrame.IsAlive() && mScrollbarActivity) { + mScrollbarActivity->ActivityOccurred(); + } +} + +// Takes client x/y in pixels, converts them to appunits, and converts into +// values relative to this nsTreeBodyFrame frame. +nsPoint +nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY) +{ + nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX), + nsPresContext::CSSPixelsToAppUnits(aY)); + + nsPresContext* presContext = PresContext(); + point -= GetOffsetTo(presContext->GetPresShell()->GetRootFrame()); + + // Adjust by the inner box coords, so that we're in the inner box's + // coordinate space. + point -= mInnerBox.TopLeft(); + return point; +} // AdjustClientCoordsToBoxCoordSpace + +nsresult +nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY, int32_t* _retval) +{ + if (!mView) + return NS_OK; + + nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY); + + // Check if the coordinates are above our visible space. + if (point.y < 0) { + *_retval = -1; + return NS_OK; + } + + *_retval = GetRowAt(point.x, point.y); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow, nsITreeColumn** aCol, + nsACString& aChildElt) +{ + if (!mView) + return NS_OK; + + nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY); + + // Check if the coordinates are above our visible space. + if (point.y < 0) { + *aRow = -1; + return NS_OK; + } + + nsTreeColumn* col; + nsIAtom* child; + GetCellAt(point.x, point.y, aRow, &col, &child); + + if (col) { + NS_ADDREF(*aCol = col); + if (child == nsCSSAnonBoxes::moztreecell) + aChildElt.AssignLiteral("cell"); + else if (child == nsCSSAnonBoxes::moztreetwisty) + aChildElt.AssignLiteral("twisty"); + else if (child == nsCSSAnonBoxes::moztreeimage) + aChildElt.AssignLiteral("image"); + else if (child == nsCSSAnonBoxes::moztreecelltext) + aChildElt.AssignLiteral("text"); + } + + return NS_OK; +} + + +// +// GetCoordsForCellItem +// +// Find the x/y location and width/height (all in PIXELS) of the given object +// in the given column. +// +// XXX IMPORTANT XXX: +// Hyatt says in the bug for this, that the following needs to be done: +// (1) You need to deal with overflow when computing cell rects. See other column +// iteration examples... if you don't deal with this, you'll mistakenly extend the +// cell into the scrollbar's rect. +// +// (2) You are adjusting the cell rect by the *row" border padding. That's +// wrong. You need to first adjust a row rect by its border/padding, and then the +// cell rect fits inside the adjusted row rect. It also can have border/padding +// as well as margins. The vertical direction isn't that important, but you need +// to get the horizontal direction right. +// +// (3) GetImageSize() does not include margins (but it does include border/padding). +// You need to make sure to add in the image's margins as well. +// +nsresult +nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsACString& aElement, + int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight) +{ + *aX = 0; + *aY = 0; + *aWidth = 0; + *aHeight = 0; + + bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + nscoord currX = mInnerBox.x - mHorzPosition; + + // The Rect for the requested item. + nsRect theRect; + + nsPresContext* presContext = PresContext(); + + for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; currCol = currCol->GetNext()) { + + // The Rect for the current cell. + nscoord colWidth; +#ifdef DEBUG + nsresult rv = +#endif + currCol->GetWidthInTwips(this, &colWidth); + NS_ASSERTION(NS_SUCCEEDED(rv), "invalid column"); + + nsRect cellRect(currX, mInnerBox.y + mRowHeight * (aRow - mTopRowIndex), + colWidth, mRowHeight); + + // Check the ID of the current column to see if it matches. If it doesn't + // increment the current X value and continue to the next column. + if (currCol != aCol) { + currX += cellRect.width; + continue; + } + // Now obtain the properties for our cell. + PrefillPropertyArray(aRow, currCol); + + nsAutoString properties; + mView->GetCellProperties(aRow, currCol, properties); + nsTreeUtils::TokenizeProperties(properties, mScratchArray); + + nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); + + // We don't want to consider any of the decorations that may be present + // on the current row, so we have to deflate the rect by the border and + // padding and offset its left and top coordinates appropriately. + AdjustForBorderPadding(rowContext, cellRect); + + nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); + + NS_NAMED_LITERAL_CSTRING(cell, "cell"); + if (currCol->IsCycler() || cell.Equals(aElement)) { + // If the current Column is a Cycler, then the Rect is just the cell - the margins. + // Similarly, if we're just being asked for the cell rect, provide it. + + theRect = cellRect; + nsMargin cellMargin; + cellContext->StyleMargin()->GetMargin(cellMargin); + theRect.Deflate(cellMargin); + break; + } + + // Since we're not looking for the cell, and since the cell isn't a cycler, + // we're looking for some subcomponent, and now we need to subtract the + // borders and padding of the cell from cellRect so this does not + // interfere with our computations. + AdjustForBorderPadding(cellContext, cellRect); + + nsRefPtr<nsRenderingContext> rc = + presContext->PresShell()->GetReferenceRenderingContext(); + if (!rc) + return NS_ERROR_OUT_OF_MEMORY; + + // Now we'll start making our way across the cell, starting at the edge of + // the cell and proceeding until we hit the right edge. |cellX| is the + // working X value that we will increment as we crawl from left to right. + nscoord cellX = cellRect.x; + nscoord remainWidth = cellRect.width; + + if (currCol->IsPrimary()) { + // If the current Column is a Primary, then we need to take into account the indentation + // and possibly a twisty. + + // The amount of indentation is the indentation width (|mIndentation|) by the level. + int32_t level; + mView->GetLevel(aRow, &level); + if (!isRTL) + cellX += mIndentation * level; + remainWidth -= mIndentation * level; + + // Find the twisty rect by computing its size. + nsRect imageRect; + nsRect twistyRect(cellRect); + nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); + GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext, + *rc, twistyContext); + + if (NS_LITERAL_CSTRING("twisty").Equals(aElement)) { + // If we're looking for the twisty Rect, just return the size + theRect = twistyRect; + break; + } + + // Now we need to add in the margins of the twisty element, so that we + // can find the offset of the next element in the cell. + nsMargin twistyMargin; + twistyContext->StyleMargin()->GetMargin(twistyMargin); + twistyRect.Inflate(twistyMargin); + + // Adjust our working X value with the twisty width (image size, margins, + // borders, padding. + if (!isRTL) + cellX += twistyRect.width; + } + + // Cell Image + nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage); + + nsRect imageSize = GetImageSize(aRow, currCol, false, imageContext); + if (NS_LITERAL_CSTRING("image").Equals(aElement)) { + theRect = imageSize; + theRect.x = cellX; + theRect.y = cellRect.y; + break; + } + + // Add in the margins of the cell image. + nsMargin imageMargin; + imageContext->StyleMargin()->GetMargin(imageMargin); + imageSize.Inflate(imageMargin); + + // Increment cellX by the image width + if (!isRTL) + cellX += imageSize.width; + + // Cell Text + nsAutoString cellText; + mView->GetCellText(aRow, currCol, cellText); + // We're going to measure this text so we need to ensure bidi is enabled if + // necessary + CheckTextForBidi(cellText); + + // Create a scratch rect to represent the text rectangle, with the current + // X and Y coords, and a guess at the width and height. The width is the + // remaining width we have left to traverse in the cell, which will be the + // widest possible value for the text rect, and the row height. + nsRect textRect(cellX, cellRect.y, remainWidth, cellRect.height); + + // Measure the width of the text. If the width of the text is greater than + // the remaining width available, then we just assume that the text has + // been cropped and use the remaining rect as the text Rect. Otherwise, + // we add in borders and padding to the text dimension and give that back. + nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext); + + nsRefPtr<nsFontMetrics> fm; + nsLayoutUtils::GetFontMetricsForStyleContext(textContext, + getter_AddRefs(fm)); + nscoord height = fm->MaxHeight(); + + nsMargin textMargin; + textContext->StyleMargin()->GetMargin(textMargin); + textRect.Deflate(textMargin); + + // Center the text. XXX Obey vertical-align style prop? + if (height < textRect.height) { + textRect.y += (textRect.height - height) / 2; + textRect.height = height; + } + + nsMargin bp(0,0,0,0); + GetBorderPadding(textContext, bp); + textRect.height += bp.top + bp.bottom; + + rc->SetFont(fm); + AdjustForCellText(cellText, aRow, currCol, *rc, textRect); + + theRect = textRect; + } + + if (isRTL) + theRect.x = mInnerBox.width - theRect.x - theRect.width; + + *aX = nsPresContext::AppUnitsToIntCSSPixels(theRect.x); + *aY = nsPresContext::AppUnitsToIntCSSPixels(theRect.y); + *aWidth = nsPresContext::AppUnitsToIntCSSPixels(theRect.width); + *aHeight = nsPresContext::AppUnitsToIntCSSPixels(theRect.height); + + return NS_OK; +} + +int32_t +nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY) +{ + // Now just mod by our total inner box height and add to our top row index. + int32_t row = (aY/mRowHeight)+mTopRowIndex; + + // Check if the coordinates are below our visible space (or within our visible + // space but below any row). + if (row > mTopRowIndex + mPageLength || row >= mRowCount) + return -1; + + return row; +} + +void +nsTreeBodyFrame::CheckTextForBidi(nsAutoString& aText) +{ + // We could check to see whether the prescontext already has bidi enabled, + // but usually it won't, so it's probably faster to avoid the call to + // GetPresContext() when it's not needed. + if (HasRTLChars(aText)) { + PresContext()->SetBidiEnabled(); + } +} + +void +nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText, + int32_t aRowIndex, nsTreeColumn* aColumn, + nsRenderingContext& aRenderingContext, + nsRect& aTextRect) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + nscoord width = + nsLayoutUtils::GetStringWidth(this, &aRenderingContext, aText.get(), aText.Length()); + nscoord maxWidth = aTextRect.width; + + if (aColumn->Overflow()) { + DebugOnly<nsresult> rv; + nsTreeColumn* nextColumn = aColumn->GetNext(); + while (nextColumn && width > maxWidth) { + while (nextColumn) { + nscoord width; + rv = nextColumn->GetWidthInTwips(this, &width); + NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid"); + + if (width != 0) + break; + + nextColumn = nextColumn->GetNext(); + } + + if (nextColumn) { + nsAutoString nextText; + mView->GetCellText(aRowIndex, nextColumn, nextText); + // We don't measure or draw this text so no need to check it for + // bidi-ness + + if (nextText.Length() == 0) { + nscoord width; + rv = nextColumn->GetWidthInTwips(this, &width); + NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid"); + + maxWidth += width; + + nextColumn = nextColumn->GetNext(); + } + else { + nextColumn = nullptr; + } + } + } + } + + if (width > maxWidth) { + // See if the width is even smaller than the ellipsis + // If so, clear the text completely. + const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); + aRenderingContext.SetTextRunRTL(false); + nscoord ellipsisWidth = aRenderingContext.GetWidth(kEllipsis); + + width = maxWidth; + if (ellipsisWidth > width) + aText.SetLength(0); + else if (ellipsisWidth == width) + aText.Assign(kEllipsis); + else { + // We will be drawing an ellipsis, thank you very much. + // Subtract out the required width of the ellipsis. + // This is the total remaining width we have to play with. + width -= ellipsisWidth; + + // Now we crop. + switch (aColumn->GetCropStyle()) { + default: + case 0: { + // Crop right. + nscoord cwidth; + nscoord twidth = 0; + uint32_t length = aText.Length(); + uint32_t i; + for (i = 0; i < length; ++i) { + PRUnichar ch = aText[i]; + // XXX this is horrible and doesn't handle clusters + cwidth = aRenderingContext.GetWidth(ch); + if (twidth + cwidth > width) + break; + twidth += cwidth; + } + aText.Truncate(i); + aText.Append(kEllipsis); + } + break; + + case 2: { + // Crop left. + nscoord cwidth; + nscoord twidth = 0; + int32_t length = aText.Length(); + int32_t i; + for (i=length-1; i >= 0; --i) { + PRUnichar ch = aText[i]; + cwidth = aRenderingContext.GetWidth(ch); + if (twidth + cwidth > width) + break; + twidth += cwidth; + } + + nsAutoString copy; + aText.Right(copy, length-1-i); + aText.Assign(kEllipsis); + aText += copy; + } + break; + + case 1: + { + // Crop center. + nsAutoString leftStr, rightStr; + nscoord cwidth, twidth = 0; + int32_t length = aText.Length(); + int32_t rightPos = length - 1; + for (int32_t leftPos = 0; leftPos < rightPos; ++leftPos) { + PRUnichar ch = aText[leftPos]; + cwidth = aRenderingContext.GetWidth(ch); + twidth += cwidth; + if (twidth > width) + break; + leftStr.Append(ch); + + ch = aText[rightPos]; + cwidth = aRenderingContext.GetWidth(ch); + twidth += cwidth; + if (twidth > width) + break; + rightStr.Insert(ch, 0); + --rightPos; + } + aText = leftStr; + aText.Append(kEllipsis); + aText += rightStr; + } + break; + } + } + + width = nsLayoutUtils::GetStringWidth(this, &aRenderingContext, aText.get(), aText.Length()); + } + + switch (aColumn->GetTextAlignment()) { + case NS_STYLE_TEXT_ALIGN_RIGHT: { + aTextRect.x += aTextRect.width - width; + } + break; + case NS_STYLE_TEXT_ALIGN_CENTER: { + aTextRect.x += (aTextRect.width - width) / 2; + } + break; + } + + aTextRect.width = width; +} + +nsIAtom* +nsTreeBodyFrame::GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect, + int32_t aRowIndex, + nsTreeColumn* aColumn) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + // Obtain the properties for our cell. + PrefillPropertyArray(aRowIndex, aColumn); + nsAutoString properties; + mView->GetCellProperties(aRowIndex, aColumn, properties); + nsTreeUtils::TokenizeProperties(properties, mScratchArray); + + // Resolve style for the cell. + nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); + + // Obtain the margins for the cell and then deflate our rect by that + // amount. The cell is assumed to be contained within the deflated rect. + nsRect cellRect(aCellRect); + nsMargin cellMargin; + cellContext->StyleMargin()->GetMargin(cellMargin); + cellRect.Deflate(cellMargin); + + // Adjust the rect for its border and padding. + AdjustForBorderPadding(cellContext, cellRect); + + if (aX < cellRect.x || aX >= cellRect.x + cellRect.width) { + // The user clicked within the cell's margins/borders/padding. This constitutes a click on the cell. + return nsCSSAnonBoxes::moztreecell; + } + + nscoord currX = cellRect.x; + nscoord remainingWidth = cellRect.width; + + // Handle right alignment hit testing. + bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + + nsPresContext* presContext = PresContext(); + nsRefPtr<nsRenderingContext> rc = + presContext->PresShell()->GetReferenceRenderingContext(); + if (!rc) + return nsCSSAnonBoxes::moztreecell; + + if (aColumn->IsPrimary()) { + // If we're the primary column, we have indentation and a twisty. + int32_t level; + mView->GetLevel(aRowIndex, &level); + + if (!isRTL) + currX += mIndentation*level; + remainingWidth -= mIndentation*level; + + if ((isRTL && aX > currX + remainingWidth) || + (!isRTL && aX < currX)) { + // The user clicked within the indentation. + return nsCSSAnonBoxes::moztreecell; + } + + // Always leave space for the twisty. + nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height); + bool hasTwisty = false; + bool isContainer = false; + mView->IsContainer(aRowIndex, &isContainer); + if (isContainer) { + bool isContainerEmpty = false; + mView->IsContainerEmpty(aRowIndex, &isContainerEmpty); + if (!isContainerEmpty) + hasTwisty = true; + } + + // Resolve style for the twisty. + nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); + + nsRect imageSize; + GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, presContext, + *rc, twistyContext); + + // We will treat a click as hitting the twisty if it happens on the margins, borders, padding, + // or content of the twisty object. By allowing a "slop" into the margin, we make it a little + // bit easier for a user to hit the twisty. (We don't want to be too picky here.) + nsMargin twistyMargin; + twistyContext->StyleMargin()->GetMargin(twistyMargin); + twistyRect.Inflate(twistyMargin); + if (isRTL) + twistyRect.x = currX + remainingWidth - twistyRect.width; + + // Now we test to see if aX is actually within the twistyRect. If it is, and if the item should + // have a twisty, then we return "twisty". If it is within the rect but we shouldn't have a twisty, + // then we return "cell". + if (aX >= twistyRect.x && aX < twistyRect.x + twistyRect.width) { + if (hasTwisty) + return nsCSSAnonBoxes::moztreetwisty; + else + return nsCSSAnonBoxes::moztreecell; + } + + if (!isRTL) + currX += twistyRect.width; + remainingWidth -= twistyRect.width; + } + + // Now test to see if the user hit the icon for the cell. + nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height); + + // Resolve style for the image. + nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage); + + nsRect iconSize = GetImageSize(aRowIndex, aColumn, false, imageContext); + nsMargin imageMargin; + imageContext->StyleMargin()->GetMargin(imageMargin); + iconSize.Inflate(imageMargin); + iconRect.width = iconSize.width; + if (isRTL) + iconRect.x = currX + remainingWidth - iconRect.width; + + if (aX >= iconRect.x && aX < iconRect.x + iconRect.width) { + // The user clicked on the image. + return nsCSSAnonBoxes::moztreeimage; + } + + if (!isRTL) + currX += iconRect.width; + remainingWidth -= iconRect.width; + + nsAutoString cellText; + mView->GetCellText(aRowIndex, aColumn, cellText); + // We're going to measure this text so we need to ensure bidi is enabled if + // necessary + CheckTextForBidi(cellText); + + nsRect textRect(currX, cellRect.y, remainingWidth, cellRect.height); + + nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext); + + nsMargin textMargin; + textContext->StyleMargin()->GetMargin(textMargin); + textRect.Deflate(textMargin); + + AdjustForBorderPadding(textContext, textRect); + + nsRefPtr<nsFontMetrics> fm; + nsLayoutUtils::GetFontMetricsForStyleContext(textContext, + getter_AddRefs(fm)); + rc->SetFont(fm); + + AdjustForCellText(cellText, aRowIndex, aColumn, *rc, textRect); + + if (aX >= textRect.x && aX < textRect.x + textRect.width) + return nsCSSAnonBoxes::moztreecelltext; + else + return nsCSSAnonBoxes::moztreecell; +} + +void +nsTreeBodyFrame::GetCellAt(nscoord aX, nscoord aY, int32_t* aRow, + nsTreeColumn** aCol, nsIAtom** aChildElt) +{ + *aCol = nullptr; + *aChildElt = nullptr; + + *aRow = GetRowAt(aX, aY); + if (*aRow < 0) + return; + + // Determine the column hit. + for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; + currCol = currCol->GetNext()) { + nsRect cellRect; + nsresult rv = currCol->GetRect(this, + mInnerBox.y + + mRowHeight * (*aRow - mTopRowIndex), + mRowHeight, + &cellRect); + if (NS_FAILED(rv)) { + NS_NOTREACHED("column has no frame"); + continue; + } + + if (!OffsetForHorzScroll(cellRect, false)) + continue; + + if (aX >= cellRect.x && aX < cellRect.x + cellRect.width) { + // We know the column hit now. + *aCol = currCol; + + if (currCol->IsCycler()) + // Cyclers contain only images. Fill this in immediately and return. + *aChildElt = nsCSSAnonBoxes::moztreeimage; + else + *aChildElt = GetItemWithinCellAt(aX, cellRect, *aRow, currCol); + break; + } + } +} + +nsresult +nsTreeBodyFrame::GetCellWidth(int32_t aRow, nsTreeColumn* aCol, + nsRenderingContext* aRenderingContext, + nscoord& aDesiredSize, nscoord& aCurrentSize) +{ + NS_PRECONDITION(aCol, "aCol must not be null"); + NS_PRECONDITION(aRenderingContext, "aRenderingContext must not be null"); + + // The rect for the current cell. + nscoord colWidth; + nsresult rv = aCol->GetWidthInTwips(this, &colWidth); + NS_ENSURE_SUCCESS(rv, rv); + + nsRect cellRect(0, 0, colWidth, mRowHeight); + + int32_t overflow = cellRect.x+cellRect.width-(mInnerBox.x+mInnerBox.width); + if (overflow > 0) + cellRect.width -= overflow; + + // Adjust borders and padding for the cell. + nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); + nsMargin bp(0,0,0,0); + GetBorderPadding(cellContext, bp); + + aCurrentSize = cellRect.width; + aDesiredSize = bp.left + bp.right; + + if (aCol->IsPrimary()) { + // If the current Column is a Primary, then we need to take into account + // the indentation and possibly a twisty. + + // The amount of indentation is the indentation width (|mIndentation|) by the level. + int32_t level; + mView->GetLevel(aRow, &level); + aDesiredSize += mIndentation * level; + + // Find the twisty rect by computing its size. + nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); + + nsRect imageSize; + nsRect twistyRect(cellRect); + GetTwistyRect(aRow, aCol, imageSize, twistyRect, PresContext(), + *aRenderingContext, twistyContext); + + // Add in the margins of the twisty element. + nsMargin twistyMargin; + twistyContext->StyleMargin()->GetMargin(twistyMargin); + twistyRect.Inflate(twistyMargin); + + aDesiredSize += twistyRect.width; + } + + nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage); + + // Account for the width of the cell image. + nsRect imageSize = GetImageSize(aRow, aCol, false, imageContext); + // Add in the margins of the cell image. + nsMargin imageMargin; + imageContext->StyleMargin()->GetMargin(imageMargin); + imageSize.Inflate(imageMargin); + + aDesiredSize += imageSize.width; + + // Get the cell text. + nsAutoString cellText; + mView->GetCellText(aRow, aCol, cellText); + // We're going to measure this text so we need to ensure bidi is enabled if + // necessary + CheckTextForBidi(cellText); + + nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext); + + // Get the borders and padding for the text. + GetBorderPadding(textContext, bp); + + nsRefPtr<nsFontMetrics> fm; + nsLayoutUtils::GetFontMetricsForStyleContext(textContext, + getter_AddRefs(fm)); + aRenderingContext->SetFont(fm); + + // Get the width of the text itself + nscoord width = + nsLayoutUtils::GetStringWidth(this, aRenderingContext, cellText.get(), cellText.Length()); + nscoord totalTextWidth = width + bp.left + bp.right; + aDesiredSize += totalTextWidth; + return NS_OK; +} + +nsresult +nsTreeBodyFrame::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *_retval) +{ + nscoord currentSize, desiredSize; + nsresult rv; + + nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol); + if (!col) + return NS_ERROR_INVALID_ARG; + + nsRefPtr<nsRenderingContext> rc = + PresContext()->PresShell()->GetReferenceRenderingContext(); + NS_ENSURE_TRUE(rc, NS_ERROR_FAILURE); + + rv = GetCellWidth(aRow, col, rc, desiredSize, currentSize); + NS_ENSURE_SUCCESS(rv, rv); + + *_retval = desiredSize > currentSize; + + return NS_OK; +} + +void +nsTreeBodyFrame::MarkDirtyIfSelect() +{ + nsIContent* baseElement = GetBaseElement(); + + if (baseElement && baseElement->Tag() == nsGkAtoms::select && + baseElement->IsHTML()) { + // If we are an intrinsically sized select widget, we may need to + // resize, if the widest item was removed or a new item was added. + // XXX optimize this more + + mStringWidth = -1; + PresContext()->PresShell()->FrameNeedsReflow(this, + nsIPresShell::eTreeChange, + NS_FRAME_IS_DIRTY); + } +} + +nsresult +nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID, + nsTimerCallbackFunc aFunc, int32_t aType, + nsITimer** aTimer) +{ + // Get the delay from the look and feel service. + int32_t delay = LookAndFeel::GetInt(aID, 0); + + nsCOMPtr<nsITimer> timer; + + // Create a new timer only if the delay is greater than zero. + // Zero value means that this feature is completely disabled. + if (delay > 0) { + timer = do_CreateInstance("@mozilla.org/timer;1"); + if (timer) + timer->InitWithFuncCallback(aFunc, this, delay, aType); + } + + NS_IF_ADDREF(*aTimer = timer); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::RowCountChanged(int32_t aIndex, int32_t aCount) +{ + if (aCount == 0 || !mView) + return NS_OK; // Nothing to do. + +#ifdef ACCESSIBILITY + if (nsIPresShell::IsAccessibilityActive()) + FireRowCountChangedEvent(aIndex, aCount); +#endif + + // Adjust our selection. + nsCOMPtr<nsITreeSelection> sel; + mView->GetSelection(getter_AddRefs(sel)); + if (sel) + sel->AdjustSelection(aIndex, aCount); + + if (mUpdateBatchNest) + return NS_OK; + + mRowCount += aCount; +#ifdef DEBUG + int32_t rowCount = mRowCount; + mView->GetRowCount(&rowCount); + NS_ASSERTION(rowCount == mRowCount, "row count did not change by the amount suggested, check caller"); +#endif + + int32_t count = Abs(aCount); + int32_t last = LastVisibleRow(); + if (aIndex >= mTopRowIndex && aIndex <= last) + InvalidateRange(aIndex, last); + + ScrollParts parts = GetScrollParts(); + + if (mTopRowIndex == 0) { + // Just update the scrollbar and return. + if (FullScrollbarsUpdate(false)) { + MarkDirtyIfSelect(); + } + return NS_OK; + } + + bool needsInvalidation = false; + // Adjust our top row index. + if (aCount > 0) { + if (mTopRowIndex > aIndex) { + // Rows came in above us. Augment the top row index. + mTopRowIndex += aCount; + } + } + else if (aCount < 0) { + if (mTopRowIndex > aIndex+count-1) { + // No need to invalidate. The remove happened + // completely above us (offscreen). + mTopRowIndex -= count; + } + else if (mTopRowIndex >= aIndex) { + // This is a full-blown invalidate. + if (mTopRowIndex + mPageLength > mRowCount - 1) { + mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength); + } + needsInvalidation = true; + } + } + + if (FullScrollbarsUpdate(needsInvalidation)) { + MarkDirtyIfSelect(); + } + return NS_OK; +} + +nsresult +nsTreeBodyFrame::BeginUpdateBatch() +{ + ++mUpdateBatchNest; + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::EndUpdateBatch() +{ + NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch"); + + if (--mUpdateBatchNest == 0) { + if (mView) { + Invalidate(); + int32_t countBeforeUpdate = mRowCount; + mView->GetRowCount(&mRowCount); + if (countBeforeUpdate != mRowCount) { + if (mTopRowIndex + mPageLength > mRowCount - 1) { + mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength); + } + FullScrollbarsUpdate(false); + } + } + } + + return NS_OK; +} + +void +nsTreeBodyFrame::PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol) +{ + NS_PRECONDITION(!aCol || aCol->GetFrame(), "invalid column passed"); + mScratchArray.Clear(); + + // focus + if (mFocused) + mScratchArray.AppendElement(nsGkAtoms::focus); + + // sort + bool sorted = false; + mView->IsSorted(&sorted); + if (sorted) + mScratchArray.AppendElement(nsGkAtoms::sorted); + + // drag session + if (mSlots && mSlots->mIsDragging) + mScratchArray.AppendElement(nsGkAtoms::dragSession); + + if (aRowIndex != -1) { + if (aRowIndex == mMouseOverRow) + mScratchArray.AppendElement(nsGkAtoms::hover); + + nsCOMPtr<nsITreeSelection> selection; + mView->GetSelection(getter_AddRefs(selection)); + + if (selection) { + // selected + bool isSelected; + selection->IsSelected(aRowIndex, &isSelected); + if (isSelected) + mScratchArray.AppendElement(nsGkAtoms::selected); + + // current + int32_t currentIndex; + selection->GetCurrentIndex(¤tIndex); + if (aRowIndex == currentIndex) + mScratchArray.AppendElement(nsGkAtoms::current); + + // active + if (aCol) { + nsCOMPtr<nsITreeColumn> currentColumn; + selection->GetCurrentColumn(getter_AddRefs(currentColumn)); + if (aCol == currentColumn) + mScratchArray.AppendElement(nsGkAtoms::active); + } + } + + // container or leaf + bool isContainer = false; + mView->IsContainer(aRowIndex, &isContainer); + if (isContainer) { + mScratchArray.AppendElement(nsGkAtoms::container); + + // open or closed + bool isOpen = false; + mView->IsContainerOpen(aRowIndex, &isOpen); + if (isOpen) + mScratchArray.AppendElement(nsGkAtoms::open); + else + mScratchArray.AppendElement(nsGkAtoms::closed); + } + else { + mScratchArray.AppendElement(nsGkAtoms::leaf); + } + + // drop orientation + if (mSlots && mSlots->mDropAllowed && mSlots->mDropRow == aRowIndex) { + if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) + mScratchArray.AppendElement(nsGkAtoms::dropBefore); + else if (mSlots->mDropOrient == nsITreeView::DROP_ON) + mScratchArray.AppendElement(nsGkAtoms::dropOn); + else if (mSlots->mDropOrient == nsITreeView::DROP_AFTER) + mScratchArray.AppendElement(nsGkAtoms::dropAfter); + } + + // odd or even + if (aRowIndex % 2) + mScratchArray.AppendElement(nsGkAtoms::odd); + else + mScratchArray.AppendElement(nsGkAtoms::even); + + nsIContent* baseContent = GetBaseElement(); + if (baseContent && baseContent->HasAttr(kNameSpaceID_None, nsGkAtoms::editing)) + mScratchArray.AppendElement(nsGkAtoms::editing); + + // multiple columns + if (mColumns->GetColumnAt(1)) + mScratchArray.AppendElement(nsGkAtoms::multicol); + } + + if (aCol) { + mScratchArray.AppendElement(aCol->GetAtom()); + + if (aCol->IsPrimary()) + mScratchArray.AppendElement(nsGkAtoms::primary); + + if (aCol->GetType() == nsITreeColumn::TYPE_CHECKBOX) { + mScratchArray.AppendElement(nsGkAtoms::checkbox); + + if (aRowIndex != -1) { + nsAutoString value; + mView->GetCellValue(aRowIndex, aCol, value); + if (value.EqualsLiteral("true")) + mScratchArray.AppendElement(nsGkAtoms::checked); + } + } + else if (aCol->GetType() == nsITreeColumn::TYPE_PROGRESSMETER) { + mScratchArray.AppendElement(nsGkAtoms::progressmeter); + + if (aRowIndex != -1) { + int32_t state; + mView->GetProgressMode(aRowIndex, aCol, &state); + if (state == nsITreeView::PROGRESS_NORMAL) + mScratchArray.AppendElement(nsGkAtoms::progressNormal); + else if (state == nsITreeView::PROGRESS_UNDETERMINED) + mScratchArray.AppendElement(nsGkAtoms::progressUndetermined); + } + } + + // Read special properties from attributes on the column content node + if (aCol->mContent->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::insertbefore, + nsGkAtoms::_true, eCaseMatters)) + mScratchArray.AppendElement(nsGkAtoms::insertbefore); + if (aCol->mContent->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::insertafter, + nsGkAtoms::_true, eCaseMatters)) + mScratchArray.AppendElement(nsGkAtoms::insertafter); + } +} + +nsITheme* +nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex, + nsTreeColumn* aColumn, + nsRect& aImageRect, + nsRect& aTwistyRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + nsStyleContext* aTwistyContext) +{ + // The twisty rect extends all the way to the end of the cell. This is incorrect. We need to + // determine the twisty rect's true width. This is done by examining the style context for + // a width first. If it has one, we use that. If it doesn't, we use the image's natural width. + // If the image hasn't loaded and if no width is specified, then we just bail. If there is + // a -moz-appearance involved, adjust the rect by the minimum widget size provided by + // the theme implementation. + aImageRect = GetImageSize(aRowIndex, aColumn, true, aTwistyContext); + if (aImageRect.height > aTwistyRect.height) + aImageRect.height = aTwistyRect.height; + if (aImageRect.width > aTwistyRect.width) + aImageRect.width = aTwistyRect.width; + else + aTwistyRect.width = aImageRect.width; + + bool useTheme = false; + nsITheme *theme = nullptr; + const nsStyleDisplay* twistyDisplayData = aTwistyContext->StyleDisplay(); + if (twistyDisplayData->mAppearance) { + theme = aPresContext->GetTheme(); + if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, twistyDisplayData->mAppearance)) + useTheme = true; + } + + if (useTheme) { + nsIntSize minTwistySizePx(0,0); + bool canOverride = true; + theme->GetMinimumWidgetSize(&aRenderingContext, this, twistyDisplayData->mAppearance, + &minTwistySizePx, &canOverride); + + // GMWS() returns size in pixels, we need to convert it back to app units + nsSize minTwistySize; + minTwistySize.width = aPresContext->DevPixelsToAppUnits(minTwistySizePx.width); + minTwistySize.height = aPresContext->DevPixelsToAppUnits(minTwistySizePx.height); + + if (aTwistyRect.width < minTwistySize.width || !canOverride) + aTwistyRect.width = minTwistySize.width; + } + + return useTheme ? theme : nullptr; +} + +nsresult +nsTreeBodyFrame::GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, + nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult) +{ + *aResult = nullptr; + + nsAutoString imageSrc; + mView->GetImageSrc(aRowIndex, aCol, imageSrc); + nsRefPtr<imgRequestProxy> styleRequest; + if (!aUseContext && !imageSrc.IsEmpty()) { + aAllowImageRegions = false; + } + else { + // Obtain the URL from the style context. + aAllowImageRegions = true; + styleRequest = aStyleContext->StyleList()->GetListStyleImage(); + if (!styleRequest) + return NS_OK; + nsCOMPtr<nsIURI> uri; + styleRequest->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetSpec(spec); + CopyUTF8toUTF16(spec, imageSrc); + } + + // Look the image up in our cache. + nsTreeImageCacheEntry entry; + if (mImageCache.Get(imageSrc, &entry)) { + // Find out if the image has loaded. + uint32_t status; + imgIRequest *imgReq = entry.request; + imgReq->GetImageStatus(&status); + imgReq->GetImage(aResult); // We hand back the image here. The GetImage call addrefs *aResult. + bool animated = true; // Assuming animated is the safe option + + // We can only call GetAnimated if we're decoded + if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE)) + (*aResult)->GetAnimated(&animated); + + if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) { + // We either aren't done loading, or we're animating. Add our row as a listener for invalidations. + nsCOMPtr<imgINotificationObserver> obs; + imgReq->GetNotificationObserver(getter_AddRefs(obs)); + + if (obs) { + static_cast<nsTreeImageListener*> (obs.get())->AddCell(aRowIndex, aCol); + } + + return NS_OK; + } + } + + if (!*aResult) { + // Create a new nsTreeImageListener object and pass it our row and column + // information. + nsTreeImageListener* listener = new nsTreeImageListener(this); + if (!listener) + return NS_ERROR_OUT_OF_MEMORY; + + if (!mCreatedListeners.PutEntry(listener)) { + return NS_ERROR_FAILURE; + } + + listener->AddCell(aRowIndex, aCol); + nsCOMPtr<imgINotificationObserver> imgNotificationObserver = listener; + + nsRefPtr<imgRequestProxy> imageRequest; + if (styleRequest) { + styleRequest->Clone(imgNotificationObserver, getter_AddRefs(imageRequest)); + } else { + nsIDocument* doc = mContent->GetDocument(); + if (!doc) + // The page is currently being torn down. Why bother. + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI(); + + nsCOMPtr<nsIURI> srcURI; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcURI), + imageSrc, + doc, + baseURI); + if (!srcURI) + return NS_ERROR_FAILURE; + + // XXXbz what's the origin principal for this stuff that comes from our + // view? I guess we should assume that it's the node's principal... + if (nsContentUtils::CanLoadImage(srcURI, mContent, doc, + mContent->NodePrincipal())) { + nsresult rv = nsContentUtils::LoadImage(srcURI, + doc, + mContent->NodePrincipal(), + doc->GetDocumentURI(), + imgNotificationObserver, + nsIRequest::LOAD_NORMAL, + getter_AddRefs(imageRequest)); + NS_ENSURE_SUCCESS(rv, rv); + + } + } + listener->UnsuppressInvalidation(); + + if (!imageRequest) + return NS_ERROR_FAILURE; + + // We don't want discarding/decode-on-draw for xul images + imageRequest->StartDecoding(); + imageRequest->LockImage(); + + // In a case it was already cached. + imageRequest->GetImage(aResult); + nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver); + mImageCache.Put(imageSrc, cacheEntry); + } + return NS_OK; +} + +nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, + nsStyleContext* aStyleContext) +{ + // XXX We should respond to visibility rules for collapsed vs. hidden. + + // This method returns the width of the twisty INCLUDING borders and padding. + // It first checks the style context for a width. If none is found, it tries to + // use the default image width for the twisty. If no image is found, it defaults + // to border+padding. + nsRect r(0,0,0,0); + nsMargin bp(0,0,0,0); + GetBorderPadding(aStyleContext, bp); + r.Inflate(bp); + + // Now r contains our border+padding info. We now need to get our width and + // height. + bool needWidth = false; + bool needHeight = false; + + // We have to load image even though we already have a size. + // Don't change this, otherwise things start to go crazy. + bool useImageRegion = true; + nsCOMPtr<imgIContainer> image; + GetImage(aRowIndex, aCol, aUseContext, aStyleContext, useImageRegion, getter_AddRefs(image)); + + const nsStylePosition* myPosition = aStyleContext->StylePosition(); + const nsStyleList* myList = aStyleContext->StyleList(); + + if (useImageRegion) { + r.x += myList->mImageRegion.x; + r.y += myList->mImageRegion.y; + } + + if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) { + int32_t val = myPosition->mWidth.GetCoordValue(); + r.width += val; + } + else if (useImageRegion && myList->mImageRegion.width > 0) + r.width += myList->mImageRegion.width; + else + needWidth = true; + + if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) { + int32_t val = myPosition->mHeight.GetCoordValue(); + r.height += val; + } + else if (useImageRegion && myList->mImageRegion.height > 0) + r.height += myList->mImageRegion.height; + else + needHeight = true; + + if (image) { + if (needWidth || needHeight) { + // Get the natural image size. + + if (needWidth) { + // Get the size from the image. + nscoord width; + image->GetWidth(&width); + r.width += nsPresContext::CSSPixelsToAppUnits(width); + } + + if (needHeight) { + nscoord height; + image->GetHeight(&height); + r.height += nsPresContext::CSSPixelsToAppUnits(height); + } + } + } + + return r; +} + +// GetImageDestSize returns the destination size of the image. +// The width and height do not include borders and padding. +// The width and height have not been adjusted to fit in the row height +// or cell width. +// The width and height reflect the destination size specified in CSS, +// or the image region specified in CSS, or the natural size of the +// image. +// If only the destination width has been specified in CSS, the height is +// calculated to maintain the aspect ratio of the image. +// If only the destination height has been specified in CSS, the width is +// calculated to maintain the aspect ratio of the image. +nsSize +nsTreeBodyFrame::GetImageDestSize(nsStyleContext* aStyleContext, + bool useImageRegion, + imgIContainer* image) +{ + nsSize size(0,0); + + // We need to get the width and height. + bool needWidth = false; + bool needHeight = false; + + // Get the style position to see if the CSS has specified the + // destination width/height. + const nsStylePosition* myPosition = aStyleContext->StylePosition(); + + if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) { + // CSS has specified the destination width. + size.width = myPosition->mWidth.GetCoordValue(); + } + else { + // We'll need to get the width of the image/region. + needWidth = true; + } + + if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) { + // CSS has specified the destination height. + size.height = myPosition->mHeight.GetCoordValue(); + } + else { + // We'll need to get the height of the image/region. + needHeight = true; + } + + if (needWidth || needHeight) { + // We need to get the size of the image/region. + nsSize imageSize(0,0); + + const nsStyleList* myList = aStyleContext->StyleList(); + + if (useImageRegion && myList->mImageRegion.width > 0) { + // CSS has specified an image region. + // Use the width of the image region. + imageSize.width = myList->mImageRegion.width; + } + else if (image) { + nscoord width; + image->GetWidth(&width); + imageSize.width = nsPresContext::CSSPixelsToAppUnits(width); + } + + if (useImageRegion && myList->mImageRegion.height > 0) { + // CSS has specified an image region. + // Use the height of the image region. + imageSize.height = myList->mImageRegion.height; + } + else if (image) { + nscoord height; + image->GetHeight(&height); + imageSize.height = nsPresContext::CSSPixelsToAppUnits(height); + } + + if (needWidth) { + if (!needHeight && imageSize.height != 0) { + // The CSS specified the destination height, but not the destination + // width. We need to calculate the width so that we maintain the + // image's aspect ratio. + size.width = imageSize.width * size.height / imageSize.height; + } + else { + size.width = imageSize.width; + } + } + + if (needHeight) { + if (!needWidth && imageSize.width != 0) { + // The CSS specified the destination width, but not the destination + // height. We need to calculate the height so that we maintain the + // image's aspect ratio. + size.height = imageSize.height * size.width / imageSize.width; + } + else { + size.height = imageSize.height; + } + } + } + + return size; +} + +// GetImageSourceRect returns the source rectangle of the image to be +// displayed. +// The width and height reflect the image region specified in CSS, or +// the natural size of the image. +// The width and height do not include borders and padding. +// The width and height do not reflect the destination size specified +// in CSS. +nsRect +nsTreeBodyFrame::GetImageSourceRect(nsStyleContext* aStyleContext, + bool useImageRegion, + imgIContainer* image) +{ + nsRect r(0,0,0,0); + + const nsStyleList* myList = aStyleContext->StyleList(); + + if (useImageRegion && + (myList->mImageRegion.width > 0 || myList->mImageRegion.height > 0)) { + // CSS has specified an image region. + r = myList->mImageRegion; + } + else if (image) { + // Use the actual image size. + nscoord coord; + image->GetWidth(&coord); + r.width = nsPresContext::CSSPixelsToAppUnits(coord); + image->GetHeight(&coord); + r.height = nsPresContext::CSSPixelsToAppUnits(coord); + } + + return r; +} + +int32_t nsTreeBodyFrame::GetRowHeight() +{ + // Look up the correct height. It is equal to the specified height + // + the specified margins. + mScratchArray.Clear(); + nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); + if (rowContext) { + const nsStylePosition* myPosition = rowContext->StylePosition(); + + nscoord minHeight = 0; + if (myPosition->mMinHeight.GetUnit() == eStyleUnit_Coord) + minHeight = myPosition->mMinHeight.GetCoordValue(); + + nscoord height = 0; + if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) + height = myPosition->mHeight.GetCoordValue(); + + if (height < minHeight) + height = minHeight; + + if (height > 0) { + height = nsPresContext::AppUnitsToIntCSSPixels(height); + height += height % 2; + height = nsPresContext::CSSPixelsToAppUnits(height); + + // XXX Check box-sizing to determine if border/padding should augment the height + // Inflate the height by our margins. + nsRect rowRect(0,0,0,height); + nsMargin rowMargin; + rowContext->StyleMargin()->GetMargin(rowMargin); + rowRect.Inflate(rowMargin); + height = rowRect.height; + return height; + } + } + + return nsPresContext::CSSPixelsToAppUnits(18); // As good a default as any. +} + +int32_t nsTreeBodyFrame::GetIndentation() +{ + // Look up the correct indentation. It is equal to the specified indentation width. + mScratchArray.Clear(); + nsStyleContext* indentContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeindentation); + if (indentContext) { + const nsStylePosition* myPosition = indentContext->StylePosition(); + if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) { + nscoord val = myPosition->mWidth.GetCoordValue(); + return val; + } + } + + return nsPresContext::CSSPixelsToAppUnits(16); // As good a default as any. +} + +void nsTreeBodyFrame::CalcInnerBox() +{ + mInnerBox.SetRect(0, 0, mRect.width, mRect.height); + AdjustForBorderPadding(mStyleContext, mInnerBox); +} + +nscoord +nsTreeBodyFrame::CalcHorzWidth(const ScrollParts& aParts) +{ + // Compute the adjustment to the last column. This varies depending on the + // visibility of the columnpicker and the scrollbar. + if (aParts.mColumnsFrame) + mAdjustWidth = mRect.width - aParts.mColumnsFrame->GetRect().width; + else + mAdjustWidth = 0; + + nscoord width = 0; + + // We calculate this from the scrollable frame, so that it + // properly covers all contingencies of what could be + // scrollable (columns, body, etc...) + + if (aParts.mColumnsScrollFrame) { + width = aParts.mColumnsScrollFrame->GetScrollRange().width + + aParts.mColumnsScrollFrame->GetScrollPortRect().width; + } + + // If no horz scrolling periphery is present, then just return our width + if (width == 0) + width = mRect.width; + + return width; +} + +NS_IMETHODIMP +nsTreeBodyFrame::GetCursor(const nsPoint& aPoint, + nsIFrame::Cursor& aCursor) +{ + // Check the GetScriptHandlingObject so we don't end up running code when + // the document is a zombie. + bool dummy; + if (mView && GetContent()->GetCurrentDoc()->GetScriptHandlingObject(dummy)) { + int32_t row; + nsTreeColumn* col; + nsIAtom* child; + GetCellAt(aPoint.x, aPoint.y, &row, &col, &child); + + if (child) { + // Our scratch array is already prefilled. + nsStyleContext* childContext = GetPseudoStyleContext(child); + + FillCursorInformationFromStyle(childContext->StyleUserInterface(), + aCursor); + if (aCursor.mCursor == NS_STYLE_CURSOR_AUTO) + aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; + + return NS_OK; + } + } + + return nsLeafBoxFrame::GetCursor(aPoint, aCursor); +} + +static uint32_t GetDropEffect(nsGUIEvent* aEvent) +{ + NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "wrong event type"); + nsDragEvent* dragEvent = static_cast<nsDragEvent *>(aEvent); + nsContentUtils::SetDataTransferInEvent(dragEvent); + + uint32_t action = 0; + if (dragEvent->dataTransfer) + dragEvent->dataTransfer->GetDropEffectInt(&action); + return action; +} + +NS_IMETHODIMP +nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + if (aEvent->message == NS_MOUSE_ENTER_SYNTH || aEvent->message == NS_MOUSE_MOVE) { + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this); + int32_t xTwips = pt.x - mInnerBox.x; + int32_t yTwips = pt.y - mInnerBox.y; + int32_t newrow = GetRowAt(xTwips, yTwips); + if (mMouseOverRow != newrow) { + // redraw the old and the new row + if (mMouseOverRow != -1) + InvalidateRow(mMouseOverRow); + mMouseOverRow = newrow; + if (mMouseOverRow != -1) + InvalidateRow(mMouseOverRow); + } + } + else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) { + if (mMouseOverRow != -1) { + InvalidateRow(mMouseOverRow); + mMouseOverRow = -1; + } + } + else if (aEvent->message == NS_DRAGDROP_ENTER) { + if (!mSlots) + mSlots = new Slots(); + + // Cache several things we'll need throughout the course of our work. These + // will all get released on a drag exit. + + if (mSlots->mTimer) { + mSlots->mTimer->Cancel(); + mSlots->mTimer = nullptr; + } + + // Cache the drag session. + mSlots->mIsDragging = true; + mSlots->mDropRow = -1; + mSlots->mDropOrient = -1; + mSlots->mDragAction = GetDropEffect(aEvent); + } + else if (aEvent->message == NS_DRAGDROP_OVER) { + // The mouse is hovering over this tree. If we determine things are + // different from the last time, invalidate the drop feedback at the old + // position, query the view to see if the current location is droppable, + // and then invalidate the drop feedback at the new location if it is. + // The mouse may or may not have changed position from the last time + // we were called, so optimize out a lot of the extra notifications by + // checking if anything changed first. For drop feedback we use drop, + // dropBefore and dropAfter property. + + if (!mView || !mSlots) + return NS_OK; + + // Save last values, we will need them. + int32_t lastDropRow = mSlots->mDropRow; + int16_t lastDropOrient = mSlots->mDropOrient; +#ifndef XP_MACOSX + int16_t lastScrollLines = mSlots->mScrollLines; +#endif + + // Find out the current drag action + uint32_t lastDragAction = mSlots->mDragAction; + mSlots->mDragAction = GetDropEffect(aEvent); + + // Compute the row mouse is over and the above/below/on state. + // Below we'll use this to see if anything changed. + // Also check if we want to auto-scroll. + ComputeDropPosition(aEvent, &mSlots->mDropRow, &mSlots->mDropOrient, &mSlots->mScrollLines); + + // While we're here, handle tracking of scrolling during a drag. + if (mSlots->mScrollLines) { + if (mSlots->mDropAllowed) { + // Invalidate primary cell at old location. + mSlots->mDropAllowed = false; + InvalidateDropFeedback(lastDropRow, lastDropOrient); + } +#ifdef XP_MACOSX + ScrollByLines(mSlots->mScrollLines); +#else + if (!lastScrollLines) { + // Cancel any previously initialized timer. + if (mSlots->mTimer) { + mSlots->mTimer->Cancel(); + mSlots->mTimer = nullptr; + } + + // Set a timer to trigger the tree scrolling. + CreateTimer(LookAndFeel::eIntID_TreeLazyScrollDelay, + LazyScrollCallback, nsITimer::TYPE_ONE_SHOT, + getter_AddRefs(mSlots->mTimer)); + } +#endif + // Bail out to prevent spring loaded timer and feedback line settings. + return NS_OK; + } + + // If changed from last time, invalidate primary cell at the old location and if allowed, + // invalidate primary cell at the new location. If nothing changed, just bail. + if (mSlots->mDropRow != lastDropRow || + mSlots->mDropOrient != lastDropOrient || + mSlots->mDragAction != lastDragAction) { + + // Invalidate row at the old location. + if (mSlots->mDropAllowed) { + mSlots->mDropAllowed = false; + InvalidateDropFeedback(lastDropRow, lastDropOrient); + } + + if (mSlots->mTimer) { + // Timer is active but for a different row than the current one, kill it. + mSlots->mTimer->Cancel(); + mSlots->mTimer = nullptr; + } + + if (mSlots->mDropRow >= 0) { + if (!mSlots->mTimer && mSlots->mDropOrient == nsITreeView::DROP_ON) { + // Either there wasn't a timer running or it was just killed above. + // If over a folder, start up a timer to open the folder. + bool isContainer = false; + mView->IsContainer(mSlots->mDropRow, &isContainer); + if (isContainer) { + bool isOpen = false; + mView->IsContainerOpen(mSlots->mDropRow, &isOpen); + if (!isOpen) { + // This node isn't expanded, set a timer to expand it. + CreateTimer(LookAndFeel::eIntID_TreeOpenDelay, + OpenCallback, nsITimer::TYPE_ONE_SHOT, + getter_AddRefs(mSlots->mTimer)); + } + } + } + + // The dataTransfer was initialized by the call to GetDropEffect above. + bool canDropAtNewLocation = false; + nsDragEvent* dragEvent = static_cast<nsDragEvent *>(aEvent); + mView->CanDrop(mSlots->mDropRow, mSlots->mDropOrient, + dragEvent->dataTransfer, &canDropAtNewLocation); + + if (canDropAtNewLocation) { + // Invalidate row at the new location. + mSlots->mDropAllowed = canDropAtNewLocation; + InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient); + } + } + } + + // Indicate that the drop is allowed by preventing the default behaviour. + if (mSlots->mDropAllowed) + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } + else if (aEvent->message == NS_DRAGDROP_DROP) { + // this event was meant for another frame, so ignore it + if (!mSlots) + return NS_OK; + + // Tell the view where the drop happened. + + // Remove the drop folder and all its parents from the array. + int32_t parentIndex; + nsresult rv = mView->GetParentIndex(mSlots->mDropRow, &parentIndex); + while (NS_SUCCEEDED(rv) && parentIndex >= 0) { + mSlots->mArray.RemoveElement(parentIndex); + rv = mView->GetParentIndex(parentIndex, &parentIndex); + } + + NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "wrong event type"); + nsDragEvent* dragEvent = static_cast<nsDragEvent*>(aEvent); + nsContentUtils::SetDataTransferInEvent(dragEvent); + + mView->Drop(mSlots->mDropRow, mSlots->mDropOrient, dragEvent->dataTransfer); + mSlots->mDropRow = -1; + mSlots->mDropOrient = -1; + mSlots->mIsDragging = false; + *aEventStatus = nsEventStatus_eConsumeNoDefault; // already handled the drop + } + else if (aEvent->message == NS_DRAGDROP_EXIT) { + // this event was meant for another frame, so ignore it + if (!mSlots) + return NS_OK; + + // Clear out all our tracking vars. + + if (mSlots->mDropAllowed) { + mSlots->mDropAllowed = false; + InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient); + } + else + mSlots->mDropAllowed = false; + mSlots->mIsDragging = false; + mSlots->mScrollLines = 0; + // If a drop is occuring, the exit event will fire just before the drop + // event, so don't reset mDropRow or mDropOrient as these fields are used + // by the drop event. + if (mSlots->mTimer) { + mSlots->mTimer->Cancel(); + mSlots->mTimer = nullptr; + } + + if (!mSlots->mArray.IsEmpty()) { + // Close all spring loaded folders except the drop folder. + CreateTimer(LookAndFeel::eIntID_TreeCloseDelay, + CloseCallback, nsITimer::TYPE_ONE_SHOT, + getter_AddRefs(mSlots->mTimer)); + } + } + + return NS_OK; +} + +static nsLineStyle +ConvertBorderStyleToLineStyle(uint8_t aBorderStyle) +{ + switch (aBorderStyle) { + case NS_STYLE_BORDER_STYLE_DOTTED: + return nsLineStyle_kDotted; + case NS_STYLE_BORDER_STYLE_DASHED: + return nsLineStyle_kDashed; + default: + return nsLineStyle_kSolid; + } +} + +static void +PaintTreeBody(nsIFrame* aFrame, nsRenderingContext* aCtx, + const nsRect& aDirtyRect, nsPoint aPt) +{ + static_cast<nsTreeBodyFrame*>(aFrame)->PaintTreeBody(*aCtx, aDirtyRect, aPt); +} + +// Painting routines +void +nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // REVIEW: why did we paint if we were collapsed? that makes no sense! + if (!IsVisibleForPainting(aBuilder)) + return; // We're invisible. Don't paint. + + // Handles painting our background, border, and outline. + nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); + + // Bail out now if there's no view or we can't run script because the + // document is a zombie + if (!mView || !GetContent()->GetCurrentDoc()->GetWindow()) + return; + + aLists.Content()->AppendNewToTop(new (aBuilder) + nsDisplayGeneric(aBuilder, this, ::PaintTreeBody, "XULTreeBody", + nsDisplayItem::TYPE_XUL_TREE_BODY)); +} + +void +nsTreeBodyFrame::PaintTreeBody(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt) +{ + // Update our available height and our page count. + CalcInnerBox(); + aRenderingContext.PushState(); + aRenderingContext.IntersectClip(mInnerBox + aPt); + int32_t oldPageCount = mPageLength; + if (!mHasFixedRowCount) + mPageLength = mInnerBox.height/mRowHeight; + + if (oldPageCount != mPageLength || mHorzWidth != CalcHorzWidth(GetScrollParts())) { + // Schedule a ResizeReflow that will update our info properly. + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); + } +#ifdef DEBUG + int32_t rowCount = mRowCount; + mView->GetRowCount(&rowCount); + NS_WARN_IF_FALSE(mRowCount == rowCount, "row count changed unexpectedly"); +#endif + + // Loop through our columns and paint them (e.g., for sorting). This is only + // relevant when painting backgrounds, since columns contain no content. Content + // is contained in the rows. + for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; + currCol = currCol->GetNext()) { + nsRect colRect; + nsresult rv = currCol->GetRect(this, mInnerBox.y, mInnerBox.height, + &colRect); + // Don't paint hidden columns. + if (NS_FAILED(rv) || colRect.width == 0) continue; + + if (OffsetForHorzScroll(colRect, false)) { + nsRect dirtyRect; + colRect += aPt; + if (dirtyRect.IntersectRect(aDirtyRect, colRect)) { + PaintColumn(currCol, colRect, PresContext(), aRenderingContext, aDirtyRect); + } + } + } + // Loop through our on-screen rows. + for (int32_t i = mTopRowIndex; i < mRowCount && i <= mTopRowIndex+mPageLength; i++) { + nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*(i-mTopRowIndex), mInnerBox.width, mRowHeight); + nsRect dirtyRect; + if (dirtyRect.IntersectRect(aDirtyRect, rowRect + aPt) && + rowRect.y < (mInnerBox.y+mInnerBox.height)) { + PaintRow(i, rowRect + aPt, PresContext(), aRenderingContext, aDirtyRect, aPt); + } + } + + if (mSlots && mSlots->mDropAllowed && (mSlots->mDropOrient == nsITreeView::DROP_BEFORE || + mSlots->mDropOrient == nsITreeView::DROP_AFTER)) { + nscoord yPos = mInnerBox.y + mRowHeight * (mSlots->mDropRow - mTopRowIndex) - mRowHeight / 2; + nsRect feedbackRect(mInnerBox.x, yPos, mInnerBox.width, mRowHeight); + if (mSlots->mDropOrient == nsITreeView::DROP_AFTER) + feedbackRect.y += mRowHeight; + + nsRect dirtyRect; + feedbackRect += aPt; + if (dirtyRect.IntersectRect(aDirtyRect, feedbackRect)) { + PaintDropFeedback(feedbackRect, PresContext(), aRenderingContext, aDirtyRect, aPt); + } + } + aRenderingContext.PopState(); +} + + + +void +nsTreeBodyFrame::PaintColumn(nsTreeColumn* aColumn, + const nsRect& aColumnRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + // Now obtain the properties for our cell. + PrefillPropertyArray(-1, aColumn); + nsAutoString properties; + mView->GetColumnProperties(aColumn, properties); + nsTreeUtils::TokenizeProperties(properties, mScratchArray); + + // Resolve style for the column. It contains all the info we need to lay ourselves + // out and to paint. + nsStyleContext* colContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecolumn); + + // Obtain the margins for the cell and then deflate our rect by that + // amount. The cell is assumed to be contained within the deflated rect. + nsRect colRect(aColumnRect); + nsMargin colMargin; + colContext->StyleMargin()->GetMargin(colMargin); + colRect.Deflate(colMargin); + + PaintBackgroundLayer(colContext, aPresContext, aRenderingContext, colRect, aDirtyRect); +} + +void +nsTreeBodyFrame::PaintRow(int32_t aRowIndex, + const nsRect& aRowRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt) +{ + // We have been given a rect for our row. We treat this row like a full-blown + // frame, meaning that it can have borders, margins, padding, and a background. + + // Without a view, we have no data. Check for this up front. + if (!mView) + return; + + nsresult rv; + + // Now obtain the properties for our row. + // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused + PrefillPropertyArray(aRowIndex, nullptr); + + nsAutoString properties; + mView->GetRowProperties(aRowIndex, properties); + nsTreeUtils::TokenizeProperties(properties, mScratchArray); + + // Resolve style for the row. It contains all the info we need to lay ourselves + // out and to paint. + nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); + + // Obtain the margins for the row and then deflate our rect by that + // amount. The row is assumed to be contained within the deflated rect. + nsRect rowRect(aRowRect); + nsMargin rowMargin; + rowContext->StyleMargin()->GetMargin(rowMargin); + rowRect.Deflate(rowMargin); + + // Paint our borders and background for our row rect. + // If a -moz-appearance is provided, use theme drawing only if the current row + // is not selected (since we draw the selection as part of drawing the background). + bool useTheme = false; + nsITheme *theme = nullptr; + const nsStyleDisplay* displayData = rowContext->StyleDisplay(); + if (displayData->mAppearance) { + theme = aPresContext->GetTheme(); + if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, displayData->mAppearance)) + useTheme = true; + } + bool isSelected = false; + nsCOMPtr<nsITreeSelection> selection; + mView->GetSelection(getter_AddRefs(selection)); + if (selection) + selection->IsSelected(aRowIndex, &isSelected); + if (useTheme && !isSelected) { + nsRect dirty; + dirty.IntersectRect(rowRect, aDirtyRect); + theme->DrawWidgetBackground(&aRenderingContext, this, + displayData->mAppearance, rowRect, dirty); + } else { + PaintBackgroundLayer(rowContext, aPresContext, aRenderingContext, rowRect, aDirtyRect); + } + + // Adjust the rect for its border and padding. + nsRect originalRowRect = rowRect; + AdjustForBorderPadding(rowContext, rowRect); + + bool isSeparator = false; + mView->IsSeparator(aRowIndex, &isSeparator); + if (isSeparator) { + // The row is a separator. + + nscoord primaryX = rowRect.x; + nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn(); + if (primaryCol) { + // Paint the primary cell. + nsRect cellRect; + rv = primaryCol->GetRect(this, rowRect.y, rowRect.height, &cellRect); + if (NS_FAILED(rv)) { + NS_NOTREACHED("primary column is invalid"); + return; + } + + if (OffsetForHorzScroll(cellRect, false)) { + cellRect.x += aPt.x; + nsRect dirtyRect; + nsRect checkRect(cellRect.x, originalRowRect.y, + cellRect.width, originalRowRect.height); + if (dirtyRect.IntersectRect(aDirtyRect, checkRect)) + PaintCell(aRowIndex, primaryCol, cellRect, aPresContext, + aRenderingContext, aDirtyRect, primaryX, aPt); + } + + // Paint the left side of the separator. + nscoord currX; + nsTreeColumn* previousCol = primaryCol->GetPrevious(); + if (previousCol) { + nsRect prevColRect; + rv = previousCol->GetRect(this, 0, 0, &prevColRect); + if (NS_SUCCEEDED(rv)) { + currX = (prevColRect.x - mHorzPosition) + prevColRect.width + aPt.x; + } else { + NS_NOTREACHED("The column before the primary column is invalid"); + currX = rowRect.x; + } + } else { + currX = rowRect.x; + } + + int32_t level; + mView->GetLevel(aRowIndex, &level); + if (level == 0) + currX += mIndentation; + + if (currX > rowRect.x) { + nsRect separatorRect(rowRect); + separatorRect.width -= rowRect.x + rowRect.width - currX; + PaintSeparator(aRowIndex, separatorRect, aPresContext, aRenderingContext, aDirtyRect); + } + } + + // Paint the right side (whole) separator. + nsRect separatorRect(rowRect); + if (primaryX > rowRect.x) { + separatorRect.width -= primaryX - rowRect.x; + separatorRect.x += primaryX - rowRect.x; + } + PaintSeparator(aRowIndex, separatorRect, aPresContext, aRenderingContext, aDirtyRect); + } + else { + // Now loop over our cells. Only paint a cell if it intersects with our dirty rect. + for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; + currCol = currCol->GetNext()) { + nsRect cellRect; + rv = currCol->GetRect(this, rowRect.y, rowRect.height, &cellRect); + // Don't paint cells in hidden columns. + if (NS_FAILED(rv) || cellRect.width == 0) + continue; + + if (OffsetForHorzScroll(cellRect, false)) { + cellRect.x += aPt.x; + + // for primary columns, use the row's vertical size so that the + // lines get drawn properly + nsRect checkRect = cellRect; + if (currCol->IsPrimary()) + checkRect = nsRect(cellRect.x, originalRowRect.y, + cellRect.width, originalRowRect.height); + + nsRect dirtyRect; + nscoord dummy; + if (dirtyRect.IntersectRect(aDirtyRect, checkRect)) + PaintCell(aRowIndex, currCol, cellRect, aPresContext, + aRenderingContext, aDirtyRect, dummy, aPt); + } + } + } +} + +void +nsTreeBodyFrame::PaintSeparator(int32_t aRowIndex, + const nsRect& aSeparatorRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + // Resolve style for the separator. + nsStyleContext* separatorContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeseparator); + bool useTheme = false; + nsITheme *theme = nullptr; + const nsStyleDisplay* displayData = separatorContext->StyleDisplay(); + if ( displayData->mAppearance ) { + theme = aPresContext->GetTheme(); + if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, displayData->mAppearance)) + useTheme = true; + } + + // use -moz-appearance if provided. + if (useTheme) { + nsRect dirty; + dirty.IntersectRect(aSeparatorRect, aDirtyRect); + theme->DrawWidgetBackground(&aRenderingContext, this, + displayData->mAppearance, aSeparatorRect, dirty); + } + else { + const nsStylePosition* stylePosition = separatorContext->StylePosition(); + + // Obtain the height for the separator or use the default value. + nscoord height; + if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord) + height = stylePosition->mHeight.GetCoordValue(); + else { + // Use default height 2px. + height = nsPresContext::CSSPixelsToAppUnits(2); + } + + // Obtain the margins for the separator and then deflate our rect by that + // amount. The separator is assumed to be contained within the deflated rect. + nsRect separatorRect(aSeparatorRect.x, aSeparatorRect.y, aSeparatorRect.width, height); + nsMargin separatorMargin; + separatorContext->StyleMargin()->GetMargin(separatorMargin); + separatorRect.Deflate(separatorMargin); + + // Center the separator. + separatorRect.y += (aSeparatorRect.height - height) / 2; + + PaintBackgroundLayer(separatorContext, aPresContext, aRenderingContext, separatorRect, aDirtyRect); + } +} + +void +nsTreeBodyFrame::PaintCell(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aCellRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aCurrX, + nsPoint aPt) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + // Now obtain the properties for our cell. + // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused, and the col ID. + PrefillPropertyArray(aRowIndex, aColumn); + nsAutoString properties; + mView->GetCellProperties(aRowIndex, aColumn, properties); + nsTreeUtils::TokenizeProperties(properties, mScratchArray); + + // Resolve style for the cell. It contains all the info we need to lay ourselves + // out and to paint. + nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); + + bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + + // Obtain the margins for the cell and then deflate our rect by that + // amount. The cell is assumed to be contained within the deflated rect. + nsRect cellRect(aCellRect); + nsMargin cellMargin; + cellContext->StyleMargin()->GetMargin(cellMargin); + cellRect.Deflate(cellMargin); + + // Paint our borders and background for our row rect. + PaintBackgroundLayer(cellContext, aPresContext, aRenderingContext, cellRect, aDirtyRect); + + // Adjust the rect for its border and padding. + AdjustForBorderPadding(cellContext, cellRect); + + nscoord currX = cellRect.x; + nscoord remainingWidth = cellRect.width; + + // Now we paint the contents of the cells. + // Directionality of the tree determines the order in which we paint. + // NS_STYLE_DIRECTION_LTR means paint from left to right. + // NS_STYLE_DIRECTION_RTL means paint from right to left. + + if (aColumn->IsPrimary()) { + // If we're the primary column, we need to indent and paint the twisty and any connecting lines + // between siblings. + + int32_t level; + mView->GetLevel(aRowIndex, &level); + + if (!isRTL) + currX += mIndentation * level; + remainingWidth -= mIndentation * level; + + // Resolve the style to use for the connecting lines. + nsStyleContext* lineContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeline); + + if (mIndentation && level && + lineContext->StyleVisibility()->IsVisibleOrCollapsed()) { + // Paint the thread lines. + + // Get the size of the twisty. We don't want to paint the twisty + // before painting of connecting lines since it would paint lines over + // the twisty. But we need to leave a place for it. + nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); + + nsRect imageSize; + nsRect twistyRect(aCellRect); + GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, aPresContext, + aRenderingContext, twistyContext); + + nsMargin twistyMargin; + twistyContext->StyleMargin()->GetMargin(twistyMargin); + twistyRect.Inflate(twistyMargin); + + aRenderingContext.PushState(); + + const nsStyleBorder* borderStyle = lineContext->StyleBorder(); + nscolor color; + bool foreground; + borderStyle->GetBorderColor(NS_SIDE_LEFT, color, foreground); + if (foreground) { + // GetBorderColor didn't touch color, thus grab it from the treeline context + color = lineContext->StyleColor()->mColor; + } + aRenderingContext.SetColor(color); + uint8_t style; + style = borderStyle->GetBorderStyle(NS_SIDE_LEFT); + aRenderingContext.SetLineStyle(ConvertBorderStyleToLineStyle(style)); + + nscoord srcX = currX + twistyRect.width - mIndentation / 2; + nscoord lineY = (aRowIndex - mTopRowIndex) * mRowHeight + aPt.y; + + // Don't paint off our cell. + if (srcX <= cellRect.x + cellRect.width) { + nscoord destX = currX + twistyRect.width; + if (destX > cellRect.x + cellRect.width) + destX = cellRect.x + cellRect.width; + if (isRTL) { + srcX = currX + remainingWidth - (srcX - cellRect.x); + destX = currX + remainingWidth - (destX - cellRect.x); + } + aRenderingContext.DrawLine(srcX, lineY + mRowHeight / 2, destX, lineY + mRowHeight / 2); + } + + int32_t currentParent = aRowIndex; + for (int32_t i = level; i > 0; i--) { + if (srcX <= cellRect.x + cellRect.width) { + // Paint full vertical line only if we have next sibling. + bool hasNextSibling; + mView->HasNextSibling(currentParent, aRowIndex, &hasNextSibling); + if (hasNextSibling) + aRenderingContext.DrawLine(srcX, lineY, srcX, lineY + mRowHeight); + else if (i == level) + aRenderingContext.DrawLine(srcX, lineY, srcX, lineY + mRowHeight / 2); + } + + int32_t parent; + if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) || parent < 0) + break; + currentParent = parent; + srcX -= mIndentation; + } + + aRenderingContext.PopState(); + } + + // Always leave space for the twisty. + nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height); + PaintTwisty(aRowIndex, aColumn, twistyRect, aPresContext, aRenderingContext, aDirtyRect, + remainingWidth, currX); + } + + // Now paint the icon for our cell. + nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height); + nsRect dirtyRect; + if (dirtyRect.IntersectRect(aDirtyRect, iconRect)) + PaintImage(aRowIndex, aColumn, iconRect, aPresContext, aRenderingContext, aDirtyRect, + remainingWidth, currX); + + // Now paint our element, but only if we aren't a cycler column. + // XXX until we have the ability to load images, allow the view to + // insert text into cycler columns... + if (!aColumn->IsCycler()) { + nsRect elementRect(currX, cellRect.y, remainingWidth, cellRect.height); + nsRect dirtyRect; + if (dirtyRect.IntersectRect(aDirtyRect, elementRect)) { + bool textRTL = cellContext->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + switch (aColumn->GetType()) { + case nsITreeColumn::TYPE_TEXT: + PaintText(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect, currX, + textRTL); + break; + case nsITreeColumn::TYPE_CHECKBOX: + PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect); + break; + case nsITreeColumn::TYPE_PROGRESSMETER: + int32_t state; + mView->GetProgressMode(aRowIndex, aColumn, &state); + switch (state) { + case nsITreeView::PROGRESS_NORMAL: + case nsITreeView::PROGRESS_UNDETERMINED: + PaintProgressMeter(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect); + break; + case nsITreeView::PROGRESS_NONE: + default: + PaintText(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect, currX, + textRTL); + break; + } + break; + } + } + } + + aCurrX = currX; +} + +void +nsTreeBodyFrame::PaintTwisty(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aTwistyRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aRemainingWidth, + nscoord& aCurrX) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + nscoord rightEdge = aCurrX + aRemainingWidth; + // Paint the twisty, but only if we are a non-empty container. + bool shouldPaint = false; + bool isContainer = false; + mView->IsContainer(aRowIndex, &isContainer); + if (isContainer) { + bool isContainerEmpty = false; + mView->IsContainerEmpty(aRowIndex, &isContainerEmpty); + if (!isContainerEmpty) + shouldPaint = true; + } + + // Resolve style for the twisty. + nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); + + // Obtain the margins for the twisty and then deflate our rect by that + // amount. The twisty is assumed to be contained within the deflated rect. + nsRect twistyRect(aTwistyRect); + nsMargin twistyMargin; + twistyContext->StyleMargin()->GetMargin(twistyMargin); + twistyRect.Deflate(twistyMargin); + + nsRect imageSize; + nsITheme* theme = GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, + aPresContext, aRenderingContext, twistyContext); + + // Subtract out the remaining width. This is done even when we don't actually paint a twisty in + // this cell, so that cells in different rows still line up. + nsRect copyRect(twistyRect); + copyRect.Inflate(twistyMargin); + aRemainingWidth -= copyRect.width; + if (!isRTL) + aCurrX += copyRect.width; + + if (shouldPaint) { + // Paint our borders and background for our image rect. + PaintBackgroundLayer(twistyContext, aPresContext, aRenderingContext, twistyRect, aDirtyRect); + + if (theme) { + if (isRTL) + twistyRect.x = rightEdge - twistyRect.width; + // yeah, I know it says we're drawing a background, but a twisty is really a fg + // object since it doesn't have anything that gecko would want to draw over it. Besides, + // we have to prevent imagelib from drawing it. + nsRect dirty; + dirty.IntersectRect(twistyRect, aDirtyRect); + theme->DrawWidgetBackground(&aRenderingContext, this, + twistyContext->StyleDisplay()->mAppearance, twistyRect, dirty); + } + else { + // Time to paint the twisty. + // Adjust the rect for its border and padding. + nsMargin bp(0,0,0,0); + GetBorderPadding(twistyContext, bp); + twistyRect.Deflate(bp); + if (isRTL) + twistyRect.x = rightEdge - twistyRect.width; + imageSize.Deflate(bp); + + // Get the image for drawing. + nsCOMPtr<imgIContainer> image; + bool useImageRegion = true; + GetImage(aRowIndex, aColumn, true, twistyContext, useImageRegion, getter_AddRefs(image)); + if (image) { + nsPoint pt = twistyRect.TopLeft(); + + // Center the image. XXX Obey vertical-align style prop? + if (imageSize.height < twistyRect.height) { + pt.y += (twistyRect.height - imageSize.height)/2; + } + + // Paint the image. + nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image, + gfxPattern::FILTER_NEAREST, pt, &aDirtyRect, + imgIContainer::FLAG_NONE, &imageSize); + } + } + } +} + +void +nsTreeBodyFrame::PaintImage(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aImageRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aRemainingWidth, + nscoord& aCurrX) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + nscoord rightEdge = aCurrX + aRemainingWidth; + // Resolve style for the image. + nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage); + + // Obtain opacity value for the image. + float opacity = imageContext->StyleDisplay()->mOpacity; + + // Obtain the margins for the image and then deflate our rect by that + // amount. The image is assumed to be contained within the deflated rect. + nsRect imageRect(aImageRect); + nsMargin imageMargin; + imageContext->StyleMargin()->GetMargin(imageMargin); + imageRect.Deflate(imageMargin); + + // Get the image. + bool useImageRegion = true; + nsCOMPtr<imgIContainer> image; + GetImage(aRowIndex, aColumn, false, imageContext, useImageRegion, getter_AddRefs(image)); + + // Get the image destination size. + nsSize imageDestSize = GetImageDestSize(imageContext, useImageRegion, image); + if (!imageDestSize.width || !imageDestSize.height) + return; + + // Get the borders and padding. + nsMargin bp(0,0,0,0); + GetBorderPadding(imageContext, bp); + + // destRect will be passed as the aDestRect argument in the DrawImage method. + // Start with the imageDestSize width and height. + nsRect destRect(0, 0, imageDestSize.width, imageDestSize.height); + // Inflate destRect for borders and padding so that we can compare/adjust + // with respect to imageRect. + destRect.Inflate(bp); + + // The destRect width and height have not been adjusted to fit within the + // cell width and height. + // We must adjust the width even if image is null, because the width is used + // to update the aRemainingWidth and aCurrX values. + // Since the height isn't used unless the image is not null, we will adjust + // the height inside the if (image) block below. + + if (destRect.width > imageRect.width) { + // The destRect is too wide to fit within the cell width. + // Adjust destRect width to fit within the cell width. + destRect.width = imageRect.width; + } + else { + // The cell is wider than the destRect. + // In a cycler column, the image is centered horizontally. + if (!aColumn->IsCycler()) { + // If this column is not a cycler, we won't center the image horizontally. + // We adjust the imageRect width so that the image is placed at the start + // of the cell. + imageRect.width = destRect.width; + } + } + + if (image) { + if (isRTL) + imageRect.x = rightEdge - imageRect.width; + // Paint our borders and background for our image rect + PaintBackgroundLayer(imageContext, aPresContext, aRenderingContext, imageRect, aDirtyRect); + + // The destRect x and y have not been set yet. Let's do that now. + // Initially, we use the imageRect x and y. + destRect.x = imageRect.x; + destRect.y = imageRect.y; + + if (destRect.width < imageRect.width) { + // The destRect width is smaller than the cell width. + // Center the image horizontally in the cell. + // Adjust the destRect x accordingly. + destRect.x += (imageRect.width - destRect.width)/2; + } + + // Now it's time to adjust the destRect height to fit within the cell height. + if (destRect.height > imageRect.height) { + // The destRect height is larger than the cell height. + // Adjust destRect height to fit within the cell height. + destRect.height = imageRect.height; + } + else if (destRect.height < imageRect.height) { + // The destRect height is smaller than the cell height. + // Center the image vertically in the cell. + // Adjust the destRect y accordingly. + destRect.y += (imageRect.height - destRect.height)/2; + } + + // It's almost time to paint the image. + // Deflate destRect for the border and padding. + destRect.Deflate(bp); + + // Get the image source rectangle - the rectangle containing the part of + // the image that we are going to display. + // sourceRect will be passed as the aSrcRect argument in the DrawImage method. + nsRect sourceRect = GetImageSourceRect(imageContext, useImageRegion, image); + + // Let's say that the image is 100 pixels tall and + // that the CSS has specified that the destination height should be 50 + // pixels tall. Let's say that the cell height is only 20 pixels. So, in + // those 20 visible pixels, we want to see the top 20/50ths of the image. + // So, the sourceRect.height should be 100 * 20 / 50, which is 40 pixels. + // Essentially, we are scaling the image as dictated by the CSS destination + // height and width, and we are then clipping the scaled image by the cell + // width and height. + nsIntSize rawImageSize; + image->GetWidth(&rawImageSize.width); + image->GetHeight(&rawImageSize.height); + nsRect wholeImageDest = + nsLayoutUtils::GetWholeImageDestination(rawImageSize, sourceRect, + nsRect(destRect.TopLeft(), imageDestSize)); + + gfxContext* ctx = aRenderingContext.ThebesContext(); + if (opacity != 1.0f) { + ctx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA); + } + + nsLayoutUtils::DrawImage(&aRenderingContext, image, + nsLayoutUtils::GetGraphicsFilterForFrame(this), + wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect, + imgIContainer::FLAG_NONE); + + if (opacity != 1.0f) { + ctx->PopGroupToSource(); + ctx->Paint(opacity); + } + } + + // Update the aRemainingWidth and aCurrX values. + imageRect.Inflate(imageMargin); + aRemainingWidth -= imageRect.width; + if (!isRTL) + aCurrX += imageRect.width; +} + +void +nsTreeBodyFrame::PaintText(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aTextRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aCurrX, + bool aTextRTL) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + + // Now obtain the text for our cell. + nsAutoString text; + mView->GetCellText(aRowIndex, aColumn, text); + // We're going to paint this text so we need to ensure bidi is enabled if + // necessary + CheckTextForBidi(text); + + if (text.Length() == 0) + return; // Don't paint an empty string. XXX What about background/borders? Still paint? + + // Resolve style for the text. It contains all the info we need to lay ourselves + // out and to paint. + nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext); + + // Obtain opacity value for the image. + float opacity = textContext->StyleDisplay()->mOpacity; + + // Obtain the margins for the text and then deflate our rect by that + // amount. The text is assumed to be contained within the deflated rect. + nsRect textRect(aTextRect); + nsMargin textMargin; + textContext->StyleMargin()->GetMargin(textMargin); + textRect.Deflate(textMargin); + + // Adjust the rect for its border and padding. + nsMargin bp(0,0,0,0); + GetBorderPadding(textContext, bp); + textRect.Deflate(bp); + + // Compute our text size. + nsRefPtr<nsFontMetrics> fontMet; + nsLayoutUtils::GetFontMetricsForStyleContext(textContext, + getter_AddRefs(fontMet)); + + nscoord height = fontMet->MaxHeight(); + nscoord baseline = fontMet->MaxAscent(); + + // Center the text. XXX Obey vertical-align style prop? + if (height < textRect.height) { + textRect.y += (textRect.height - height)/2; + textRect.height = height; + } + + // Set our font. + aRenderingContext.SetFont(fontMet); + + AdjustForCellText(text, aRowIndex, aColumn, aRenderingContext, textRect); + textRect.Inflate(bp); + + // Subtract out the remaining width. + if (!isRTL) + aCurrX += textRect.width + textMargin.LeftRight(); + + PaintBackgroundLayer(textContext, aPresContext, aRenderingContext, textRect, aDirtyRect); + + // Time to paint our text. + textRect.Deflate(bp); + + // Set our color. + aRenderingContext.SetColor(textContext->StyleColor()->mColor); + + // Draw decorations. + uint8_t decorations = textContext->StyleTextReset()->mTextDecorationLine; + + nscoord offset; + nscoord size; + if (decorations & (NS_FONT_DECORATION_OVERLINE | NS_FONT_DECORATION_UNDERLINE)) { + fontMet->GetUnderline(offset, size); + if (decorations & NS_FONT_DECORATION_OVERLINE) + aRenderingContext.FillRect(textRect.x, textRect.y, textRect.width, size); + if (decorations & NS_FONT_DECORATION_UNDERLINE) + aRenderingContext.FillRect(textRect.x, textRect.y + baseline - offset, textRect.width, size); + } + if (decorations & NS_FONT_DECORATION_LINE_THROUGH) { + fontMet->GetStrikeout(offset, size); + aRenderingContext.FillRect(textRect.x, textRect.y + baseline - offset, textRect.width, size); + } + uint8_t direction = aTextRTL ? NS_STYLE_DIRECTION_RTL : + NS_STYLE_DIRECTION_LTR; + + gfxContext* ctx = aRenderingContext.ThebesContext(); + if (opacity != 1.0f) { + ctx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA); + } + + nsLayoutUtils::DrawString(this, &aRenderingContext, text.get(), text.Length(), + textRect.TopLeft() + nsPoint(0, baseline), direction); + + if (opacity != 1.0f) { + ctx->PopGroupToSource(); + ctx->Paint(opacity); + } + +} + +void +nsTreeBodyFrame::PaintCheckbox(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aCheckboxRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + // Resolve style for the checkbox. + nsStyleContext* checkboxContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecheckbox); + + nscoord rightEdge = aCheckboxRect.XMost(); + + // Obtain the margins for the checkbox and then deflate our rect by that + // amount. The checkbox is assumed to be contained within the deflated rect. + nsRect checkboxRect(aCheckboxRect); + nsMargin checkboxMargin; + checkboxContext->StyleMargin()->GetMargin(checkboxMargin); + checkboxRect.Deflate(checkboxMargin); + + nsRect imageSize = GetImageSize(aRowIndex, aColumn, true, checkboxContext); + + if (imageSize.height > checkboxRect.height) + imageSize.height = checkboxRect.height; + if (imageSize.width > checkboxRect.width) + imageSize.width = checkboxRect.width; + + if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) + checkboxRect.x = rightEdge - checkboxRect.width; + + // Paint our borders and background for our image rect. + PaintBackgroundLayer(checkboxContext, aPresContext, aRenderingContext, checkboxRect, aDirtyRect); + + // Time to paint the checkbox. + // Adjust the rect for its border and padding. + nsMargin bp(0,0,0,0); + GetBorderPadding(checkboxContext, bp); + checkboxRect.Deflate(bp); + + // Get the image for drawing. + nsCOMPtr<imgIContainer> image; + bool useImageRegion = true; + GetImage(aRowIndex, aColumn, true, checkboxContext, useImageRegion, getter_AddRefs(image)); + if (image) { + nsPoint pt = checkboxRect.TopLeft(); + + if (imageSize.height < checkboxRect.height) { + pt.y += (checkboxRect.height - imageSize.height)/2; + } + + if (imageSize.width < checkboxRect.width) { + pt.x += (checkboxRect.width - imageSize.width)/2; + } + + // Paint the image. + nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image, + gfxPattern::FILTER_NEAREST, pt, &aDirtyRect, + imgIContainer::FLAG_NONE, &imageSize); + } +} + +void +nsTreeBodyFrame::PaintProgressMeter(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aProgressMeterRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + // Resolve style for the progress meter. It contains all the info we need + // to lay ourselves out and to paint. + nsStyleContext* meterContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeprogressmeter); + + // Obtain the margins for the progress meter and then deflate our rect by that + // amount. The progress meter is assumed to be contained within the deflated + // rect. + nsRect meterRect(aProgressMeterRect); + nsMargin meterMargin; + meterContext->StyleMargin()->GetMargin(meterMargin); + meterRect.Deflate(meterMargin); + + // Paint our borders and background for our progress meter rect. + PaintBackgroundLayer(meterContext, aPresContext, aRenderingContext, meterRect, aDirtyRect); + + // Time to paint our progress. + int32_t state; + mView->GetProgressMode(aRowIndex, aColumn, &state); + if (state == nsITreeView::PROGRESS_NORMAL) { + // Adjust the rect for its border and padding. + AdjustForBorderPadding(meterContext, meterRect); + + // Set our color. + aRenderingContext.SetColor(meterContext->StyleColor()->mColor); + + // Now obtain the value for our cell. + nsAutoString value; + mView->GetCellValue(aRowIndex, aColumn, value); + + nsresult rv; + int32_t intValue = value.ToInteger(&rv); + if (intValue < 0) + intValue = 0; + else if (intValue > 100) + intValue = 100; + + nscoord meterWidth = NSToCoordRound((float)intValue / 100 * meterRect.width); + if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) + meterRect.x += meterRect.width - meterWidth; // right align + meterRect.width = meterWidth; + bool useImageRegion = true; + nsCOMPtr<imgIContainer> image; + GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image)); + if (image) { + int32_t width, height; + image->GetWidth(&width); + image->GetHeight(&height); + nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(), + height*nsDeviceContext::AppUnitsPerCSSPixel()); + nsLayoutUtils::DrawImage(&aRenderingContext, image, + nsLayoutUtils::GetGraphicsFilterForFrame(this), + nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), + aDirtyRect, imgIContainer::FLAG_NONE); + } else { + aRenderingContext.FillRect(meterRect); + } + } + else if (state == nsITreeView::PROGRESS_UNDETERMINED) { + // Adjust the rect for its border and padding. + AdjustForBorderPadding(meterContext, meterRect); + + bool useImageRegion = true; + nsCOMPtr<imgIContainer> image; + GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image)); + if (image) { + int32_t width, height; + image->GetWidth(&width); + image->GetHeight(&height); + nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(), + height*nsDeviceContext::AppUnitsPerCSSPixel()); + nsLayoutUtils::DrawImage(&aRenderingContext, image, + nsLayoutUtils::GetGraphicsFilterForFrame(this), + nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), + aDirtyRect, imgIContainer::FLAG_NONE); + } + } +} + + +void +nsTreeBodyFrame::PaintDropFeedback(const nsRect& aDropFeedbackRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt) +{ + // Paint the drop feedback in between rows. + + nscoord currX; + + // Adjust for the primary cell. + nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn(); + + if (primaryCol) { +#ifdef DEBUG + nsresult rv = +#endif + primaryCol->GetXInTwips(this, &currX); + NS_ASSERTION(NS_SUCCEEDED(rv), "primary column is invalid?"); + + currX += aPt.x - mHorzPosition; + } else { + currX = aDropFeedbackRect.x; + } + + PrefillPropertyArray(mSlots->mDropRow, primaryCol); + + // Resolve the style to use for the drop feedback. + nsStyleContext* feedbackContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreedropfeedback); + + // Paint only if it is visible. + if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) { + int32_t level; + mView->GetLevel(mSlots->mDropRow, &level); + + // If our previous or next row has greater level use that for + // correct visual indentation. + if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) { + if (mSlots->mDropRow > 0) { + int32_t previousLevel; + mView->GetLevel(mSlots->mDropRow - 1, &previousLevel); + if (previousLevel > level) + level = previousLevel; + } + } + else { + if (mSlots->mDropRow < mRowCount - 1) { + int32_t nextLevel; + mView->GetLevel(mSlots->mDropRow + 1, &nextLevel); + if (nextLevel > level) + level = nextLevel; + } + } + + currX += mIndentation * level; + + if (primaryCol){ + nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); + nsRect imageSize; + nsRect twistyRect; + GetTwistyRect(mSlots->mDropRow, primaryCol, imageSize, twistyRect, aPresContext, + aRenderingContext, twistyContext); + nsMargin twistyMargin; + twistyContext->StyleMargin()->GetMargin(twistyMargin); + twistyRect.Inflate(twistyMargin); + currX += twistyRect.width; + } + + const nsStylePosition* stylePosition = feedbackContext->StylePosition(); + + // Obtain the width for the drop feedback or use default value. + nscoord width; + if (stylePosition->mWidth.GetUnit() == eStyleUnit_Coord) + width = stylePosition->mWidth.GetCoordValue(); + else { + // Use default width 50px. + width = nsPresContext::CSSPixelsToAppUnits(50); + } + + // Obtain the height for the drop feedback or use default value. + nscoord height; + if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord) + height = stylePosition->mHeight.GetCoordValue(); + else { + // Use default height 2px. + height = nsPresContext::CSSPixelsToAppUnits(2); + } + + // Obtain the margins for the drop feedback and then deflate our rect + // by that amount. + nsRect feedbackRect(currX, aDropFeedbackRect.y, width, height); + nsMargin margin; + feedbackContext->StyleMargin()->GetMargin(margin); + feedbackRect.Deflate(margin); + + feedbackRect.y += (aDropFeedbackRect.height - height) / 2; + + // Finally paint the drop feedback. + PaintBackgroundLayer(feedbackContext, aPresContext, aRenderingContext, feedbackRect, aDirtyRect); + } +} + +void +nsTreeBodyFrame::PaintBackgroundLayer(nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aRect, + const nsRect& aDirtyRect) +{ + const nsStyleBorder* myBorder = aStyleContext->StyleBorder(); + + nsCSSRendering::PaintBackgroundWithSC(aPresContext, aRenderingContext, + this, aDirtyRect, aRect, + aStyleContext, *myBorder, + nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES); + + nsCSSRendering::PaintBorderWithStyleBorder(aPresContext, aRenderingContext, + this, aDirtyRect, aRect, + *myBorder, mStyleContext); + + nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this, + aDirtyRect, aRect, aStyleContext); +} + +// Scrolling +nsresult +nsTreeBodyFrame::EnsureRowIsVisible(int32_t aRow) +{ + ScrollParts parts = GetScrollParts(); + nsresult rv = EnsureRowIsVisibleInternal(parts, aRow); + NS_ENSURE_SUCCESS(rv, rv); + UpdateScrollbars(parts); + return rv; +} + +nsresult nsTreeBodyFrame::EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow) +{ + if (!mView || !mPageLength) + return NS_OK; + + if (mTopRowIndex <= aRow && mTopRowIndex+mPageLength > aRow) + return NS_OK; + + if (aRow < mTopRowIndex) + ScrollToRowInternal(aParts, aRow); + else { + // Bring it just on-screen. + int32_t distance = aRow - (mTopRowIndex+mPageLength)+1; + ScrollToRowInternal(aParts, mTopRowIndex+distance); + } + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol) +{ + nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol); + if (!col) + return NS_ERROR_INVALID_ARG; + + ScrollParts parts = GetScrollParts(); + + nscoord result = -1; + nsresult rv; + + nscoord columnPos; + rv = col->GetXInTwips(this, &columnPos); + if(NS_FAILED(rv)) return rv; + + nscoord columnWidth; + rv = col->GetWidthInTwips(this, &columnWidth); + if(NS_FAILED(rv)) return rv; + + // If the start of the column is before the + // start of the horizontal view, then scroll + if (columnPos < mHorzPosition) + result = columnPos; + // If the end of the column is past the end of + // the horizontal view, then scroll + else if ((columnPos + columnWidth) > (mHorzPosition + mInnerBox.width)) + result = ((columnPos + columnWidth) - (mHorzPosition + mInnerBox.width)) + mHorzPosition; + + if (result != -1) { + rv = ScrollHorzInternal(parts, result); + if(NS_FAILED(rv)) return rv; + } + + rv = EnsureRowIsVisibleInternal(parts, aRow); + NS_ENSURE_SUCCESS(rv, rv); + UpdateScrollbars(parts); + return rv; +} + +nsresult +nsTreeBodyFrame::ScrollToCell(int32_t aRow, nsITreeColumn* aCol) +{ + ScrollParts parts = GetScrollParts(); + nsresult rv = ScrollToRowInternal(parts, aRow); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ScrollToColumnInternal(parts, aCol); + NS_ENSURE_SUCCESS(rv, rv); + + UpdateScrollbars(parts); + return rv; +} + +nsresult +nsTreeBodyFrame::ScrollToColumn(nsITreeColumn* aCol) +{ + ScrollParts parts = GetScrollParts(); + nsresult rv = ScrollToColumnInternal(parts, aCol); + NS_ENSURE_SUCCESS(rv, rv); + UpdateScrollbars(parts); + return rv; +} + +nsresult nsTreeBodyFrame::ScrollToColumnInternal(const ScrollParts& aParts, + nsITreeColumn* aCol) +{ + nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol); + if (!col) + return NS_ERROR_INVALID_ARG; + + nscoord x; + nsresult rv = col->GetXInTwips(this, &x); + if (NS_FAILED(rv)) + return rv; + + return ScrollHorzInternal(aParts, x); +} + +nsresult +nsTreeBodyFrame::ScrollToHorizontalPosition(int32_t aHorizontalPosition) +{ + ScrollParts parts = GetScrollParts(); + int32_t position = nsPresContext::CSSPixelsToAppUnits(aHorizontalPosition); + nsresult rv = ScrollHorzInternal(parts, position); + NS_ENSURE_SUCCESS(rv, rv); + UpdateScrollbars(parts); + return rv; +} + +nsresult +nsTreeBodyFrame::ScrollToRow(int32_t aRow) +{ + ScrollParts parts = GetScrollParts(); + nsresult rv = ScrollToRowInternal(parts, aRow); + NS_ENSURE_SUCCESS(rv, rv); + UpdateScrollbars(parts); + return rv; +} + +nsresult nsTreeBodyFrame::ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow) +{ + ScrollInternal(aParts, aRow); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::ScrollByLines(int32_t aNumLines) +{ + if (!mView) + return NS_OK; + + int32_t newIndex = mTopRowIndex + aNumLines; + if (newIndex < 0) + newIndex = 0; + else { + int32_t lastPageTopRow = mRowCount - mPageLength; + if (newIndex > lastPageTopRow) + newIndex = lastPageTopRow; + } + ScrollToRow(newIndex); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::ScrollByPages(int32_t aNumPages) +{ + if (!mView) + return NS_OK; + + int32_t newIndex = mTopRowIndex + aNumPages * mPageLength; + if (newIndex < 0) + newIndex = 0; + else { + int32_t lastPageTopRow = mRowCount - mPageLength; + if (newIndex > lastPageTopRow) + newIndex = lastPageTopRow; + } + ScrollToRow(newIndex); + + return NS_OK; +} + +nsresult +nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts, int32_t aRow) +{ + if (!mView) + return NS_OK; + + int32_t delta = aRow - mTopRowIndex; + + if (delta > 0) { + if (mTopRowIndex == (mRowCount - mPageLength + 1)) + return NS_OK; + } + else { + if (mTopRowIndex == 0) + return NS_OK; + } + + mTopRowIndex += delta; + + Invalidate(); + + PostScrollEvent(); + return NS_OK; +} + +nsresult +nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition) +{ + if (!mView || !aParts.mColumnsScrollFrame || !aParts.mHScrollbar) + return NS_OK; + + if (aPosition == mHorzPosition) + return NS_OK; + + if (aPosition < 0 || aPosition > mHorzWidth) + return NS_OK; + + nsRect bounds = aParts.mColumnsFrame->GetRect(); + if (aPosition > (mHorzWidth - bounds.width)) + aPosition = mHorzWidth - bounds.width; + + mHorzPosition = aPosition; + + Invalidate(); + + // Update the column scroll view + nsWeakFrame weakFrame(this); + aParts.mColumnsScrollFrame->ScrollTo(nsPoint(mHorzPosition, 0), + nsIScrollableFrame::INSTANT); + if (!weakFrame.IsAlive()) { + return NS_ERROR_FAILURE; + } + // And fire off an event about it all + PostScrollEvent(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBodyFrame::ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) +{ + ScrollParts parts = GetScrollParts(); + + if (aScrollbar == parts.mVScrollbar) { + if (aNewIndex > aOldIndex) + ScrollToRowInternal(parts, mTopRowIndex+1); + else if (aNewIndex < aOldIndex) + ScrollToRowInternal(parts, mTopRowIndex-1); + } else { + nsresult rv = ScrollHorzInternal(parts, aNewIndex); + if (NS_FAILED(rv)) return rv; + } + + UpdateScrollbars(parts); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBodyFrame::PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) +{ + ScrollParts parts = GetScrollParts(); + + if (aOldIndex == aNewIndex) + return NS_OK; + + // Vertical Scrollbar + if (parts.mVScrollbar == aScrollbar) { + nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); + + nscoord newrow = aNewIndex/rh; + ScrollInternal(parts, newrow); + // Horizontal Scrollbar + } else if (parts.mHScrollbar == aScrollbar) { + nsresult rv = ScrollHorzInternal(parts, aNewIndex); + if (NS_FAILED(rv)) return rv; + } + + UpdateScrollbars(parts); + return NS_OK; +} + +// The style cache. +nsStyleContext* +nsTreeBodyFrame::GetPseudoStyleContext(nsIAtom* aPseudoElement) +{ + return mStyleCache.GetStyleContext(this, PresContext(), mContent, + mStyleContext, aPseudoElement, + mScratchArray); +} + +// Our comparator for resolving our complex pseudos +bool +nsTreeBodyFrame::PseudoMatches(nsCSSSelector* aSelector) +{ + // Iterate the class list. For each item in the list, see if + // it is contained in our scratch array. If we have a miss, then + // we aren't a match. If all items in the class list are + // present in the scratch array, then we have a match. + nsAtomList* curr = aSelector->mClassList; + while (curr) { + if (!mScratchArray.Contains(curr->mAtom)) + return false; + curr = curr->mNext; + } + return true; +} + +nsIContent* +nsTreeBodyFrame::GetBaseElement() +{ + nsIFrame* parent = GetParent(); + while (parent) { + nsIContent* content = parent->GetContent(); + if (content) { + nsINodeInfo* ni = content->NodeInfo(); + + if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL) || + (ni->Equals(nsGkAtoms::select) && + content->IsHTML())) + return content; + } + + parent = parent->GetParent(); + } + + return nullptr; +} + +nsresult +nsTreeBodyFrame::ClearStyleAndImageCaches() +{ + mStyleCache.Clear(); + mImageCache.EnumerateRead(CancelImageRequest, this); + mImageCache.Clear(); + return NS_OK; +} + +/* virtual */ void +nsTreeBodyFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext); + + // Clear the style cache; the pointers are no longer even valid + mStyleCache.Clear(); + // XXX The following is hacky, but it's not incorrect, + // and appears to fix a few bugs with style changes, like text zoom and + // dpi changes + mIndentation = GetIndentation(); + mRowHeight = GetRowHeight(); + mStringWidth = -1; +} + +bool +nsTreeBodyFrame::OffsetForHorzScroll(nsRect& rect, bool clip) +{ + rect.x -= mHorzPosition; + + // Scrolled out before + if (rect.XMost() <= mInnerBox.x) + return false; + + // Scrolled out after + if (rect.x > mInnerBox.XMost()) + return false; + + if (clip) { + nscoord leftEdge = std::max(rect.x, mInnerBox.x); + nscoord rightEdge = std::min(rect.XMost(), mInnerBox.XMost()); + rect.x = leftEdge; + rect.width = rightEdge - leftEdge; + + // Should have returned false above + NS_ASSERTION(rect.width >= 0, "horz scroll code out of sync"); + } + + return true; +} + +bool +nsTreeBodyFrame::CanAutoScroll(int32_t aRowIndex) +{ + // Check first for partially visible last row. + if (aRowIndex == mRowCount - 1) { + nscoord y = mInnerBox.y + (aRowIndex - mTopRowIndex) * mRowHeight; + if (y < mInnerBox.height && y + mRowHeight > mInnerBox.height) + return true; + } + + if (aRowIndex > 0 && aRowIndex < mRowCount - 1) + return true; + + return false; +} + +// Given a dom event, figure out which row in the tree the mouse is over, +// if we should drop before/after/on that row or we should auto-scroll. +// Doesn't query the content about if the drag is allowable, that's done elsewhere. +// +// For containers, we break up the vertical space of the row as follows: if in +// the topmost 25%, the drop is _before_ the row the mouse is over; if in the +// last 25%, _after_; in the middle 50%, we consider it a drop _on_ the container. +// +// For non-containers, if the mouse is in the top 50% of the row, the drop is +// _before_ and the bottom 50% _after_ +void +nsTreeBodyFrame::ComputeDropPosition(nsGUIEvent* aEvent, int32_t* aRow, int16_t* aOrient, + int16_t* aScrollLines) +{ + *aOrient = -1; + *aScrollLines = 0; + + // Convert the event's point to our coordinates. We want it in + // the coordinates of our inner box's coordinates. + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this); + int32_t xTwips = pt.x - mInnerBox.x; + int32_t yTwips = pt.y - mInnerBox.y; + + *aRow = GetRowAt(xTwips, yTwips); + if (*aRow >=0) { + // Compute the top/bottom of the row in question. + int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex); + + bool isContainer = false; + mView->IsContainer (*aRow, &isContainer); + if (isContainer) { + // for a container, use a 25%/50%/25% breakdown + if (yOffset < mRowHeight / 4) + *aOrient = nsITreeView::DROP_BEFORE; + else if (yOffset > mRowHeight - (mRowHeight / 4)) + *aOrient = nsITreeView::DROP_AFTER; + else + *aOrient = nsITreeView::DROP_ON; + } + else { + // for a non-container use a 50%/50% breakdown + if (yOffset < mRowHeight / 2) + *aOrient = nsITreeView::DROP_BEFORE; + else + *aOrient = nsITreeView::DROP_AFTER; + } + } + + if (CanAutoScroll(*aRow)) { + // Get the max value from the look and feel service. + int32_t scrollLinesMax = + LookAndFeel::GetInt(LookAndFeel::eIntID_TreeScrollLinesMax, 0); + scrollLinesMax--; + if (scrollLinesMax < 0) + scrollLinesMax = 0; + + // Determine if we're w/in a margin of the top/bottom of the tree during a drag. + // This will ultimately cause us to scroll, but that's done elsewhere. + nscoord height = (3 * mRowHeight) / 4; + if (yTwips < height) { + // scroll up + *aScrollLines = NSToIntRound(-scrollLinesMax * (1 - (float)yTwips / height) - 1); + } + else if (yTwips > mRect.height - height) { + // scroll down + *aScrollLines = NSToIntRound(scrollLinesMax * (1 - (float)(mRect.height - yTwips) / height) + 1); + } + } +} // ComputeDropPosition + +void +nsTreeBodyFrame::OpenCallback(nsITimer *aTimer, void *aClosure) +{ + nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure); + if (self) { + aTimer->Cancel(); + self->mSlots->mTimer = nullptr; + + if (self->mSlots->mDropRow >= 0) { + self->mSlots->mArray.AppendElement(self->mSlots->mDropRow); + self->mView->ToggleOpenState(self->mSlots->mDropRow); + } + } +} + +void +nsTreeBodyFrame::CloseCallback(nsITimer *aTimer, void *aClosure) +{ + nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure); + if (self) { + aTimer->Cancel(); + self->mSlots->mTimer = nullptr; + + for (uint32_t i = self->mSlots->mArray.Length(); i--; ) { + if (self->mView) + self->mView->ToggleOpenState(self->mSlots->mArray[i]); + } + self->mSlots->mArray.Clear(); + } +} + +void +nsTreeBodyFrame::LazyScrollCallback(nsITimer *aTimer, void *aClosure) +{ + nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure); + if (self) { + aTimer->Cancel(); + self->mSlots->mTimer = nullptr; + + if (self->mView) { + // Set a new timer to scroll the tree repeatedly. + self->CreateTimer(LookAndFeel::eIntID_TreeScrollDelay, + ScrollCallback, nsITimer::TYPE_REPEATING_SLACK, + getter_AddRefs(self->mSlots->mTimer)); + self->ScrollByLines(self->mSlots->mScrollLines); + // ScrollByLines may have deleted |self|. + } + } +} + +void +nsTreeBodyFrame::ScrollCallback(nsITimer *aTimer, void *aClosure) +{ + nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure); + if (self) { + // Don't scroll if we are already at the top or bottom of the view. + if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) { + self->ScrollByLines(self->mSlots->mScrollLines); + } + else { + aTimer->Cancel(); + self->mSlots->mTimer = nullptr; + } + } +} + +NS_IMETHODIMP +nsTreeBodyFrame::ScrollEvent::Run() +{ + if (mInner) { + mInner->FireScrollEvent(); + } + return NS_OK; +} + + +void +nsTreeBodyFrame::FireScrollEvent() +{ + mScrollEvent.Forget(); + nsScrollbarEvent event(true, NS_SCROLL_EVENT, nullptr); + // scroll events fired at elements don't bubble + event.mFlags.mBubbles = false; + nsEventDispatcher::Dispatch(GetContent(), PresContext(), &event); +} + +void +nsTreeBodyFrame::PostScrollEvent() +{ + if (mScrollEvent.IsPending()) + return; + + nsRefPtr<ScrollEvent> ev = new ScrollEvent(this); + if (NS_FAILED(NS_DispatchToCurrentThread(ev))) { + NS_WARNING("failed to dispatch ScrollEvent"); + } else { + mScrollEvent = ev; + } +} + +void +nsTreeBodyFrame::DetachImageListeners() +{ + mCreatedListeners.Clear(); +} + +void +nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener) +{ + if (aListener) { + mCreatedListeners.RemoveEntry(aListener); + } +} + +#ifdef ACCESSIBILITY +void +nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount) +{ + nsCOMPtr<nsIContent> content(GetBaseElement()); + if (!content) + return; + + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->OwnerDoc()); + if (!domDoc) + return; + + nsCOMPtr<nsIDOMEvent> event; + domDoc->CreateEvent(NS_LITERAL_STRING("datacontainerevents"), + getter_AddRefs(event)); + + nsCOMPtr<nsIDOMDataContainerEvent> treeEvent(do_QueryInterface(event)); + if (!treeEvent) + return; + + event->InitEvent(NS_LITERAL_STRING("TreeRowCountChanged"), true, false); + + // Set 'index' data - the row index rows are changed from. + nsCOMPtr<nsIWritableVariant> indexVariant( + do_CreateInstance("@mozilla.org/variant;1")); + if (!indexVariant) + return; + + indexVariant->SetAsInt32(aIndex); + treeEvent->SetData(NS_LITERAL_STRING("index"), indexVariant); + + // Set 'count' data - the number of changed rows. + nsCOMPtr<nsIWritableVariant> countVariant( + do_CreateInstance("@mozilla.org/variant;1")); + if (!countVariant) + return; + + countVariant->SetAsInt32(aCount); + treeEvent->SetData(NS_LITERAL_STRING("count"), countVariant); + + event->SetTrusted(true); + + nsRefPtr<nsAsyncDOMEvent> plevent = new nsAsyncDOMEvent(content, event); + if (!plevent) + return; + + plevent->PostDOMEvent(); +} + +void +nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx, int32_t aEndRowIdx, + nsITreeColumn *aStartCol, + nsITreeColumn *aEndCol) +{ + nsCOMPtr<nsIContent> content(GetBaseElement()); + if (!content) + return; + + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->OwnerDoc()); + if (!domDoc) + return; + + nsCOMPtr<nsIDOMEvent> event; + domDoc->CreateEvent(NS_LITERAL_STRING("datacontainerevents"), + getter_AddRefs(event)); + + nsCOMPtr<nsIDOMDataContainerEvent> treeEvent(do_QueryInterface(event)); + if (!treeEvent) + return; + + event->InitEvent(NS_LITERAL_STRING("TreeInvalidated"), true, false); + + if (aStartRowIdx != -1 && aEndRowIdx != -1) { + // Set 'startrow' data - the start index of invalidated rows. + nsCOMPtr<nsIWritableVariant> startRowVariant( + do_CreateInstance("@mozilla.org/variant;1")); + if (!startRowVariant) + return; + + startRowVariant->SetAsInt32(aStartRowIdx); + treeEvent->SetData(NS_LITERAL_STRING("startrow"), startRowVariant); + + // Set 'endrow' data - the end index of invalidated rows. + nsCOMPtr<nsIWritableVariant> endRowVariant( + do_CreateInstance("@mozilla.org/variant;1")); + if (!endRowVariant) + return; + + endRowVariant->SetAsInt32(aEndRowIdx); + treeEvent->SetData(NS_LITERAL_STRING("endrow"), endRowVariant); + } + + if (aStartCol && aEndCol) { + // Set 'startcolumn' data - the start index of invalidated rows. + nsCOMPtr<nsIWritableVariant> startColVariant( + do_CreateInstance("@mozilla.org/variant;1")); + if (!startColVariant) + return; + + int32_t startColIdx = 0; + nsresult rv = aStartCol->GetIndex(&startColIdx); + if (NS_FAILED(rv)) + return; + + startColVariant->SetAsInt32(startColIdx); + treeEvent->SetData(NS_LITERAL_STRING("startcolumn"), startColVariant); + + // Set 'endcolumn' data - the start index of invalidated rows. + nsCOMPtr<nsIWritableVariant> endColVariant( + do_CreateInstance("@mozilla.org/variant;1")); + if (!endColVariant) + return; + + int32_t endColIdx = 0; + rv = aEndCol->GetIndex(&endColIdx); + if (NS_FAILED(rv)) + return; + + endColVariant->SetAsInt32(endColIdx); + treeEvent->SetData(NS_LITERAL_STRING("endcolumn"), endColVariant); + } + + event->SetTrusted(true); + + nsRefPtr<nsAsyncDOMEvent> plevent = new nsAsyncDOMEvent(content, event); + if (plevent) + plevent->PostDOMEvent(); +} +#endif + +class nsOverflowChecker : public nsRunnable +{ +public: + nsOverflowChecker(nsTreeBodyFrame* aFrame) : mFrame(aFrame) {} + NS_IMETHOD Run() + { + if (mFrame.IsAlive()) { + nsTreeBodyFrame* tree = static_cast<nsTreeBodyFrame*>(mFrame.GetFrame()); + nsTreeBodyFrame::ScrollParts parts = tree->GetScrollParts(); + tree->CheckOverflow(parts); + } + return NS_OK; + } +private: + nsWeakFrame mFrame; +}; + +bool +nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation) +{ + ScrollParts parts = GetScrollParts(); + nsWeakFrame weakFrame(this); + nsWeakFrame weakColumnsFrame(parts.mColumnsFrame); + UpdateScrollbars(parts); + NS_ENSURE_TRUE(weakFrame.IsAlive(), false); + if (aNeedsFullInvalidation) { + Invalidate(); + } + InvalidateScrollbars(parts, weakColumnsFrame); + NS_ENSURE_TRUE(weakFrame.IsAlive(), false); + nsContentUtils::AddScriptRunner(new nsOverflowChecker(this)); + return weakFrame.IsAlive(); +} + +nsresult +nsTreeBodyFrame::OnImageIsAnimated(imgIRequest* aRequest) +{ + nsLayoutUtils::RegisterImageRequest(PresContext(), + aRequest, nullptr); + + return NS_OK; +} diff --git a/layout/xul/tree/nsTreeBodyFrame.h b/layout/xul/tree/nsTreeBodyFrame.h new file mode 100644 index 000000000..0d1557e52 --- /dev/null +++ b/layout/xul/tree/nsTreeBodyFrame.h @@ -0,0 +1,626 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTreeBodyFrame_h +#define nsTreeBodyFrame_h + +#include "mozilla/Attributes.h" + +#include "nsLeafBoxFrame.h" +#include "nsITreeView.h" +#include "nsICSSPseudoComparator.h" +#include "nsIScrollbarMediator.h" +#include "nsITimer.h" +#include "nsIReflowCallback.h" +#include "nsTArray.h" +#include "nsTreeStyleCache.h" +#include "nsTreeColumns.h" +#include "nsAutoPtr.h" +#include "nsDataHashtable.h" +#include "imgIRequest.h" +#include "imgINotificationObserver.h" +#include "nsScrollbarFrame.h" +#include "nsThreadUtils.h" +#include "mozilla/LookAndFeel.h" +#include "nsIScrollbarOwner.h" + +class nsOverflowChecker; +class nsTreeImageListener; + +namespace mozilla { +namespace layout { +class ScrollbarActivity; +} +} + +// An entry in the tree's image cache +struct nsTreeImageCacheEntry +{ + nsTreeImageCacheEntry() {} + nsTreeImageCacheEntry(imgIRequest *aRequest, imgINotificationObserver *aListener) + : request(aRequest), listener(aListener) {} + + nsCOMPtr<imgIRequest> request; + nsCOMPtr<imgINotificationObserver> listener; +}; + +// The actual frame that paints the cells and rows. +class nsTreeBodyFrame MOZ_FINAL + : public nsLeafBoxFrame + , public nsICSSPseudoComparator + , public nsIScrollbarMediator + , public nsIReflowCallback + , public nsIScrollbarOwner +{ +public: + typedef mozilla::layout::ScrollbarActivity ScrollbarActivity; + + nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + ~nsTreeBodyFrame(); + + NS_DECL_QUERYFRAME_TARGET(nsTreeBodyFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // Callback handler methods for refresh driver based animations. + // Calls to these functions are forwarded from nsTreeImageListener. These + // mirror how nsImageFrame works. + nsresult OnImageIsAnimated(imgIRequest* aRequest); + + // non-virtual signatures like nsITreeBodyFrame + already_AddRefed<nsTreeColumns> Columns() const + { + nsRefPtr<nsTreeColumns> cols = mColumns; + return cols.forget(); + } + already_AddRefed<nsITreeView> GetExistingView() const + { + nsCOMPtr<nsITreeView> view = mView; + return view.forget(); + } + nsresult GetView(nsITreeView **aView); + nsresult SetView(nsITreeView *aView); + nsresult GetFocused(bool *aFocused); + nsresult SetFocused(bool aFocused); + nsresult GetTreeBody(nsIDOMElement **aElement); + nsresult GetRowHeight(int32_t *aValue); + nsresult GetRowWidth(int32_t *aValue); + nsresult GetHorizontalPosition(int32_t *aValue); + nsresult GetSelectionRegion(nsIScriptableRegion **aRegion); + int32_t FirstVisibleRow() const { return mTopRowIndex; } + int32_t LastVisibleRow() const { return mTopRowIndex + mPageLength; } + int32_t PageLength() const { return mPageLength; } + nsresult EnsureRowIsVisible(int32_t aRow); + nsresult EnsureCellIsVisible(int32_t aRow, nsITreeColumn *aCol); + nsresult ScrollToRow(int32_t aRow); + nsresult ScrollByLines(int32_t aNumLines); + nsresult ScrollByPages(int32_t aNumPages); + nsresult ScrollToCell(int32_t aRow, nsITreeColumn *aCol); + nsresult ScrollToColumn(nsITreeColumn *aCol); + nsresult ScrollToHorizontalPosition(int32_t aValue); + nsresult Invalidate(); + nsresult InvalidateColumn(nsITreeColumn *aCol); + nsresult InvalidateRow(int32_t aRow); + nsresult InvalidateCell(int32_t aRow, nsITreeColumn *aCol); + nsresult InvalidateRange(int32_t aStart, int32_t aEnd); + nsresult InvalidateColumnRange(int32_t aStart, int32_t aEnd, + nsITreeColumn *aCol); + nsresult GetRowAt(int32_t aX, int32_t aY, int32_t *aValue); + nsresult GetCellAt(int32_t aX, int32_t aY, int32_t *aRow, + nsITreeColumn **aCol, nsACString &aChildElt); + nsresult GetCoordsForCellItem(int32_t aRow, nsITreeColumn *aCol, + const nsACString &aElt, + int32_t *aX, int32_t *aY, + int32_t *aWidth, int32_t *aHeight); + nsresult IsCellCropped(int32_t aRow, nsITreeColumn *aCol, bool *aResult); + nsresult RowCountChanged(int32_t aIndex, int32_t aCount); + nsresult BeginUpdateBatch(); + nsresult EndUpdateBatch(); + nsresult ClearStyleAndImageCaches(); + + void ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth); + + virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE; + virtual void SetBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, + bool aRemoveOverflowArea = false) MOZ_OVERRIDE; + + // nsIReflowCallback + virtual bool ReflowFinished() MOZ_OVERRIDE; + virtual void ReflowCallbackCanceled() MOZ_OVERRIDE; + + // nsICSSPseudoComparator + virtual bool PseudoMatches(nsCSSSelector* aSelector) MOZ_OVERRIDE; + + // nsIScrollbarMediator + NS_IMETHOD PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) MOZ_OVERRIDE; + NS_IMETHOD ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) MOZ_OVERRIDE; + NS_IMETHOD VisibilityChanged(bool aVisible) MOZ_OVERRIDE { Invalidate(); return NS_OK; } + + // nsIScrollbarOwner + virtual nsIFrame* GetScrollbarBox(bool aVertical) MOZ_OVERRIDE { + ScrollParts parts = GetScrollParts(); + return aVertical ? parts.mVScrollbar : parts.mHScrollbar; + } + + // Overridden from nsIFrame to cache our pres context. + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + NS_IMETHOD GetCursor(const nsPoint& aPoint, + nsIFrame::Cursor& aCursor) MOZ_OVERRIDE; + + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) MOZ_OVERRIDE; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) MOZ_OVERRIDE; + + friend nsIFrame* NS_NewTreeBodyFrame(nsIPresShell* aPresShell); + friend class nsTreeColumn; + + struct ScrollParts { + nsScrollbarFrame* mVScrollbar; + nsCOMPtr<nsIContent> mVScrollbarContent; + nsScrollbarFrame* mHScrollbar; + nsCOMPtr<nsIContent> mHScrollbarContent; + nsIFrame* mColumnsFrame; + nsIScrollableFrame* mColumnsScrollFrame; + }; + + void PaintTreeBody(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt); + + nsITreeBoxObject* GetTreeBoxObject() const { return mTreeBoxObject; } + + // Get the base element, <tree> or <select> + nsIContent* GetBaseElement(); + + bool GetVerticalOverflow() const { return mVerticalOverflow; } + bool GetHorizontalOverflow() const {return mHorizontalOverflow; } + +protected: + friend class nsOverflowChecker; + + // This method paints a specific column background of the tree. + void PaintColumn(nsTreeColumn* aColumn, + const nsRect& aColumnRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + // This method paints a single row in the tree. + void PaintRow(int32_t aRowIndex, + const nsRect& aRowRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt); + + // This method paints a single separator in the tree. + void PaintSeparator(int32_t aRowIndex, + const nsRect& aSeparatorRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + // This method paints a specific cell in a given row of the tree. + void PaintCell(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aCellRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aCurrX, + nsPoint aPt); + + // This method paints the twisty inside a cell in the primary column of an tree. + void PaintTwisty(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aTwistyRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aRemainingWidth, + nscoord& aCurrX); + + // This method paints the image inside the cell of an tree. + void PaintImage(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aImageRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aRemainingWidth, + nscoord& aCurrX); + + // This method paints the text string inside a particular cell of the tree. + void PaintText(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aTextRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aCurrX, + bool aTextRTL); + + // This method paints the checkbox inside a particular cell of the tree. + void PaintCheckbox(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aCheckboxRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + // This method paints the progress meter inside a particular cell of the tree. + void PaintProgressMeter(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aProgressMeterRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + // This method paints a drop feedback of the tree. + void PaintDropFeedback(const nsRect& aDropFeedbackRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt); + + // This method is called with a specific style context and rect to + // paint the background rect as if it were a full-blown frame. + void PaintBackgroundLayer(nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aRect, + const nsRect& aDirtyRect); + + + // An internal hit test. aX and aY are expected to be in twips in the + // coordinate system of this frame. + int32_t GetRowAt(nscoord aX, nscoord aY); + + // Check for bidi characters in the text, and if there are any, ensure + // that the prescontext is in bidi mode. + void CheckTextForBidi(nsAutoString& aText); + + void AdjustForCellText(nsAutoString& aText, + int32_t aRowIndex, nsTreeColumn* aColumn, + nsRenderingContext& aRenderingContext, + nsRect& aTextRect); + + // A helper used when hit testing. + nsIAtom* GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect, + int32_t aRowIndex, nsTreeColumn* aColumn); + + // An internal hit test. aX and aY are expected to be in twips in the + // coordinate system of this frame. + void GetCellAt(nscoord aX, nscoord aY, int32_t* aRow, nsTreeColumn** aCol, + nsIAtom** aChildElt); + + // Retrieve the area for the twisty for a cell. + nsITheme* GetTwistyRect(int32_t aRowIndex, + nsTreeColumn* aColumn, + nsRect& aImageRect, + nsRect& aTwistyRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + nsStyleContext* aTwistyContext); + + // Fetch an image from the image cache. + nsresult GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, + nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult); + + // Returns the size of a given image. This size *includes* border and + // padding. It does not include margins. + nsRect GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, nsStyleContext* aStyleContext); + + // Returns the destination size of the image, not including borders and padding. + nsSize GetImageDestSize(nsStyleContext* aStyleContext, bool useImageRegion, imgIContainer* image); + + // Returns the source rectangle of the image to be displayed. + nsRect GetImageSourceRect(nsStyleContext* aStyleContext, bool useImageRegion, imgIContainer* image); + + // Returns the height of rows in the tree. + int32_t GetRowHeight(); + + // Returns our indentation width. + int32_t GetIndentation(); + + // Calculates our width/height once border and padding have been removed. + void CalcInnerBox(); + + // Calculate the total width of our scrollable portion + nscoord CalcHorzWidth(const ScrollParts& aParts); + + // Looks up a style context in the style cache. On a cache miss we resolve + // the pseudo-styles passed in and place them into the cache. + nsStyleContext* GetPseudoStyleContext(nsIAtom* aPseudoElement); + + // Retrieves the scrollbars and scrollview relevant to this treebody. We + // traverse the frame tree under our base element, in frame order, looking + // for the first relevant vertical scrollbar, horizontal scrollbar, and + // scrollable frame (with associated content and scrollable view). These + // are all volatile and should not be retained. + ScrollParts GetScrollParts(); + + // Update the curpos of the scrollbar. + void UpdateScrollbars(const ScrollParts& aParts); + + // Update the maxpos of the scrollbar. + void InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame); + + // Check overflow and generate events. + void CheckOverflow(const ScrollParts& aParts); + + // Calls UpdateScrollbars, Invalidate aNeedsFullInvalidation if true, + // InvalidateScrollbars and finally CheckOverflow. + // returns true if the frame is still alive after the method call. + bool FullScrollbarsUpdate(bool aNeedsFullInvalidation); + + // Use to auto-fill some of the common properties without the view having to do it. + // Examples include container, open, selected, and focus. + void PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol); + + // Our internal scroll method, used by all the public scroll methods. + nsresult ScrollInternal(const ScrollParts& aParts, int32_t aRow); + nsresult ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow); + nsresult ScrollToColumnInternal(const ScrollParts& aParts, nsITreeColumn* aCol); + nsresult ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition); + nsresult EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow); + + // Convert client pixels into appunits in our coordinate space. + nsPoint AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY); + + // Cache the box object + void EnsureBoxObject(); + + void EnsureView(); + + nsresult GetCellWidth(int32_t aRow, nsTreeColumn* aCol, + nsRenderingContext* aRenderingContext, + nscoord& aDesiredSize, nscoord& aCurrentSize); + nscoord CalcMaxRowWidth(); + + // Translate the given rect horizontally from tree coordinates into the + // coordinate system of our nsTreeBodyFrame. If clip is true, then clip the + // rect to its intersection with mInnerBox in the horizontal direction. + // Return whether the result has a nonempty intersection with mInnerBox + // after projecting both onto the horizontal coordinate axis. + bool OffsetForHorzScroll(nsRect& rect, bool clip); + + bool CanAutoScroll(int32_t aRowIndex); + + // Calc the row and above/below/on status given where the mouse currently is hovering. + // Also calc if we're in the region in which we want to auto-scroll the tree. + // A positive value of |aScrollLines| means scroll down, a negative value + // means scroll up, a zero value means that we aren't in drag scroll region. + void ComputeDropPosition(nsGUIEvent* aEvent, int32_t* aRow, int16_t* aOrient, + int16_t* aScrollLines); + + // Mark ourselves dirty if we're a select widget + void MarkDirtyIfSelect(); + + void InvalidateDropFeedback(int32_t aRow, int16_t aOrientation) { + InvalidateRow(aRow); + if (aOrientation != nsITreeView::DROP_ON) + InvalidateRow(aRow + aOrientation); + } + +public: + static + already_AddRefed<nsTreeColumn> GetColumnImpl(nsITreeColumn* aUnknownCol) { + if (!aUnknownCol) + return nullptr; + + nsCOMPtr<nsTreeColumn> col = do_QueryInterface(aUnknownCol); + return col.forget(); + } + + /** + * Remove an nsITreeImageListener from being tracked by this frame. Only tree + * image listeners that are created by this frame are tracked. + * + * @param aListener A pointer to an nsTreeImageListener to no longer + * track. + */ + void RemoveTreeImageListener(nsTreeImageListener* aListener); + +protected: + + // Create a new timer. This method is used to delay various actions like + // opening/closing folders or tree scrolling. + // aID is type of the action, aFunc is the function to be called when + // the timer fires and aType is type of timer - one shot or repeating. + nsresult CreateTimer(const mozilla::LookAndFeel::IntID aID, + nsTimerCallbackFunc aFunc, int32_t aType, + nsITimer** aTimer); + + static void OpenCallback(nsITimer *aTimer, void *aClosure); + + static void CloseCallback(nsITimer *aTimer, void *aClosure); + + static void LazyScrollCallback(nsITimer *aTimer, void *aClosure); + + static void ScrollCallback(nsITimer *aTimer, void *aClosure); + + class ScrollEvent : public nsRunnable { + public: + NS_DECL_NSIRUNNABLE + ScrollEvent(nsTreeBodyFrame *aInner) : mInner(aInner) {} + void Revoke() { mInner = nullptr; } + private: + nsTreeBodyFrame* mInner; + }; + + void PostScrollEvent(); + void FireScrollEvent(); + + /** + * Clear the pointer to this frame for all nsTreeImageListeners that were + * created by this frame. + */ + void DetachImageListeners(); + +#ifdef ACCESSIBILITY + /** + * Fires 'treeRowCountChanged' event asynchronously. The event supports + * nsIDOMDataContainerEvent interface that is used to expose the following + * information structures. + * + * @param aIndex the row index rows are added/removed from + * @param aCount the number of added/removed rows (the sign points to + * an operation, plus - addition, minus - removing) + */ + void FireRowCountChangedEvent(int32_t aIndex, int32_t aCount); + + /** + * Fires 'treeInvalidated' event asynchronously. The event supports + * nsIDOMDataContainerEvent interface that is used to expose the information + * structures described by method arguments. + * + * @param aStartRow the start index of invalidated rows, -1 means that + * columns have been invalidated only + * @param aEndRow the end index of invalidated rows, -1 means that columns + * have been invalidated only + * @param aStartCol the start invalidated column, nullptr means that only rows + * have been invalidated + * @param aEndCol the end invalidated column, nullptr means that rows have + * been invalidated only + */ + void FireInvalidateEvent(int32_t aStartRow, int32_t aEndRow, + nsITreeColumn *aStartCol, nsITreeColumn *aEndCol); +#endif + +protected: // Data Members + + class Slots { + public: + Slots() { + } + + ~Slots() { + if (mTimer) + mTimer->Cancel(); + } + + friend class nsTreeBodyFrame; + + protected: + // If the drop is actually allowed here or not. + bool mDropAllowed; + + // True while dragging over the tree. + bool mIsDragging; + + // The row the mouse is hovering over during a drop. + int32_t mDropRow; + + // Where we want to draw feedback (above/on this row/below) if allowed. + int16_t mDropOrient; + + // Number of lines to be scrolled. + int16_t mScrollLines; + + // The drag action that was received for this slot + uint32_t mDragAction; + + // Timer for opening/closing spring loaded folders or scrolling the tree. + nsCOMPtr<nsITimer> mTimer; + + // An array used to keep track of all spring loaded folders. + nsTArray<int32_t> mArray; + }; + + Slots* mSlots; + + nsRevocableEventPtr<ScrollEvent> mScrollEvent; + + nsCOMPtr<ScrollbarActivity> mScrollbarActivity; + + // The cached box object parent. + nsCOMPtr<nsITreeBoxObject> mTreeBoxObject; + + // Cached column information. + nsRefPtr<nsTreeColumns> mColumns; + + // The current view for this tree widget. We get all of our row and cell data + // from the view. + nsCOMPtr<nsITreeView> mView; + + // A cache of all the style contexts we have seen for rows and cells of the tree. This is a mapping from + // a list of atoms to a corresponding style context. This cache stores every combination that + // occurs in the tree, so for n distinct properties, this cache could have 2 to the n entries + // (the power set of all row properties). + nsTreeStyleCache mStyleCache; + + // A hashtable that maps from URLs to image request/listener pairs. The URL + // is provided by the view or by the style context. The style context + // represents a resolved :-moz-tree-cell-image (or twisty) pseudo-element. + // It maps directly to an imgIRequest. + nsDataHashtable<nsStringHashKey, nsTreeImageCacheEntry> mImageCache; + + // A scratch array used when looking up cached style contexts. + AtomArray mScratchArray; + + // The index of the first visible row and the # of rows visible onscreen. + // The tree only examines onscreen rows, starting from + // this index and going up to index+pageLength. + int32_t mTopRowIndex; + int32_t mPageLength; + + // The horizontal scroll position + nscoord mHorzPosition; + + // The original desired horizontal width before changing it and posting a + // reflow callback. In some cases, the desired horizontal width can first be + // different from the current desired horizontal width, only to return to + // the same value later during the same reflow. In this case, we can cancel + // the posted reflow callback and prevent an unnecessary reflow. + nscoord mOriginalHorzWidth; + // Our desired horizontal width (the width for which we actually have tree + // columns). + nscoord mHorzWidth; + // The amount by which to adjust the width of the last cell. + // This depends on whether or not the columnpicker and scrollbars are present. + nscoord mAdjustWidth; + + // Cached heights and indent info. + nsRect mInnerBox; // 4-byte aligned + int32_t mRowHeight; + int32_t mIndentation; + nscoord mStringWidth; + + int32_t mUpdateBatchNest; + + // Cached row count. + int32_t mRowCount; + + // The row the mouse is hovering over. + int32_t mMouseOverRow; + + // Whether or not we're currently focused. + bool mFocused; + + // Do we have a fixed number of onscreen rows? + bool mHasFixedRowCount; + + bool mVerticalOverflow; + bool mHorizontalOverflow; + + bool mReflowCallbackPosted; + + // Hash table to keep track of which listeners we created and thus + // have pointers to us. + nsTHashtable<nsPtrHashKey<nsTreeImageListener> > mCreatedListeners; + +}; // class nsTreeBodyFrame + +#endif diff --git a/layout/xul/tree/nsTreeBoxObject.cpp b/layout/xul/tree/nsTreeBoxObject.cpp new file mode 100644 index 000000000..e00c8a3e2 --- /dev/null +++ b/layout/xul/tree/nsTreeBoxObject.cpp @@ -0,0 +1,497 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTreeBoxObject.h" +#include "nsCOMPtr.h" +#include "nsIDOMXULElement.h" +#include "nsIXULTemplateBuilder.h" +#include "nsTreeContentView.h" +#include "nsITreeSelection.h" +#include "nsChildIterator.h" +#include "nsContentUtils.h" +#include "nsError.h" +#include "nsTreeBodyFrame.h" + +NS_IMPL_CYCLE_COLLECTION_1(nsTreeBoxObject, mView) + +NS_IMPL_ADDREF_INHERITED(nsTreeBoxObject, nsBoxObject) +NS_IMPL_RELEASE_INHERITED(nsTreeBoxObject, nsBoxObject) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsTreeBoxObject) + NS_INTERFACE_MAP_ENTRY(nsITreeBoxObject) +NS_INTERFACE_MAP_END_INHERITING(nsBoxObject) + +void +nsTreeBoxObject::Clear() +{ + ClearCachedValues(); + + // Drop the view's ref to us. + if (mView) { + nsCOMPtr<nsITreeSelection> sel; + mView->GetSelection(getter_AddRefs(sel)); + if (sel) + sel->SetTree(nullptr); + mView->SetTree(nullptr); // Break the circular ref between the view and us. + } + mView = nullptr; + + nsBoxObject::Clear(); +} + + +nsTreeBoxObject::nsTreeBoxObject() + : mTreeBody(nullptr) +{ +} + +nsTreeBoxObject::~nsTreeBoxObject() +{ + /* destructor code */ +} + + +static void FindBodyElement(nsIContent* aParent, nsIContent** aResult) +{ + *aResult = nullptr; + ChildIterator iter, last; + for (ChildIterator::Init(aParent, &iter, &last); iter != last; ++iter) { + nsCOMPtr<nsIContent> content = *iter; + + nsINodeInfo *ni = content->NodeInfo(); + if (ni->Equals(nsGkAtoms::treechildren, kNameSpaceID_XUL)) { + *aResult = content; + NS_ADDREF(*aResult); + break; + } else if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) { + // There are nesting tree elements. Only the innermost should + // find the treechilren. + break; + } else if (content->IsElement() && + !ni->Equals(nsGkAtoms::_template, kNameSpaceID_XUL)) { + FindBodyElement(content, aResult); + if (*aResult) + break; + } + } +} + +nsTreeBodyFrame* +nsTreeBoxObject::GetTreeBody(bool aFlushLayout) +{ + // Make sure our frames are up to date, and layout as needed. We + // have to do this before checking for our cached mTreeBody, since + // it might go away on style flush, and in any case if aFlushLayout + // is true we need to make sure to flush no matter what. + // XXXbz except that flushing style when we were not asked to flush + // layout here breaks things. See bug 585123. + nsIFrame* frame; + if (aFlushLayout) { + frame = GetFrame(aFlushLayout); + if (!frame) + return nullptr; + } + + if (mTreeBody) { + // Have one cached already. + return mTreeBody; + } + + if (!aFlushLayout) { + frame = GetFrame(aFlushLayout); + if (!frame) + return nullptr; + } + + // Iterate over our content model children looking for the body. + nsCOMPtr<nsIContent> content; + FindBodyElement(frame->GetContent(), getter_AddRefs(content)); + if (!content) + return nullptr; + + frame = content->GetPrimaryFrame(); + if (!frame) + return nullptr; + + // Make sure that the treebodyframe has a pointer to |this|. + nsTreeBodyFrame *treeBody = do_QueryFrame(frame); + NS_ENSURE_TRUE(treeBody && treeBody->GetTreeBoxObject() == this, nullptr); + + mTreeBody = treeBody; + return mTreeBody; +} + +NS_IMETHODIMP nsTreeBoxObject::GetView(nsITreeView * *aView) +{ + if (!mTreeBody) { + if (!GetTreeBody()) { + // Don't return an uninitialised view + *aView = nullptr; + return NS_OK; + } + + if (mView) + // Our new frame needs to initialise itself + return mTreeBody->GetView(aView); + } + if (!mView) { + nsCOMPtr<nsIDOMXULElement> xulele = do_QueryInterface(mContent); + if (xulele) { + // See if there is a XUL tree builder associated with the element + nsCOMPtr<nsIXULTemplateBuilder> builder; + xulele->GetBuilder(getter_AddRefs(builder)); + mView = do_QueryInterface(builder); + + if (!mView) { + // No tree builder, create a tree content view. + nsresult rv = NS_NewTreeContentView(getter_AddRefs(mView)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Initialise the frame and view + mTreeBody->SetView(mView); + } + } + NS_IF_ADDREF(*aView = mView); + return NS_OK; +} + +static bool +CanTrustView(nsISupports* aValue) +{ + // Untrusted content is only allowed to specify known-good views + if (nsContentUtils::IsCallerChrome()) + return true; + nsCOMPtr<nsINativeTreeView> nativeTreeView = do_QueryInterface(aValue); + if (!nativeTreeView || NS_FAILED(nativeTreeView->EnsureNative())) { + // XXX ERRMSG need a good error here for developers + return false; + } + return true; +} + +NS_IMETHODIMP nsTreeBoxObject::SetView(nsITreeView * aView) +{ + if (!CanTrustView(aView)) + return NS_ERROR_DOM_SECURITY_ERR; + + mView = aView; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + body->SetView(aView); + + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetFocused(bool* aFocused) +{ + *aFocused = false; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetFocused(aFocused); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::SetFocused(bool aFocused) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->SetFocused(aFocused); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetTreeBody(nsIDOMElement** aElement) +{ + *aElement = nullptr; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetTreeBody(aElement); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetColumns(nsITreeColumns** aColumns) +{ + *aColumns = nullptr; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + *aColumns = body->Columns().get(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetRowHeight(int32_t* aRowHeight) +{ + *aRowHeight = 0; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetRowHeight(aRowHeight); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetRowWidth(int32_t *aRowWidth) +{ + *aRowWidth = 0; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetRowWidth(aRowWidth); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetFirstVisibleRow(int32_t *aFirstVisibleRow) +{ + *aFirstVisibleRow = 0; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + *aFirstVisibleRow = body->FirstVisibleRow(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetLastVisibleRow(int32_t *aLastVisibleRow) +{ + *aLastVisibleRow = 0; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + *aLastVisibleRow = body->LastVisibleRow(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetHorizontalPosition(int32_t *aHorizontalPosition) +{ + *aHorizontalPosition = 0; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetHorizontalPosition(aHorizontalPosition); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetPageLength(int32_t *aPageLength) +{ + *aPageLength = 0; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + *aPageLength = body->PageLength(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetSelectionRegion(nsIScriptableRegion **aRegion) +{ + *aRegion = nullptr; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetSelectionRegion(aRegion); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::EnsureRowIsVisible(int32_t aRow) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->EnsureRowIsVisible(aRow); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->EnsureCellIsVisible(aRow, aCol); + return NS_OK; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsTreeBoxObject::ScrollToRow(int32_t aRow) +{ + nsTreeBodyFrame* body = GetTreeBody(true); + if (body) + return body->ScrollToRow(aRow); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::ScrollByLines(int32_t aNumLines) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->ScrollByLines(aNumLines); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::ScrollByPages(int32_t aNumPages) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->ScrollByPages(aNumPages); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::ScrollToCell(int32_t aRow, nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->ScrollToCell(aRow, aCol); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::ScrollToColumn(nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->ScrollToColumn(aCol); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::ScrollToHorizontalPosition(int32_t aHorizontalPosition) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->ScrollToHorizontalPosition(aHorizontalPosition); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::Invalidate() +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->Invalidate(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::InvalidateColumn(nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->InvalidateColumn(aCol); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::InvalidateRow(int32_t aIndex) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->InvalidateRow(aIndex); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::InvalidateCell(int32_t aRow, nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->InvalidateCell(aRow, aCol); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::InvalidateRange(int32_t aStart, int32_t aEnd) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->InvalidateRange(aStart, aEnd); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->InvalidateColumnRange(aStart, aEnd, aCol); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetRowAt(int32_t x, int32_t y, int32_t *aRow) +{ + *aRow = 0; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetRowAt(x, y, aRow); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::GetCellAt(int32_t aX, int32_t aY, int32_t *aRow, nsITreeColumn** aCol, + nsACString& aChildElt) +{ + *aRow = 0; + *aCol = nullptr; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetCellAt(aX, aY, aRow, aCol, aChildElt); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsACString& aElement, + int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight) +{ + *aX = *aY = *aWidth = *aHeight = 0; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->GetCoordsForCellItem(aRow, aCol, aElement, aX, aY, aWidth, aHeight); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeBoxObject::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *aIsCropped) +{ + *aIsCropped = false; + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->IsCellCropped(aRow, aCol, aIsCropped); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::RowCountChanged(int32_t aIndex, int32_t aDelta) +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->RowCountChanged(aIndex, aDelta); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::BeginUpdateBatch() +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->BeginUpdateBatch(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::EndUpdateBatch() +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->EndUpdateBatch(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeBoxObject::ClearStyleAndImageCaches() +{ + nsTreeBodyFrame* body = GetTreeBody(); + if (body) + return body->ClearStyleAndImageCaches(); + return NS_OK; +} + +void +nsTreeBoxObject::ClearCachedValues() +{ + mTreeBody = nullptr; +} + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewTreeBoxObject(nsIBoxObject** aResult) +{ + *aResult = new nsTreeBoxObject; + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} + diff --git a/layout/xul/tree/nsTreeBoxObject.h b/layout/xul/tree/nsTreeBoxObject.h new file mode 100644 index 000000000..897b22684 --- /dev/null +++ b/layout/xul/tree/nsTreeBoxObject.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTreeBoxObject_h___ +#define nsTreeBoxObject_h___ + +#include "mozilla/Attributes.h" +#include "nsBoxObject.h" +#include "nsITreeView.h" +#include "nsITreeBoxObject.h" + +class nsTreeBodyFrame; + +class nsTreeBoxObject : public nsITreeBoxObject, public nsBoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsTreeBoxObject, nsBoxObject) + NS_DECL_NSITREEBOXOBJECT + + nsTreeBoxObject(); + ~nsTreeBoxObject(); + + nsTreeBodyFrame* GetTreeBody(bool aFlushLayout = false); + nsTreeBodyFrame* GetCachedTreeBody() { return mTreeBody; } + + //NS_PIBOXOBJECT interfaces + virtual void Clear() MOZ_OVERRIDE; + virtual void ClearCachedValues() MOZ_OVERRIDE; + +protected: + nsTreeBodyFrame* mTreeBody; + nsCOMPtr<nsITreeView> mView; +}; + +#endif diff --git a/layout/xul/tree/nsTreeColFrame.cpp b/layout/xul/tree/nsTreeColFrame.cpp new file mode 100644 index 000000000..2a958a0b1 --- /dev/null +++ b/layout/xul/tree/nsTreeColFrame.cpp @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsTreeColFrame.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsStyleContext.h" +#include "nsINameSpaceManager.h" +#include "nsIBoxObject.h" +#include "nsTreeBoxObject.h" +#include "nsIDOMElement.h" +#include "nsITreeBoxObject.h" +#include "nsITreeColumns.h" +#include "nsIDOMXULTreeElement.h" +#include "nsDisplayList.h" +#include "nsTreeBodyFrame.h" + +// +// NS_NewTreeColFrame +// +// Creates a new col frame +// +nsIFrame* +NS_NewTreeColFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTreeColFrame(aPresShell, aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTreeColFrame) + +// Destructor +nsTreeColFrame::~nsTreeColFrame() +{ +} + +void +nsTreeColFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + InvalidateColumns(); +} + +void +nsTreeColFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + InvalidateColumns(false); + nsBoxFrame::DestroyFrom(aDestructRoot); +} + +class nsDisplayXULTreeColSplitterTarget : public nsDisplayItem { +public: + nsDisplayXULTreeColSplitterTarget(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) : + nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayXULTreeColSplitterTarget); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayXULTreeColSplitterTarget() { + MOZ_COUNT_DTOR(nsDisplayXULTreeColSplitterTarget); + } +#endif + + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames); + NS_DISPLAY_DECL_NAME("XULTreeColSplitterTarget", TYPE_XUL_TREE_COL_SPLITTER_TARGET) +}; + +void +nsDisplayXULTreeColSplitterTarget::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) +{ + nsRect rect = aRect - ToReferenceFrame(); + // If we are in either in the first 4 pixels or the last 4 pixels, we're going to + // do something really strange. Check for an adjacent splitter. + bool left = false; + bool right = false; + if (mFrame->GetSize().width - nsPresContext::CSSPixelsToAppUnits(4) <= rect.XMost()) { + right = true; + } else if (nsPresContext::CSSPixelsToAppUnits(4) > rect.x) { + left = true; + } + + // Swap left and right for RTL trees in order to find the correct splitter + if (mFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { + bool tmp = left; + left = right; + right = tmp; + } + + if (left || right) { + // We are a header. Look for the correct splitter. + nsIFrame* child; + if (left) + child = mFrame->GetPrevSibling(); + else + child = mFrame->GetNextSibling(); + + if (child && child->GetContent()->NodeInfo()->Equals(nsGkAtoms::splitter, + kNameSpaceID_XUL)) { + aOutFrames->AppendElement(child); + } + } + +} + +void +nsTreeColFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!aBuilder->IsForEventDelivery()) { + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); + return; + } + + nsDisplayListCollection set; + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set); + + WrapListsInRedirector(aBuilder, set, aLists); + + aLists.Content()->AppendNewToTop(new (aBuilder) + nsDisplayXULTreeColSplitterTarget(aBuilder, this)); +} + +NS_IMETHODIMP +nsTreeColFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + + if (aAttribute == nsGkAtoms::ordinal || aAttribute == nsGkAtoms::primary) { + InvalidateColumns(); + } + + return rv; +} + +void +nsTreeColFrame::SetBounds(nsBoxLayoutState& aBoxLayoutState, + const nsRect& aRect, + bool aRemoveOverflowArea) { + nscoord oldWidth = mRect.width; + + nsBoxFrame::SetBounds(aBoxLayoutState, aRect, aRemoveOverflowArea); + if (mRect.width != oldWidth) { + nsITreeBoxObject* treeBoxObject = GetTreeBoxObject(); + if (treeBoxObject) { + treeBoxObject->Invalidate(); + } + } +} + +nsITreeBoxObject* +nsTreeColFrame::GetTreeBoxObject() +{ + nsITreeBoxObject* result = nullptr; + + nsIContent* parent = mContent->GetParent(); + if (parent) { + nsIContent* grandParent = parent->GetParent(); + nsCOMPtr<nsIDOMXULElement> treeElement = do_QueryInterface(grandParent); + if (treeElement) { + nsCOMPtr<nsIBoxObject> boxObject; + treeElement->GetBoxObject(getter_AddRefs(boxObject)); + + nsCOMPtr<nsITreeBoxObject> treeBoxObject = do_QueryInterface(boxObject); + result = treeBoxObject.get(); + } + } + return result; +} + +void +nsTreeColFrame::InvalidateColumns(bool aCanWalkFrameTree) +{ + nsITreeBoxObject* treeBoxObject = GetTreeBoxObject(); + if (treeBoxObject) { + nsCOMPtr<nsITreeColumns> columns; + + if (aCanWalkFrameTree) { + treeBoxObject->GetColumns(getter_AddRefs(columns)); + } else { + nsTreeBodyFrame* body = static_cast<nsTreeBoxObject*>(treeBoxObject)->GetCachedTreeBody(); + if (body) { + columns = body->Columns(); + } + } + + if (columns) + columns->InvalidateColumns(); + } +} diff --git a/layout/xul/tree/nsTreeColFrame.h b/layout/xul/tree/nsTreeColFrame.h new file mode 100644 index 000000000..bad44a539 --- /dev/null +++ b/layout/xul/tree/nsTreeColFrame.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include "nsBoxFrame.h" + +class nsITreeBoxObject; + +nsIFrame* NS_NewTreeColFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + +class nsTreeColFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + nsTreeColFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext): + nsBoxFrame(aPresShell, aContext) {} + + virtual void Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) MOZ_OVERRIDE; + + NS_IMETHOD AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) MOZ_OVERRIDE; + + virtual void SetBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, + bool aRemoveOverflowArea = false) MOZ_OVERRIDE; + + friend nsIFrame* NS_NewTreeColFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + +protected: + virtual ~nsTreeColFrame(); + + /** + * @return the tree box object of the tree this column belongs to, or nullptr. + */ + nsITreeBoxObject* GetTreeBoxObject(); + + /** + * Helper method that gets the nsITreeColumns object this column belongs to + * and calls InvalidateColumns() on it. + */ + void InvalidateColumns(bool aCanWalkFrameTree = true); +}; diff --git a/layout/xul/tree/nsTreeColumns.cpp b/layout/xul/tree/nsTreeColumns.cpp new file mode 100644 index 000000000..56f8cd5f7 --- /dev/null +++ b/layout/xul/tree/nsTreeColumns.cpp @@ -0,0 +1,716 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsIDOMElement.h" +#include "nsIBoxObject.h" +#include "nsTreeColumns.h" +#include "nsTreeUtils.h" +#include "nsStyleContext.h" +#include "nsDOMClassInfoID.h" +#include "nsINodeInfo.h" +#include "nsContentUtils.h" +#include "nsTreeBodyFrame.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TreeColumnsBinding.h" + +using namespace mozilla; + +// Column class that caches all the info about our column. +nsTreeColumn::nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent) + : mContent(aContent), + mColumns(aColumns), + mPrevious(nullptr) +{ + NS_ASSERTION(aContent && + aContent->NodeInfo()->Equals(nsGkAtoms::treecol, + kNameSpaceID_XUL), + "nsTreeColumn's content must be a <xul:treecol>"); + + Invalidate(); +} + +nsTreeColumn::~nsTreeColumn() +{ + if (mNext) { + mNext->SetPrevious(nullptr); + } +} + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTreeColumn) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent) + if (tmp->mNext) { + tmp->mNext->SetPrevious(nullptr); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNext) + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTreeColumn) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNext) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumn) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumn) + +DOMCI_DATA(TreeColumn, nsTreeColumn) + +// QueryInterface implementation for nsTreeColumn +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumn) + NS_INTERFACE_MAP_ENTRY(nsITreeColumn) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeColumn) + if (aIID.Equals(NS_GET_IID(nsTreeColumn))) { + AddRef(); + *aInstancePtr = this; + return NS_OK; + } + else +NS_INTERFACE_MAP_END + +nsIFrame* +nsTreeColumn::GetFrame() +{ + NS_ENSURE_TRUE(mContent, nullptr); + + return mContent->GetPrimaryFrame(); +} + +bool +nsTreeColumn::IsLastVisible(nsTreeBodyFrame* aBodyFrame) +{ + NS_ASSERTION(GetFrame(), "should have checked for this already"); + + // cyclers are fixed width, don't adjust them + if (IsCycler()) + return false; + + // we're certainly not the last visible if we're not visible + if (GetFrame()->GetRect().width == 0) + return false; + + // try to find a visible successor + for (nsTreeColumn *next = GetNext(); next; next = next->GetNext()) { + nsIFrame* frame = next->GetFrame(); + if (frame && frame->GetRect().width > 0) + return false; + } + return true; +} + +nsresult +nsTreeColumn::GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight, nsRect* aResult) +{ + nsIFrame* frame = GetFrame(); + if (!frame) { + *aResult = nsRect(); + return NS_ERROR_FAILURE; + } + + bool isRTL = aBodyFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + *aResult = frame->GetRect(); + aResult->y = aY; + aResult->height = aHeight; + if (isRTL) + aResult->x += aBodyFrame->mAdjustWidth; + else if (IsLastVisible(aBodyFrame)) + aResult->width += aBodyFrame->mAdjustWidth; + return NS_OK; +} + +nsresult +nsTreeColumn::GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult) +{ + nsIFrame* frame = GetFrame(); + if (!frame) { + *aResult = 0; + return NS_ERROR_FAILURE; + } + *aResult = frame->GetRect().x; + return NS_OK; +} + +nsresult +nsTreeColumn::GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult) +{ + nsIFrame* frame = GetFrame(); + if (!frame) { + *aResult = 0; + return NS_ERROR_FAILURE; + } + *aResult = frame->GetRect().width; + if (IsLastVisible(aBodyFrame)) + *aResult += aBodyFrame->mAdjustWidth; + return NS_OK; +} + + +NS_IMETHODIMP +nsTreeColumn::GetElement(nsIDOMElement** aElement) +{ + if (mContent) { + return CallQueryInterface(mContent, aElement); + } + *aElement = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTreeColumn::GetColumns(nsITreeColumns** aColumns) +{ + NS_IF_ADDREF(*aColumns = mColumns); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetX(int32_t* aX) +{ + nsIFrame* frame = GetFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + *aX = nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().x); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetWidth(int32_t* aWidth) +{ + nsIFrame* frame = GetFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + *aWidth = nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().width); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetId(nsAString& aId) +{ + aId = GetId(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetIdConst(const PRUnichar** aIdConst) +{ + *aIdConst = mId.get(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetAtom(nsIAtom** aAtom) +{ + NS_IF_ADDREF(*aAtom = GetAtom()); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetIndex(int32_t* aIndex) +{ + *aIndex = GetIndex(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetPrimary(bool* aPrimary) +{ + *aPrimary = IsPrimary(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetCycler(bool* aCycler) +{ + *aCycler = IsCycler(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetEditable(bool* aEditable) +{ + *aEditable = IsEditable(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetSelectable(bool* aSelectable) +{ + *aSelectable = IsSelectable(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetType(int16_t* aType) +{ + *aType = GetType(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetNext(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetNext()); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetPrevious(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetPrevious()); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::Invalidate() +{ + nsIFrame* frame = GetFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + // Fetch the Id. + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, mId); + + // If we have an Id, cache the Id as an atom. + if (!mId.IsEmpty()) { + mAtom = do_GetAtom(mId); + } + + // Cache our index. + nsTreeUtils::GetColumnIndex(mContent, &mIndex); + + const nsStyleVisibility* vis = frame->StyleVisibility(); + + // Cache our text alignment policy. + const nsStyleText* textStyle = frame->StyleText(); + + mTextAlignment = textStyle->mTextAlign; + // DEFAULT or END alignment sometimes means RIGHT + if ((mTextAlignment == NS_STYLE_TEXT_ALIGN_DEFAULT && + vis->mDirection == NS_STYLE_DIRECTION_RTL) || + (mTextAlignment == NS_STYLE_TEXT_ALIGN_END && + vis->mDirection == NS_STYLE_DIRECTION_LTR)) { + mTextAlignment = NS_STYLE_TEXT_ALIGN_RIGHT; + } else if (mTextAlignment == NS_STYLE_TEXT_ALIGN_DEFAULT || + mTextAlignment == NS_STYLE_TEXT_ALIGN_END) { + mTextAlignment = NS_STYLE_TEXT_ALIGN_LEFT; + } + + // Figure out if we're the primary column (that has to have indentation + // and twisties drawn. + mIsPrimary = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary, + nsGkAtoms::_true, eCaseMatters); + + // Figure out if we're a cycling column (one that doesn't cause a selection + // to happen). + mIsCycler = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::cycler, + nsGkAtoms::_true, eCaseMatters); + + mIsEditable = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, + nsGkAtoms::_true, eCaseMatters); + + mIsSelectable = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable, + nsGkAtoms::_false, eCaseMatters); + + mOverflow = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::overflow, + nsGkAtoms::_true, eCaseMatters); + + // Figure out our column type. Default type is text. + mType = nsITreeColumn::TYPE_TEXT; + static nsIContent::AttrValuesArray typestrings[] = + {&nsGkAtoms::checkbox, &nsGkAtoms::progressmeter, nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, + typestrings, eCaseMatters)) { + case 0: mType = nsITreeColumn::TYPE_CHECKBOX; break; + case 1: mType = nsITreeColumn::TYPE_PROGRESSMETER; break; + } + + // Fetch the crop style. + mCropStyle = 0; + static nsIContent::AttrValuesArray cropstrings[] = + {&nsGkAtoms::center, &nsGkAtoms::left, &nsGkAtoms::start, nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop, + cropstrings, eCaseMatters)) { + case 0: + mCropStyle = 1; + break; + case 1: + case 2: + mCropStyle = 2; + break; + } + + return NS_OK; +} + + +nsTreeColumns::nsTreeColumns(nsTreeBodyFrame* aTree) + : mTree(aTree), + mFirstColumn(nullptr) +{ + SetIsDOMBinding(); +} + +nsTreeColumns::~nsTreeColumns() +{ + nsTreeColumns::InvalidateColumns(); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsTreeColumns) + +// QueryInterface implementation for nsTreeColumns +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumns) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsITreeColumns) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumns) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumns) + +nsIContent* +nsTreeColumns::GetParentObject() const +{ + return mTree ? mTree->GetBaseElement() : nullptr; +} + +/* virtual */ JSObject* +nsTreeColumns::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) +{ + return dom::TreeColumnsBinding::Wrap(aCx, aScope, this); +} + +nsITreeBoxObject* +nsTreeColumns::GetTree() const +{ + return mTree ? mTree->GetTreeBoxObject() : nullptr; +} + +NS_IMETHODIMP +nsTreeColumns::GetTree(nsITreeBoxObject** _retval) +{ + NS_IF_ADDREF(*_retval = GetTree()); + return NS_OK; +} + +uint32_t +nsTreeColumns::Count() +{ + EnsureColumns(); + uint32_t count = 0; + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + ++count; + } + return count; +} + +NS_IMETHODIMP +nsTreeColumns::GetCount(int32_t* _retval) +{ + *_retval = Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::GetLength(int32_t* _retval) +{ + *_retval = Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::GetFirstColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetFirstColumn()); + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::GetLastColumn() +{ + EnsureColumns(); + nsTreeColumn* currCol = mFirstColumn; + while (currCol) { + nsTreeColumn* next = currCol->GetNext(); + if (!next) { + return currCol; + } + currCol = next; + } + return nullptr; +} + +NS_IMETHODIMP +nsTreeColumns::GetLastColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetLastColumn()); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::GetPrimaryColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetPrimaryColumn()); + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::GetSortedColumn() +{ + EnsureColumns(); + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + if (currCol->mContent && + nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None, + nsGkAtoms::sortDirection)) { + return currCol; + } + } + return nullptr; +} + +NS_IMETHODIMP +nsTreeColumns::GetSortedColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetSortedColumn()); + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::GetKeyColumn() +{ + EnsureColumns(); + + nsTreeColumn* first = nullptr; + nsTreeColumn* primary = nullptr; + nsTreeColumn* sorted = nullptr; + + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + // Skip hidden columns. + if (!currCol->mContent || + currCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + continue; + + // Skip non-text column + if (currCol->GetType() != nsITreeColumn::TYPE_TEXT) + continue; + + if (!first) + first = currCol; + + if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None, + nsGkAtoms::sortDirection)) { + // Use sorted column as the key. + sorted = currCol; + break; + } + + if (currCol->IsPrimary()) + if (!primary) + primary = currCol; + } + + if (sorted) + return sorted; + if (primary) + return primary; + return first; +} + +NS_IMETHODIMP +nsTreeColumns::GetKeyColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetKeyColumn()); + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::GetColumnFor(dom::Element* aElement) +{ + EnsureColumns(); + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + if (currCol->mContent == aElement) { + return currCol; + } + } + return nullptr; +} + +NS_IMETHODIMP +nsTreeColumns::GetColumnFor(nsIDOMElement* aElement, nsITreeColumn** _retval) +{ + nsCOMPtr<dom::Element> element = do_QueryInterface(aElement); + NS_ADDREF(*_retval = GetColumnFor(element)); + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::NamedGetter(const nsAString& aId, bool& aFound) +{ + EnsureColumns(); + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + if (currCol->GetId().Equals(aId)) { + aFound = true; + return currCol; + } + } + aFound = false; + return nullptr; +} + +nsTreeColumn* +nsTreeColumns::GetNamedColumn(const nsAString& aId) +{ + bool dummy; + return NamedGetter(aId, dummy); +} + +NS_IMETHODIMP +nsTreeColumns::GetNamedColumn(const nsAString& aId, nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetNamedColumn(aId)); + return NS_OK; +} + +void +nsTreeColumns::GetSupportedNames(nsTArray<nsString>& aNames) +{ + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + aNames.AppendElement(currCol->GetId()); + } +} + + +nsTreeColumn* +nsTreeColumns::IndexedGetter(uint32_t aIndex, bool& aFound) +{ + EnsureColumns(); + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + if (currCol->GetIndex() == static_cast<int32_t>(aIndex)) { + aFound = true; + return currCol; + } + } + aFound = false; + return nullptr; +} + +nsTreeColumn* +nsTreeColumns::GetColumnAt(uint32_t aIndex) +{ + bool dummy; + return IndexedGetter(aIndex, dummy); +} + +NS_IMETHODIMP +nsTreeColumns::GetColumnAt(int32_t aIndex, nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetColumnAt(static_cast<uint32_t>(aIndex))); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::InvalidateColumns() +{ + for (nsTreeColumn* currCol = mFirstColumn; currCol; + currCol = currCol->GetNext()) { + currCol->SetColumns(nullptr); + } + NS_IF_RELEASE(mFirstColumn); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::RestoreNaturalOrder() +{ + if (!mTree) + return NS_OK; + + nsIContent* content = mTree->GetBaseElement(); + + // Strong ref, since we'll be setting attributes + nsCOMPtr<nsIContent> colsContent = + nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treecols); + if (!colsContent) + return NS_OK; + + for (uint32_t i = 0; i < colsContent->GetChildCount(); ++i) { + nsCOMPtr<nsIContent> child = colsContent->GetChildAt(i); + nsAutoString ordinal; + ordinal.AppendInt(i); + child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, true); + } + + nsTreeColumns::InvalidateColumns(); + + if (mTree) { + mTree->Invalidate(); + } + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::GetPrimaryColumn() +{ + EnsureColumns(); + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + if (currCol->IsPrimary()) { + return currCol; + } + } + return nullptr; +} + +void +nsTreeColumns::EnsureColumns() +{ + if (mTree && !mFirstColumn) { + nsIContent* treeContent = mTree->GetBaseElement(); + nsIContent* colsContent = + nsTreeUtils::GetDescendantChild(treeContent, nsGkAtoms::treecols); + if (!colsContent) + return; + + nsIContent* colContent = + nsTreeUtils::GetDescendantChild(colsContent, nsGkAtoms::treecol); + if (!colContent) + return; + + nsIFrame* colFrame = colContent->GetPrimaryFrame(); + if (!colFrame) + return; + + colFrame = colFrame->GetParent(); + if (!colFrame) + return; + + colFrame = colFrame->GetFirstPrincipalChild(); + if (!colFrame) + return; + + // Now that we have the first visible column, + // we can enumerate the columns in visible order + nsTreeColumn* currCol = nullptr; + while (colFrame) { + nsIContent* colContent = colFrame->GetContent(); + + if (colContent->NodeInfo()->Equals(nsGkAtoms::treecol, + kNameSpaceID_XUL)) { + // Create a new column structure. + nsTreeColumn* col = new nsTreeColumn(this, colContent); + if (!col) + return; + + if (currCol) { + currCol->SetNext(col); + col->SetPrevious(currCol); + } + else { + NS_ADDREF(mFirstColumn = col); + } + currCol = col; + } + + colFrame = colFrame->GetNextSibling(); + } + } +} diff --git a/layout/xul/tree/nsTreeColumns.h b/layout/xul/tree/nsTreeColumns.h new file mode 100644 index 000000000..88f5a6cc9 --- /dev/null +++ b/layout/xul/tree/nsTreeColumns.h @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTreeColumns_h__ +#define nsTreeColumns_h__ + +#include "nsITreeColumns.h" +#include "nsITreeBoxObject.h" +#include "mozilla/Attributes.h" +#include "nsCoord.h" +#include "nsCycleCollectionParticipant.h" +#include "nsAutoPtr.h" +#include "nsWrapperCache.h" + +class nsTreeBodyFrame; +class nsTreeColumns; +class nsIFrame; +class nsIContent; +struct nsRect; + +namespace mozilla { +namespace dom { +class Element; +} // namespace dom +} // namespace mozilla + +#define NS_TREECOLUMN_IMPL_CID \ +{ /* 02cd1963-4b5d-4a6c-9223-814d3ade93a3 */ \ + 0x02cd1963, \ + 0x4b5d, \ + 0x4a6c, \ + {0x92, 0x23, 0x81, 0x4d, 0x3a, 0xde, 0x93, 0xa3} \ +} + +// This class is our column info. We use it to iterate our columns and to obtain +// information about each column. +class nsTreeColumn MOZ_FINAL : public nsITreeColumn { +public: + nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent); + ~nsTreeColumn(); + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TREECOLUMN_IMPL_CID) + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsTreeColumn) + NS_DECL_NSITREECOLUMN + + friend class nsTreeBodyFrame; + friend class nsTreeColumns; + +protected: + nsIFrame* GetFrame(); + nsIFrame* GetFrame(nsTreeBodyFrame* aBodyFrame); + // Don't call this if GetWidthInTwips or GetRect fails + bool IsLastVisible(nsTreeBodyFrame* aBodyFrame); + + /** + * Returns a rect with x and width taken from the frame's rect and specified + * y and height. May fail in case there's no frame for the column. + */ + nsresult GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight, + nsRect* aResult); + + nsresult GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult); + nsresult GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult); + + void SetColumns(nsTreeColumns* aColumns) { mColumns = aColumns; } + + const nsAString& GetId() { return mId; } + nsIAtom* GetAtom() { return mAtom; } + + int32_t GetIndex() { return mIndex; } + + bool IsPrimary() { return mIsPrimary; } + bool IsCycler() { return mIsCycler; } + bool IsEditable() { return mIsEditable; } + bool IsSelectable() { return mIsSelectable; } + bool Overflow() { return mOverflow; } + + int16_t GetType() { return mType; } + + int8_t GetCropStyle() { return mCropStyle; } + int32_t GetTextAlignment() { return mTextAlignment; } + + nsTreeColumn* GetNext() { return mNext; } + nsTreeColumn* GetPrevious() { return mPrevious; } + void SetNext(nsTreeColumn* aNext) { + NS_ASSERTION(!mNext, "already have a next sibling"); + mNext = aNext; + } + void SetPrevious(nsTreeColumn* aPrevious) { mPrevious = aPrevious; } + +private: + /** + * Non-null nsIContent for the associated <treecol> element. + */ + nsCOMPtr<nsIContent> mContent; + + nsTreeColumns* mColumns; + + nsString mId; + nsCOMPtr<nsIAtom> mAtom; + + int32_t mIndex; + + bool mIsPrimary; + bool mIsCycler; + bool mIsEditable; + bool mIsSelectable; + bool mOverflow; + + int16_t mType; + + int8_t mCropStyle; + int8_t mTextAlignment; + + nsRefPtr<nsTreeColumn> mNext; + nsTreeColumn* mPrevious; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsTreeColumn, NS_TREECOLUMN_IMPL_CID) + +class nsTreeColumns MOZ_FINAL : public nsITreeColumns + , public nsWrapperCache +{ +public: + nsTreeColumns(nsTreeBodyFrame* aTree); + ~nsTreeColumns(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsTreeColumns) + NS_DECL_NSITREECOLUMNS + + nsIContent* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aScope) MOZ_OVERRIDE; + + // WebIDL + nsITreeBoxObject* GetTree() const; + uint32_t Count(); + uint32_t Length() + { + return Count(); + } + + nsTreeColumn* GetFirstColumn() { EnsureColumns(); return mFirstColumn; } + nsTreeColumn* GetLastColumn(); + + nsTreeColumn* GetPrimaryColumn(); + nsTreeColumn* GetSortedColumn(); + nsTreeColumn* GetKeyColumn(); + + nsTreeColumn* GetColumnFor(mozilla::dom::Element* aElement); + + nsTreeColumn* IndexedGetter(uint32_t aIndex, bool& aFound); + nsTreeColumn* GetColumnAt(uint32_t aIndex); + nsTreeColumn* NamedGetter(const nsAString& aId, bool& aFound); + nsTreeColumn* GetNamedColumn(const nsAString& aId); + void GetSupportedNames(nsTArray<nsString>& aNames); + + // Uses XPCOM InvalidateColumns(). + // Uses XPCOM RestoreNaturalOrder(). + + friend class nsTreeBodyFrame; +protected: + void SetTree(nsTreeBodyFrame* aTree) { mTree = aTree; } + + // Builds our cache of column info. + void EnsureColumns(); + +private: + nsTreeBodyFrame* mTree; + + /** + * The first column in the list of columns. All of the columns are supposed + * to be "alive", i.e. have a frame. This is achieved by clearing the columns + * list each time an nsTreeColFrame is destroyed. + * + * XXX this means that new nsTreeColumn objects are unnecessarily created + * for untouched columns. + */ + nsTreeColumn* mFirstColumn; +}; + +#endif // nsTreeColumns_h__ diff --git a/layout/xul/tree/nsTreeContentView.cpp b/layout/xul/tree/nsTreeContentView.cpp new file mode 100644 index 000000000..6071bb2e7 --- /dev/null +++ b/layout/xul/tree/nsTreeContentView.cpp @@ -0,0 +1,1388 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsIBoxObject.h" +#include "nsTreeUtils.h" +#include "nsTreeContentView.h" +#include "nsChildIterator.h" +#include "nsDOMClassInfoID.h" +#include "nsError.h" +#include "nsEventStates.h" +#include "nsINodeInfo.h" +#include "nsIXULSortService.h" +#include "nsContentUtils.h" +#include "nsTreeBodyFrame.h" +#include "mozilla/dom/Element.h" +#include "nsServiceManagerUtils.h" + +namespace dom = mozilla::dom; + +#define NS_ENSURE_NATIVE_COLUMN(_col) \ + nsRefPtr<nsTreeColumn> col = nsTreeBodyFrame::GetColumnImpl(_col); \ + if (!col) { \ + return NS_ERROR_INVALID_ARG; \ + } + +// A content model view implementation for the tree. + +#define ROW_FLAG_CONTAINER 0x01 +#define ROW_FLAG_OPEN 0x02 +#define ROW_FLAG_EMPTY 0x04 +#define ROW_FLAG_SEPARATOR 0x08 + +class Row +{ + public: + Row(nsIContent* aContent, int32_t aParentIndex) + : mContent(aContent), mParentIndex(aParentIndex), + mSubtreeSize(0), mFlags(0) { + } + + ~Row() { + } + + void SetContainer(bool aContainer) { + aContainer ? mFlags |= ROW_FLAG_CONTAINER : mFlags &= ~ROW_FLAG_CONTAINER; + } + bool IsContainer() { return mFlags & ROW_FLAG_CONTAINER; } + + void SetOpen(bool aOpen) { + aOpen ? mFlags |= ROW_FLAG_OPEN : mFlags &= ~ROW_FLAG_OPEN; + } + bool IsOpen() { return !!(mFlags & ROW_FLAG_OPEN); } + + void SetEmpty(bool aEmpty) { + aEmpty ? mFlags |= ROW_FLAG_EMPTY : mFlags &= ~ROW_FLAG_EMPTY; + } + bool IsEmpty() { return !!(mFlags & ROW_FLAG_EMPTY); } + + void SetSeparator(bool aSeparator) { + aSeparator ? mFlags |= ROW_FLAG_SEPARATOR : mFlags &= ~ROW_FLAG_SEPARATOR; + } + bool IsSeparator() { return !!(mFlags & ROW_FLAG_SEPARATOR); } + + // Weak reference to a content item. + nsIContent* mContent; + + // The parent index of the item, set to -1 for the top level items. + int32_t mParentIndex; + + // Subtree size for this item. + int32_t mSubtreeSize; + + private: + // State flags + int8_t mFlags; +}; + + +// We don't reference count the reference to the document +// If the document goes away first, we'll be informed and we +// can drop our reference. +// If we go away first, we'll get rid of ourselves from the +// document's observer list. + +nsTreeContentView::nsTreeContentView(void) : + mBoxObject(nullptr), + mSelection(nullptr), + mRoot(nullptr), + mDocument(nullptr) +{ +} + +nsTreeContentView::~nsTreeContentView(void) +{ + // Remove ourselves from mDocument's observers. + if (mDocument) + mDocument->RemoveObserver(this); +} + +nsresult +NS_NewTreeContentView(nsITreeView** aResult) +{ + *aResult = new nsTreeContentView; + if (! *aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION_4(nsTreeContentView, + mBoxObject, + mSelection, + mRoot, + mBody) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeContentView) + +DOMCI_DATA(TreeContentView, nsTreeContentView) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeContentView) + NS_INTERFACE_MAP_ENTRY(nsITreeView) + NS_INTERFACE_MAP_ENTRY(nsITreeContentView) + NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITreeContentView) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeContentView) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsTreeContentView::GetRowCount(int32_t* aRowCount) +{ + *aRowCount = mRows.Length(); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetSelection(nsITreeSelection** aSelection) +{ + NS_IF_ADDREF(*aSelection = mSelection); + + return NS_OK; +} + +bool +nsTreeContentView::CanTrustTreeSelection(nsISupports* aValue) +{ + // Untrusted content is only allowed to specify known-good views + if (nsContentUtils::IsCallerChrome()) + return true; + nsCOMPtr<nsINativeTreeSelection> nativeTreeSel = do_QueryInterface(aValue); + return nativeTreeSel && NS_SUCCEEDED(nativeTreeSel->EnsureNative()); +} + +NS_IMETHODIMP +nsTreeContentView::SetSelection(nsITreeSelection* aSelection) +{ + NS_ENSURE_TRUE(!aSelection || CanTrustTreeSelection(aSelection), + NS_ERROR_DOM_SECURITY_ERR); + + mSelection = aSelection; + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + Row* row = mRows[aIndex]; + nsIContent* realRow; + if (row->IsSeparator()) + realRow = row->mContent; + else + realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + + if (realRow) { + realRow->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetCellProperties(int32_t aRow, nsITreeColumn* aCol, + nsAString& aProps) +{ + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + Row* row = mRows[aRow]; + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell) { + cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps) +{ + NS_ENSURE_NATIVE_COLUMN(aCol); + nsCOMPtr<nsIDOMElement> element; + aCol->GetElement(getter_AddRefs(element)); + + element->GetAttribute(NS_LITERAL_STRING("properties"), aProps); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = mRows[aIndex]->IsContainer(); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = mRows[aIndex]->IsOpen(); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = mRows[aIndex]->IsEmpty(); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::IsSeparator(int32_t aIndex, bool *_retval) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = mRows[aIndex]->IsSeparator(); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::IsSorted(bool *_retval) +{ + *_retval = false; + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation, + nsIDOMDataTransfer* aDataTransfer, bool *_retval) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = false; + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation, nsIDOMDataTransfer* aDataTransfer) +{ + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval) +{ + NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()), + "bad row index"); + if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = mRows[aRowIndex]->mParentIndex; + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex, bool* _retval) +{ + NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()), + "bad row index"); + if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + // We have a next sibling if the row is not the last in the subtree. + int32_t parentIndex = mRows[aRowIndex]->mParentIndex; + if (parentIndex >= 0) { + // Compute the last index in this subtree. + int32_t lastIndex = parentIndex + (mRows[parentIndex])->mSubtreeSize; + Row* row = mRows[lastIndex]; + while (row->mParentIndex != parentIndex) { + lastIndex = row->mParentIndex; + row = mRows[lastIndex]; + } + + *_retval = aRowIndex < lastIndex; + } + else { + *_retval = uint32_t(aRowIndex) < mRows.Length() - 1; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + int32_t level = 0; + Row* row = mRows[aIndex]; + while (row->mParentIndex >= 0) { + level++; + row = mRows[row->mParentIndex]; + } + *_retval = level; + + return NS_OK; +} + + NS_IMETHODIMP +nsTreeContentView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) +{ + _retval.Truncate(); + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + Row* row = mRows[aRow]; + + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell) + cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, _retval); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* _retval) +{ + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = nsITreeView::PROGRESS_NONE; + + Row* row = mRows[aRow]; + + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell) { + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::normal, &nsGkAtoms::undetermined, nullptr}; + switch (cell->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::mode, + strings, eCaseMatters)) { + case 0: *_retval = nsITreeView::PROGRESS_NORMAL; break; + case 1: *_retval = nsITreeView::PROGRESS_UNDETERMINED; break; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) +{ + _retval.Truncate(); + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + Row* row = mRows[aRow]; + + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell) + cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, _retval); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) +{ + _retval.Truncate(); + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + NS_PRECONDITION(aCol, "bad column"); + + if (aRow < 0 || aRow >= int32_t(mRows.Length()) || !aCol) + return NS_ERROR_INVALID_ARG; + + Row* row = mRows[aRow]; + + // Check for a "label" attribute - this is valid on an <treeitem> + // with a single implied column. + if (row->mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval) + && !_retval.IsEmpty()) + return NS_OK; + + nsIAtom *rowTag = row->mContent->Tag(); + if (rowTag == nsGkAtoms::treeitem && row->mContent->IsXUL()) { + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell) + cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::SetTree(nsITreeBoxObject* aTree) +{ + ClearRows(); + + mBoxObject = aTree; + + MOZ_ASSERT(!mRoot, "mRoot should have been cleared out by ClearRows"); + + if (aTree) { + // Get our root element + nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mBoxObject); + if (!boxObject) { + mBoxObject = nullptr; + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIDOMElement> element; + boxObject->GetElement(getter_AddRefs(element)); + + mRoot = do_QueryInterface(element); + NS_ENSURE_STATE(mRoot); + + // Add ourselves to document's observers. + nsIDocument* document = mRoot->GetDocument(); + if (document) { + document->AddObserver(this); + mDocument = document; + } + + nsCOMPtr<nsIDOMElement> bodyElement; + mBoxObject->GetTreeBody(getter_AddRefs(bodyElement)); + if (bodyElement) { + mBody = do_QueryInterface(bodyElement); + int32_t index = 0; + Serialize(mBody, -1, &index, mRows); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::ToggleOpenState(int32_t aIndex) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + // We don't serialize content right here, since content might be generated + // lazily. + Row* row = mRows[aIndex]; + + if (row->IsOpen()) + row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("false"), true); + else + row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::CycleHeader(nsITreeColumn* aCol) +{ + NS_ENSURE_NATIVE_COLUMN(aCol); + + if (!mRoot) + return NS_OK; + + nsCOMPtr<nsIDOMElement> element; + aCol->GetElement(getter_AddRefs(element)); + if (element) { + nsCOMPtr<nsIContent> column = do_QueryInterface(element); + nsAutoString sort; + column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort); + if (!sort.IsEmpty()) { + nsCOMPtr<nsIXULSortService> xs = do_GetService("@mozilla.org/xul/xul-sort-service;1"); + if (xs) { + nsAutoString sortdirection; + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::ascending, &nsGkAtoms::descending, nullptr}; + switch (column->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::sortDirection, + strings, eCaseMatters)) { + case 0: sortdirection.AssignLiteral("descending"); break; + case 1: sortdirection.AssignLiteral("natural"); break; + default: sortdirection.AssignLiteral("ascending"); break; + } + + nsAutoString hints; + column->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints); + sortdirection.AppendLiteral(" "); + sortdirection += hints; + + nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot); + xs->Sort(rootnode, sort, sortdirection); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::SelectionChanged() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::CycleCell(int32_t aRow, nsITreeColumn* aCol) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::IsEditable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) +{ + *_retval = false; + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = true; + + Row* row = mRows[aRow]; + + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, + nsGkAtoms::_false, eCaseMatters)) { + *_retval = false; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::IsSelectable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) +{ + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = true; + + Row* row = mRows[aRow]; + + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable, + nsGkAtoms::_false, eCaseMatters)) { + *_retval = false; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::SetCellValue(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) +{ + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + Row* row = mRows[aRow]; + + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell) + cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::SetCellText(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) +{ + NS_ENSURE_NATIVE_COLUMN(aCol); + NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); + if (aRow < 0 || aRow >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + Row* row = mRows[aRow]; + + nsIContent* realRow = + nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); + if (realRow) { + nsIContent* cell = GetCell(realRow, aCol); + if (cell) + cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::PerformAction(const PRUnichar* aAction) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::PerformActionOnRow(const PRUnichar* aAction, int32_t aRow) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::PerformActionOnCell(const PRUnichar* aAction, int32_t aRow, nsITreeColumn* aCol) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsTreeContentView::GetItemAtIndex(int32_t aIndex, nsIDOMElement** _retval) +{ + NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); + if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) + return NS_ERROR_INVALID_ARG; + + Row* row = mRows[aIndex]; + row->mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)_retval); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval) +{ + nsCOMPtr<nsIContent> content = do_QueryInterface(aItem); + *_retval = FindContent(content); + + return NS_OK; +} + +void +nsTreeContentView::AttributeChanged(nsIDocument* aDocument, + dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + // Lots of codepaths under here that do all sorts of stuff, so be safe. + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + + // Make sure this notification concerns us. + // First check the tag to see if it's one that we care about. + nsIAtom* tag = aElement->Tag(); + + if (mBoxObject && (aElement == mRoot || aElement == mBody)) { + mBoxObject->ClearStyleAndImageCaches(); + mBoxObject->Invalidate(); + } + + // We don't consider non-XUL nodes. + nsIContent* parent = nullptr; + if (!aElement->IsXUL() || + ((parent = aElement->GetParent()) && !parent->IsXUL())) { + return; + } + if (tag != nsGkAtoms::treecol && + tag != nsGkAtoms::treeitem && + tag != nsGkAtoms::treeseparator && + tag != nsGkAtoms::treerow && + tag != nsGkAtoms::treecell) { + return; + } + + // If we have a legal tag, go up to the tree/select and make sure + // that it's ours. + + for (nsIContent* element = aElement; element != mBody; element = element->GetParent()) { + if (!element) + return; // this is not for us + nsIAtom *parentTag = element->Tag(); + if (element->IsXUL() && parentTag == nsGkAtoms::tree) + return; // this is not for us + } + + // Handle changes of the hidden attribute. + if (aAttribute == nsGkAtoms::hidden && + (tag == nsGkAtoms::treeitem || tag == nsGkAtoms::treeseparator)) { + bool hidden = aElement->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters); + + int32_t index = FindContent(aElement); + if (hidden && index >= 0) { + // Hide this row along with its children. + int32_t count = RemoveRow(index); + if (mBoxObject) + mBoxObject->RowCountChanged(index, -count); + } + else if (!hidden && index < 0) { + // Show this row along with its children. + nsCOMPtr<nsIContent> parent = aElement->GetParent(); + if (parent) { + InsertRowFor(parent, aElement); + } + } + + return; + } + + if (tag == nsGkAtoms::treecol) { + if (aAttribute == nsGkAtoms::properties) { + if (mBoxObject) { + nsCOMPtr<nsITreeColumns> cols; + mBoxObject->GetColumns(getter_AddRefs(cols)); + if (cols) { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aElement); + nsCOMPtr<nsITreeColumn> col; + cols->GetColumnFor(element, getter_AddRefs(col)); + mBoxObject->InvalidateColumn(col); + } + } + } + } + else if (tag == nsGkAtoms::treeitem) { + int32_t index = FindContent(aElement); + if (index >= 0) { + Row* row = mRows[index]; + if (aAttribute == nsGkAtoms::container) { + bool isContainer = + aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, + nsGkAtoms::_true, eCaseMatters); + row->SetContainer(isContainer); + if (mBoxObject) + mBoxObject->InvalidateRow(index); + } + else if (aAttribute == nsGkAtoms::open) { + bool isOpen = + aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, + nsGkAtoms::_true, eCaseMatters); + bool wasOpen = row->IsOpen(); + if (! isOpen && wasOpen) + CloseContainer(index); + else if (isOpen && ! wasOpen) + OpenContainer(index); + } + else if (aAttribute == nsGkAtoms::empty) { + bool isEmpty = + aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty, + nsGkAtoms::_true, eCaseMatters); + row->SetEmpty(isEmpty); + if (mBoxObject) + mBoxObject->InvalidateRow(index); + } + } + } + else if (tag == nsGkAtoms::treeseparator) { + int32_t index = FindContent(aElement); + if (index >= 0) { + if (aAttribute == nsGkAtoms::properties && mBoxObject) { + mBoxObject->InvalidateRow(index); + } + } + } + else if (tag == nsGkAtoms::treerow) { + if (aAttribute == nsGkAtoms::properties) { + nsCOMPtr<nsIContent> parent = aElement->GetParent(); + if (parent) { + int32_t index = FindContent(parent); + if (index >= 0 && mBoxObject) { + mBoxObject->InvalidateRow(index); + } + } + } + } + else if (tag == nsGkAtoms::treecell) { + if (aAttribute == nsGkAtoms::ref || + aAttribute == nsGkAtoms::properties || + aAttribute == nsGkAtoms::mode || + aAttribute == nsGkAtoms::src || + aAttribute == nsGkAtoms::value || + aAttribute == nsGkAtoms::label) { + nsIContent* parent = aElement->GetParent(); + if (parent) { + nsCOMPtr<nsIContent> grandParent = parent->GetParent(); + if (grandParent && grandParent->IsXUL()) { + int32_t index = FindContent(grandParent); + if (index >= 0 && mBoxObject) { + // XXX Should we make an effort to invalidate only cell ? + mBoxObject->InvalidateRow(index); + } + } + } + } + } +} + +void +nsTreeContentView::ContentAppended(nsIDocument *aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t /* unused */) +{ + for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { + // Our contentinserted doesn't use the index + ContentInserted(aDocument, aContainer, cur, 0); + } +} + +void +nsTreeContentView::ContentInserted(nsIDocument *aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t /* unused */) +{ + NS_ASSERTION(aChild, "null ptr"); + + // Make sure this notification concerns us. + // First check the tag to see if it's one that we care about. + nsIAtom *childTag = aChild->Tag(); + + // Don't allow non-XUL nodes. + if (!aChild->IsXUL() || !aContainer->IsXUL()) + return; + if (childTag != nsGkAtoms::treeitem && + childTag != nsGkAtoms::treeseparator && + childTag != nsGkAtoms::treechildren && + childTag != nsGkAtoms::treerow && + childTag != nsGkAtoms::treecell) { + return; + } + + // If we have a legal tag, go up to the tree/select and make sure + // that it's ours. + + for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) { + if (!element) + return; // this is not for us + nsIAtom *parentTag = element->Tag(); + if (element->IsXUL() && parentTag == nsGkAtoms::tree) + return; // this is not for us + } + + // Lots of codepaths under here that do all sorts of stuff, so be safe. + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + + if (childTag == nsGkAtoms::treechildren) { + int32_t index = FindContent(aContainer); + if (index >= 0) { + Row* row = mRows[index]; + row->SetEmpty(false); + if (mBoxObject) + mBoxObject->InvalidateRow(index); + if (row->IsContainer() && row->IsOpen()) { + int32_t count = EnsureSubtree(index); + if (mBoxObject) + mBoxObject->RowCountChanged(index + 1, count); + } + } + } + else if (childTag == nsGkAtoms::treeitem || + childTag == nsGkAtoms::treeseparator) { + InsertRowFor(aContainer, aChild); + } + else if (childTag == nsGkAtoms::treerow) { + int32_t index = FindContent(aContainer); + if (index >= 0 && mBoxObject) + mBoxObject->InvalidateRow(index); + } + else if (childTag == nsGkAtoms::treecell) { + nsCOMPtr<nsIContent> parent = aContainer->GetParent(); + if (parent) { + int32_t index = FindContent(parent); + if (index >= 0 && mBoxObject) + mBoxObject->InvalidateRow(index); + } + } +} + +void +nsTreeContentView::ContentRemoved(nsIDocument *aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + NS_ASSERTION(aChild, "null ptr"); + + // Make sure this notification concerns us. + // First check the tag to see if it's one that we care about. + nsIAtom *tag = aChild->Tag(); + + // We don't consider non-XUL nodes. + if (!aChild->IsXUL() || !aContainer->IsXUL()) + return; + if (tag != nsGkAtoms::treeitem && + tag != nsGkAtoms::treeseparator && + tag != nsGkAtoms::treechildren && + tag != nsGkAtoms::treerow && + tag != nsGkAtoms::treecell) { + return; + } + + // If we have a legal tag, go up to the tree/select and make sure + // that it's ours. + + for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) { + if (!element) + return; // this is not for us + nsIAtom *parentTag = element->Tag(); + if (element->IsXUL() && parentTag == nsGkAtoms::tree) + return; // this is not for us + } + + // Lots of codepaths under here that do all sorts of stuff, so be safe. + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + + if (tag == nsGkAtoms::treechildren) { + int32_t index = FindContent(aContainer); + if (index >= 0) { + Row* row = mRows[index]; + row->SetEmpty(true); + int32_t count = RemoveSubtree(index); + // Invalidate also the row to update twisty. + if (mBoxObject) { + mBoxObject->InvalidateRow(index); + mBoxObject->RowCountChanged(index + 1, -count); + } + } + } + else if (tag == nsGkAtoms::treeitem || + tag == nsGkAtoms::treeseparator + ) { + int32_t index = FindContent(aChild); + if (index >= 0) { + int32_t count = RemoveRow(index); + if (mBoxObject) + mBoxObject->RowCountChanged(index, -count); + } + } + else if (tag == nsGkAtoms::treerow) { + int32_t index = FindContent(aContainer); + if (index >= 0 && mBoxObject) + mBoxObject->InvalidateRow(index); + } + else if (tag == nsGkAtoms::treecell) { + nsCOMPtr<nsIContent> parent = aContainer->GetParent(); + if (parent) { + int32_t index = FindContent(parent); + if (index >= 0 && mBoxObject) + mBoxObject->InvalidateRow(index); + } + } +} + +void +nsTreeContentView::NodeWillBeDestroyed(const nsINode* aNode) +{ + // XXXbz do we need this strong ref? Do we drop refs to self in ClearRows? + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + ClearRows(); +} + + +// Recursively serialize content, starting with aContent. +void +nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex, + int32_t* aIndex, nsTArray<nsAutoPtr<Row> >& aRows) +{ + // Don't allow non-XUL nodes. + if (!aContent->IsXUL()) + return; + + ChildIterator iter, last; + for (ChildIterator::Init(aContent, &iter, &last); iter != last; ++iter) { + nsIContent* content = *iter; + nsIAtom *tag = content->Tag(); + int32_t count = aRows.Length(); + + if (content->IsXUL()) { + if (tag == nsGkAtoms::treeitem) + SerializeItem(content, aParentIndex, aIndex, aRows); + else if (tag == nsGkAtoms::treeseparator) + SerializeSeparator(content, aParentIndex, aIndex, aRows); + } + *aIndex += aRows.Length() - count; + } +} + +void +nsTreeContentView::SerializeItem(nsIContent* aContent, int32_t aParentIndex, + int32_t* aIndex, nsTArray<nsAutoPtr<Row> >& aRows) +{ + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + return; + + Row* row = new Row(aContent, aParentIndex); + aRows.AppendElement(row); + + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, + nsGkAtoms::_true, eCaseMatters)) { + row->SetContainer(true); + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, + nsGkAtoms::_true, eCaseMatters)) { + row->SetOpen(true); + nsIContent* child = + nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::treechildren); + if (child && child->IsXUL()) { + // Now, recursively serialize our child. + int32_t count = aRows.Length(); + int32_t index = 0; + Serialize(child, aParentIndex + *aIndex + 1, &index, aRows); + row->mSubtreeSize += aRows.Length() - count; + } + else + row->SetEmpty(true); + } else if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty, + nsGkAtoms::_true, eCaseMatters)) { + row->SetEmpty(true); + } + } +} + +void +nsTreeContentView::SerializeSeparator(nsIContent* aContent, + int32_t aParentIndex, int32_t* aIndex, + nsTArray<nsAutoPtr<Row> >& aRows) +{ + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + return; + + Row* row = new Row(aContent, aParentIndex); + row->SetSeparator(true); + aRows.AppendElement(row); +} + +void +nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer, + nsIContent* aContent, int32_t* aIndex) +{ + uint32_t childCount = aContainer->GetChildCount(); + + if (!aContainer->IsXUL()) + return; + + for (uint32_t i = 0; i < childCount; i++) { + nsIContent *content = aContainer->GetChildAt(i); + + if (content == aContent) + break; + + nsIAtom *tag = content->Tag(); + + if (content->IsXUL()) { + if (tag == nsGkAtoms::treeitem) { + if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) { + (*aIndex)++; + if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, + nsGkAtoms::_true, eCaseMatters) && + content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, + nsGkAtoms::_true, eCaseMatters)) { + nsIContent* child = + nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treechildren); + if (child && child->IsXUL()) + GetIndexInSubtree(child, aContent, aIndex); + } + } + } + else if (tag == nsGkAtoms::treeseparator) { + if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + (*aIndex)++; + } + } + } +} + +int32_t +nsTreeContentView::EnsureSubtree(int32_t aIndex) +{ + Row* row = mRows[aIndex]; + + nsIContent* child; + child = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren); + if (!child || !child->IsXUL()) { + return 0; + } + + nsAutoTArray<nsAutoPtr<Row>, 8> rows; + int32_t index = 0; + Serialize(child, aIndex, &index, rows); + // We can't use InsertElementsAt since the destination can't steal + // ownership from its const source argument. + for (nsTArray<Row>::index_type i = 0; i < rows.Length(); i++) { + nsAutoPtr<Row>* newRow = mRows.InsertElementAt(aIndex + i + 1); + *newRow = rows[i]; + } + int32_t count = rows.Length(); + + row->mSubtreeSize += count; + UpdateSubtreeSizes(row->mParentIndex, count); + + // Update parent indexes, but skip newly added rows. + // They already have correct values. + UpdateParentIndexes(aIndex, count + 1, count); + + return count; +} + +int32_t +nsTreeContentView::RemoveSubtree(int32_t aIndex) +{ + Row* row = mRows[aIndex]; + int32_t count = row->mSubtreeSize; + + mRows.RemoveElementsAt(aIndex + 1, count); + + row->mSubtreeSize -= count; + UpdateSubtreeSizes(row->mParentIndex, -count); + + UpdateParentIndexes(aIndex, 0, -count); + + return count; +} + +void +nsTreeContentView::InsertRowFor(nsIContent* aParent, nsIContent* aChild) +{ + int32_t grandParentIndex = -1; + bool insertRow = false; + + nsCOMPtr<nsIContent> grandParent = aParent->GetParent(); + nsIAtom* grandParentTag = grandParent->Tag(); + + if (grandParent->IsXUL() && grandParentTag == nsGkAtoms::tree) { + // Allow insertion to the outermost container. + insertRow = true; + } + else { + // Test insertion to an inner container. + + // First try to find this parent in our array of rows, if we find one + // we can be sure that all other parents are open too. + grandParentIndex = FindContent(grandParent); + if (grandParentIndex >= 0) { + // Got it, now test if it is open. + if (mRows[grandParentIndex]->IsOpen()) + insertRow = true; + } + } + + if (insertRow) { + int32_t index = 0; + GetIndexInSubtree(aParent, aChild, &index); + + int32_t count = InsertRow(grandParentIndex, index, aChild); + if (mBoxObject) + mBoxObject->RowCountChanged(grandParentIndex + index + 1, count); + } +} + +int32_t +nsTreeContentView::InsertRow(int32_t aParentIndex, int32_t aIndex, nsIContent* aContent) +{ + nsAutoTArray<nsAutoPtr<Row>, 8> rows; + nsIAtom *tag = aContent->Tag(); + if (aContent->IsXUL()) { + if (tag == nsGkAtoms::treeitem) + SerializeItem(aContent, aParentIndex, &aIndex, rows); + else if (tag == nsGkAtoms::treeseparator) + SerializeSeparator(aContent, aParentIndex, &aIndex, rows); + } + + // We can't use InsertElementsAt since the destination can't steal + // ownership from its const source argument. + for (nsTArray<Row>::index_type i = 0; i < rows.Length(); i++) { + nsAutoPtr<Row>* newRow = mRows.InsertElementAt(aParentIndex + aIndex + i + 1); + *newRow = rows[i]; + } + int32_t count = rows.Length(); + + UpdateSubtreeSizes(aParentIndex, count); + + // Update parent indexes, but skip added rows. + // They already have correct values. + UpdateParentIndexes(aParentIndex + aIndex, count + 1, count); + + return count; +} + +int32_t +nsTreeContentView::RemoveRow(int32_t aIndex) +{ + Row* row = mRows[aIndex]; + int32_t count = row->mSubtreeSize + 1; + int32_t parentIndex = row->mParentIndex; + + mRows.RemoveElementsAt(aIndex, count); + + UpdateSubtreeSizes(parentIndex, -count); + + UpdateParentIndexes(aIndex, 0, -count); + + return count; +} + +void +nsTreeContentView::ClearRows() +{ + mRows.Clear(); + mRoot = nullptr; + mBody = nullptr; + // Remove ourselves from mDocument's observers. + if (mDocument) { + mDocument->RemoveObserver(this); + mDocument = nullptr; + } +} + +void +nsTreeContentView::OpenContainer(int32_t aIndex) +{ + Row* row = mRows[aIndex]; + row->SetOpen(true); + + int32_t count = EnsureSubtree(aIndex); + if (mBoxObject) { + mBoxObject->InvalidateRow(aIndex); + mBoxObject->RowCountChanged(aIndex + 1, count); + } +} + +void +nsTreeContentView::CloseContainer(int32_t aIndex) +{ + Row* row = mRows[aIndex]; + row->SetOpen(false); + + int32_t count = RemoveSubtree(aIndex); + if (mBoxObject) { + mBoxObject->InvalidateRow(aIndex); + mBoxObject->RowCountChanged(aIndex + 1, -count); + } +} + +int32_t +nsTreeContentView::FindContent(nsIContent* aContent) +{ + for (uint32_t i = 0; i < mRows.Length(); i++) { + if (mRows[i]->mContent == aContent) { + return i; + } + } + + return -1; +} + +void +nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex, int32_t count) +{ + while (aParentIndex >= 0) { + Row* row = mRows[aParentIndex]; + row->mSubtreeSize += count; + aParentIndex = row->mParentIndex; + } +} + +void +nsTreeContentView::UpdateParentIndexes(int32_t aIndex, int32_t aSkip, int32_t aCount) +{ + int32_t count = mRows.Length(); + for (int32_t i = aIndex + aSkip; i < count; i++) { + Row* row = mRows[i]; + if (row->mParentIndex > aIndex) { + row->mParentIndex += aCount; + } + } +} + +nsIContent* +nsTreeContentView::GetCell(nsIContent* aContainer, nsITreeColumn* aCol) +{ + nsCOMPtr<nsIAtom> colAtom; + int32_t colIndex; + aCol->GetAtom(getter_AddRefs(colAtom)); + aCol->GetIndex(&colIndex); + + // Traverse through cells, try to find the cell by "ref" attribute or by cell + // index in a row. "ref" attribute has higher priority. + nsIContent* result = nullptr; + int32_t j = 0; + ChildIterator iter, last; + for (ChildIterator::Init(aContainer, &iter, &last); iter != last; ++iter) { + nsIContent* cell = *iter; + + if (cell->Tag() == nsGkAtoms::treecell) { + if (colAtom && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref, + colAtom, eCaseMatters)) { + result = cell; + break; + } + else if (j == colIndex) { + result = cell; + } + j++; + } + } + + return result; +} diff --git a/layout/xul/tree/nsTreeContentView.h b/layout/xul/tree/nsTreeContentView.h new file mode 100644 index 000000000..2430a3b5e --- /dev/null +++ b/layout/xul/tree/nsTreeContentView.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTreeContentView_h__ +#define nsTreeContentView_h__ + +#include "nsAutoPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsTArray.h" +#include "nsStubDocumentObserver.h" +#include "nsITreeBoxObject.h" +#include "nsITreeColumns.h" +#include "nsITreeView.h" +#include "nsITreeContentView.h" +#include "nsITreeSelection.h" +#include "mozilla/Attributes.h" + +class nsIDocument; +class Row; + +nsresult NS_NewTreeContentView(nsITreeView** aResult); + +class nsTreeContentView MOZ_FINAL : public nsINativeTreeView, + public nsITreeContentView, + public nsStubDocumentObserver +{ + public: + nsTreeContentView(void); + + ~nsTreeContentView(void); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTreeContentView, + nsINativeTreeView) + + NS_DECL_NSITREEVIEW + // nsINativeTreeView: Untrusted code can use us + NS_IMETHOD EnsureNative() MOZ_OVERRIDE { return NS_OK; } + + NS_DECL_NSITREECONTENTVIEW + + // nsIDocumentObserver + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED + + static bool CanTrustTreeSelection(nsISupports* aValue); + + protected: + // Recursive methods which deal with serializing of nested content. + void Serialize(nsIContent* aContent, int32_t aParentIndex, int32_t* aIndex, + nsTArray<nsAutoPtr<Row> >& aRows); + + void SerializeItem(nsIContent* aContent, int32_t aParentIndex, + int32_t* aIndex, nsTArray<nsAutoPtr<Row> >& aRows); + + void SerializeSeparator(nsIContent* aContent, int32_t aParentIndex, + int32_t* aIndex, nsTArray<nsAutoPtr<Row> >& aRows); + + void GetIndexInSubtree(nsIContent* aContainer, nsIContent* aContent, int32_t* aResult); + + // Helper methods which we use to manage our plain array of rows. + int32_t EnsureSubtree(int32_t aIndex); + + int32_t RemoveSubtree(int32_t aIndex); + + int32_t InsertRow(int32_t aParentIndex, int32_t aIndex, nsIContent* aContent); + + void InsertRowFor(nsIContent* aParent, nsIContent* aChild); + + int32_t RemoveRow(int32_t aIndex); + + void ClearRows(); + + void OpenContainer(int32_t aIndex); + + void CloseContainer(int32_t aIndex); + + int32_t FindContent(nsIContent* aContent); + + void UpdateSubtreeSizes(int32_t aIndex, int32_t aCount); + + void UpdateParentIndexes(int32_t aIndex, int32_t aSkip, int32_t aCount); + + // Content helpers. + nsIContent* GetCell(nsIContent* aContainer, nsITreeColumn* aCol); + + private: + nsCOMPtr<nsITreeBoxObject> mBoxObject; + nsCOMPtr<nsITreeSelection> mSelection; + nsCOMPtr<nsIContent> mRoot; + nsCOMPtr<nsIContent> mBody; + nsIDocument* mDocument; // WEAK + nsTArray<nsAutoPtr<Row> > mRows; +}; + +#endif // nsTreeContentView_h__ diff --git a/layout/xul/tree/nsTreeImageListener.cpp b/layout/xul/tree/nsTreeImageListener.cpp new file mode 100644 index 000000000..1680a4b5b --- /dev/null +++ b/layout/xul/tree/nsTreeImageListener.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTreeImageListener.h" +#include "nsITreeBoxObject.h" +#include "imgIRequest.h" +#include "imgIContainer.h" + +NS_IMPL_ISUPPORTS1(nsTreeImageListener, imgINotificationObserver) + +nsTreeImageListener::nsTreeImageListener(nsTreeBodyFrame* aTreeFrame) + : mTreeFrame(aTreeFrame), + mInvalidationSuppressed(true), + mInvalidationArea(nullptr) +{ +} + +nsTreeImageListener::~nsTreeImageListener() +{ + delete mInvalidationArea; +} + +NS_IMETHODIMP +nsTreeImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) +{ + if (aType == imgINotificationObserver::IS_ANIMATED) { + return mTreeFrame ? mTreeFrame->OnImageIsAnimated(aRequest) : NS_OK; + } + + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + // Ensure the animation (if any) is started. Note: There is no + // corresponding call to Decrement for this. This Increment will be + // 'cleaned up' by the Request when it is destroyed, but only then. + aRequest->IncrementAnimationConsumers(); + } + + if (aType == imgINotificationObserver::FRAME_UPDATE) { + Invalidate(); + } + + return NS_OK; +} + +void +nsTreeImageListener::AddCell(int32_t aIndex, nsITreeColumn* aCol) +{ + if (!mInvalidationArea) { + mInvalidationArea = new InvalidationArea(aCol); + mInvalidationArea->AddRow(aIndex); + } + else { + InvalidationArea* currArea; + for (currArea = mInvalidationArea; currArea; currArea = currArea->GetNext()) { + if (currArea->GetCol() == aCol) { + currArea->AddRow(aIndex); + break; + } + } + if (!currArea) { + currArea = new InvalidationArea(aCol); + currArea->SetNext(mInvalidationArea); + mInvalidationArea = currArea; + mInvalidationArea->AddRow(aIndex); + } + } +} + + +void +nsTreeImageListener::Invalidate() +{ + if (!mInvalidationSuppressed) { + for (InvalidationArea* currArea = mInvalidationArea; currArea; + currArea = currArea->GetNext()) { + // Loop from min to max, invalidating each cell that was listening for this image. + for (int32_t i = currArea->GetMin(); i <= currArea->GetMax(); ++i) { + if (mTreeFrame) { + nsITreeBoxObject* tree = mTreeFrame->GetTreeBoxObject(); + if (tree) { + tree->InvalidateCell(i, currArea->GetCol()); + } + } + } + } + } +} + +nsTreeImageListener::InvalidationArea::InvalidationArea(nsITreeColumn* aCol) + : mCol(aCol), + mMin(-1), // min should start out "undefined" + mMax(0), + mNext(nullptr) +{ +} + +void +nsTreeImageListener::InvalidationArea::AddRow(int32_t aIndex) +{ + if (mMin == -1) + mMin = mMax = aIndex; + else if (aIndex < mMin) + mMin = aIndex; + else if (aIndex > mMax) + mMax = aIndex; +} + +NS_IMETHODIMP +nsTreeImageListener::ClearFrame() +{ + mTreeFrame = nullptr; + return NS_OK; +} diff --git a/layout/xul/tree/nsTreeImageListener.h b/layout/xul/tree/nsTreeImageListener.h new file mode 100644 index 000000000..940083869 --- /dev/null +++ b/layout/xul/tree/nsTreeImageListener.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTreeImageListener_h__ +#define nsTreeImageListener_h__ + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsITreeColumns.h" +#include "nsTreeBodyFrame.h" +#include "mozilla/Attributes.h" + +// This class handles image load observation. +class nsTreeImageListener MOZ_FINAL : public imgINotificationObserver +{ +public: + nsTreeImageListener(nsTreeBodyFrame *aTreeFrame); + ~nsTreeImageListener(); + + NS_DECL_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + + NS_IMETHOD ClearFrame(); + + friend class nsTreeBodyFrame; + +protected: + void UnsuppressInvalidation() { mInvalidationSuppressed = false; } + void Invalidate(); + void AddCell(int32_t aIndex, nsITreeColumn* aCol); + +private: + nsTreeBodyFrame* mTreeFrame; + + // A guard that prevents us from recursive painting. + bool mInvalidationSuppressed; + + class InvalidationArea { + public: + InvalidationArea(nsITreeColumn* aCol); + ~InvalidationArea() { delete mNext; } + + friend class nsTreeImageListener; + + protected: + void AddRow(int32_t aIndex); + nsITreeColumn* GetCol() { return mCol.get(); } + int32_t GetMin() { return mMin; } + int32_t GetMax() { return mMax; } + InvalidationArea* GetNext() { return mNext; } + void SetNext(InvalidationArea* aNext) { mNext = aNext; } + + private: + nsCOMPtr<nsITreeColumn> mCol; + int32_t mMin; + int32_t mMax; + InvalidationArea* mNext; + }; + + InvalidationArea* mInvalidationArea; +}; + +#endif // nsTreeImageListener_h__ diff --git a/layout/xul/tree/nsTreeSelection.cpp b/layout/xul/tree/nsTreeSelection.cpp new file mode 100644 index 000000000..e6c6cc89c --- /dev/null +++ b/layout/xul/tree/nsTreeSelection.cpp @@ -0,0 +1,868 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsTreeSelection.h" +#include "nsIBoxObject.h" +#include "nsITreeBoxObject.h" +#include "nsITreeView.h" +#include "nsString.h" +#include "nsIDOMElement.h" +#include "nsDOMClassInfoID.h" +#include "nsIContent.h" +#include "nsGUIEvent.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsAsyncDOMEvent.h" +#include "nsEventDispatcher.h" +#include "nsAutoPtr.h" + +// A helper class for managing our ranges of selection. +struct nsTreeRange +{ + nsTreeSelection* mSelection; + + nsTreeRange* mPrev; + nsTreeRange* mNext; + + int32_t mMin; + int32_t mMax; + + nsTreeRange(nsTreeSelection* aSel, int32_t aSingleVal) + :mSelection(aSel), mPrev(nullptr), mNext(nullptr), mMin(aSingleVal), mMax(aSingleVal) {} + nsTreeRange(nsTreeSelection* aSel, int32_t aMin, int32_t aMax) + :mSelection(aSel), mPrev(nullptr), mNext(nullptr), mMin(aMin), mMax(aMax) {} + + ~nsTreeRange() { delete mNext; } + + void Connect(nsTreeRange* aPrev = nullptr, nsTreeRange* aNext = nullptr) { + if (aPrev) + aPrev->mNext = this; + else + mSelection->mFirstRange = this; + + if (aNext) + aNext->mPrev = this; + + mPrev = aPrev; + mNext = aNext; + } + + nsresult RemoveRange(int32_t aStart, int32_t aEnd) { + // This should so be a loop... sigh... + // We start past the range to remove, so no more to remove + if (aEnd < mMin) + return NS_OK; + // We are the last range to be affected + if (aEnd < mMax) { + if (aStart <= mMin) { + // Just chop the start of the range off + mMin = aEnd + 1; + } else { + // We need to split the range + nsTreeRange* range = new nsTreeRange(mSelection, aEnd + 1, mMax); + if (!range) + return NS_ERROR_OUT_OF_MEMORY; + + mMax = aStart - 1; + range->Connect(this, mNext); + } + return NS_OK; + } + nsTreeRange* next = mNext; + if (aStart <= mMin) { + // The remove includes us, remove ourselves from the list + if (mPrev) + mPrev->mNext = next; + else + mSelection->mFirstRange = next; + + if (next) + next->mPrev = mPrev; + mPrev = mNext = nullptr; + delete this; + } else if (aStart <= mMax) { + // Just chop the end of the range off + mMax = aStart - 1; + } + return next ? next->RemoveRange(aStart, aEnd) : NS_OK; + } + + nsresult Remove(int32_t aIndex) { + if (aIndex >= mMin && aIndex <= mMax) { + // We have found the range that contains us. + if (mMin == mMax) { + // Delete the whole range. + if (mPrev) + mPrev->mNext = mNext; + if (mNext) + mNext->mPrev = mPrev; + nsTreeRange* first = mSelection->mFirstRange; + if (first == this) + mSelection->mFirstRange = mNext; + mNext = mPrev = nullptr; + delete this; + } + else if (aIndex == mMin) + mMin++; + else if (aIndex == mMax) + mMax--; + else { + // We have to break this range. + nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex + 1, mMax); + if (!newRange) + return NS_ERROR_OUT_OF_MEMORY; + + newRange->Connect(this, mNext); + mMax = aIndex - 1; + } + } + else if (mNext) + return mNext->Remove(aIndex); + + return NS_OK; + } + + nsresult Add(int32_t aIndex) { + if (aIndex < mMin) { + // We have found a spot to insert. + if (aIndex + 1 == mMin) + mMin = aIndex; + else if (mPrev && mPrev->mMax+1 == aIndex) + mPrev->mMax = aIndex; + else { + // We have to create a new range. + nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex); + if (!newRange) + return NS_ERROR_OUT_OF_MEMORY; + + newRange->Connect(mPrev, this); + } + } + else if (mNext) + mNext->Add(aIndex); + else { + // Insert on to the end. + if (mMax+1 == aIndex) + mMax = aIndex; + else { + // We have to create a new range. + nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex); + if (!newRange) + return NS_ERROR_OUT_OF_MEMORY; + + newRange->Connect(this, nullptr); + } + } + return NS_OK; + } + + bool Contains(int32_t aIndex) { + if (aIndex >= mMin && aIndex <= mMax) + return true; + + if (mNext) + return mNext->Contains(aIndex); + + return false; + } + + int32_t Count() { + int32_t total = mMax - mMin + 1; + if (mNext) + total += mNext->Count(); + return total; + } + + static void CollectRanges(nsTreeRange* aRange, nsTArray<int32_t>& aRanges) + { + nsTreeRange* cur = aRange; + while (cur) { + aRanges.AppendElement(cur->mMin); + aRanges.AppendElement(cur->mMax); + cur = cur->mNext; + } + } + + static void InvalidateRanges(nsITreeBoxObject* aTree, + nsTArray<int32_t>& aRanges) + { + if (aTree) { + nsCOMPtr<nsITreeBoxObject> tree = aTree; + for (uint32_t i = 0; i < aRanges.Length(); i += 2) { + aTree->InvalidateRange(aRanges[i], aRanges[i + 1]); + } + } + } + + void Invalidate() { + nsTArray<int32_t> ranges; + CollectRanges(this, ranges); + InvalidateRanges(mSelection->mTree, ranges); + + } + + void RemoveAllBut(int32_t aIndex) { + if (aIndex >= mMin && aIndex <= mMax) { + + // Invalidate everything in this list. + nsTArray<int32_t> ranges; + CollectRanges(mSelection->mFirstRange, ranges); + + mMin = aIndex; + mMax = aIndex; + + nsTreeRange* first = mSelection->mFirstRange; + if (mPrev) + mPrev->mNext = mNext; + if (mNext) + mNext->mPrev = mPrev; + mNext = mPrev = nullptr; + + if (first != this) { + delete mSelection->mFirstRange; + mSelection->mFirstRange = this; + } + InvalidateRanges(mSelection->mTree, ranges); + } + else if (mNext) + mNext->RemoveAllBut(aIndex); + } + + void Insert(nsTreeRange* aRange) { + if (mMin >= aRange->mMax) + aRange->Connect(mPrev, this); + else if (mNext) + mNext->Insert(aRange); + else + aRange->Connect(this, nullptr); + } +}; + +nsTreeSelection::nsTreeSelection(nsITreeBoxObject* aTree) + : mTree(aTree), + mSuppressed(false), + mCurrentIndex(-1), + mShiftSelectPivot(-1), + mFirstRange(nullptr) +{ +} + +nsTreeSelection::~nsTreeSelection() +{ + delete mFirstRange; + if (mSelectTimer) + mSelectTimer->Cancel(); +} + +NS_IMPL_CYCLE_COLLECTION_2(nsTreeSelection, mTree, mCurrentColumn) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeSelection) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeSelection) + +DOMCI_DATA(TreeSelection, nsTreeSelection) + +// QueryInterface implementation for nsBoxObject +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeSelection) + NS_INTERFACE_MAP_ENTRY(nsITreeSelection) + NS_INTERFACE_MAP_ENTRY(nsINativeTreeSelection) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeSelection) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP nsTreeSelection::GetTree(nsITreeBoxObject * *aTree) +{ + NS_IF_ADDREF(*aTree = mTree); + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::SetTree(nsITreeBoxObject * aTree) +{ + if (mSelectTimer) { + mSelectTimer->Cancel(); + mSelectTimer = nullptr; + } + + // Make sure aTree really implements nsITreeBoxObject and nsIBoxObject! + nsCOMPtr<nsIBoxObject> bo = do_QueryInterface(aTree); + mTree = do_QueryInterface(bo); + NS_ENSURE_STATE(mTree == aTree); + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::GetSingle(bool* aSingle) +{ + if (!mTree) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree); + + nsCOMPtr<nsIDOMElement> element; + boxObject->GetElement(getter_AddRefs(element)); + + nsCOMPtr<nsIContent> content = do_QueryInterface(element); + + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::single, &nsGkAtoms::cell, &nsGkAtoms::text, nullptr}; + + *aSingle = content->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::seltype, + strings, eCaseMatters) >= 0; + + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::IsSelected(int32_t aIndex, bool* aResult) +{ + if (mFirstRange) + *aResult = mFirstRange->Contains(aIndex); + else + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::TimedSelect(int32_t aIndex, int32_t aMsec) +{ + bool suppressSelect = mSuppressed; + + if (aMsec != -1) + mSuppressed = true; + + nsresult rv = Select(aIndex); + if (NS_FAILED(rv)) + return rv; + + if (aMsec != -1) { + mSuppressed = suppressSelect; + if (!mSuppressed) { + if (mSelectTimer) + mSelectTimer->Cancel(); + + mSelectTimer = do_CreateInstance("@mozilla.org/timer;1"); + mSelectTimer->InitWithFuncCallback(SelectCallback, this, aMsec, + nsITimer::TYPE_ONE_SHOT); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::Select(int32_t aIndex) +{ + mShiftSelectPivot = -1; + + nsresult rv = SetCurrentIndex(aIndex); + if (NS_FAILED(rv)) + return rv; + + if (mFirstRange) { + bool alreadySelected = mFirstRange->Contains(aIndex); + + if (alreadySelected) { + int32_t count = mFirstRange->Count(); + if (count > 1) { + // We need to deselect everything but our item. + mFirstRange->RemoveAllBut(aIndex); + FireOnSelectHandler(); + } + return NS_OK; + } + else { + // Clear out our selection. + mFirstRange->Invalidate(); + delete mFirstRange; + } + } + + // Create our new selection. + mFirstRange = new nsTreeRange(this, aIndex); + if (!mFirstRange) + return NS_ERROR_OUT_OF_MEMORY; + + mFirstRange->Invalidate(); + + // Fire the select event + FireOnSelectHandler(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::ToggleSelect(int32_t aIndex) +{ + // There are six cases that can occur on a ToggleSelect with our + // range code. + // (1) A new range should be made for a selection. + // (2) A single range is removed from the selection. + // (3) The item is added to an existing range. + // (4) The item is removed from an existing range. + // (5) The addition of the item causes two ranges to be merged. + // (6) The removal of the item causes two ranges to be split. + mShiftSelectPivot = -1; + nsresult rv = SetCurrentIndex(aIndex); + if (NS_FAILED(rv)) + return rv; + + if (!mFirstRange) + Select(aIndex); + else { + if (!mFirstRange->Contains(aIndex)) { + bool single; + rv = GetSingle(&single); + if (NS_SUCCEEDED(rv) && !single) + rv = mFirstRange->Add(aIndex); + } + else + rv = mFirstRange->Remove(aIndex); + if (NS_SUCCEEDED(rv)) { + if (mTree) + mTree->InvalidateRow(aIndex); + + FireOnSelectHandler(); + } + } + + return rv; +} + +NS_IMETHODIMP nsTreeSelection::RangedSelect(int32_t aStartIndex, int32_t aEndIndex, bool aAugment) +{ + bool single; + nsresult rv = GetSingle(&single); + if (NS_FAILED(rv)) + return rv; + + if ((mFirstRange || (aStartIndex != aEndIndex)) && single) + return NS_OK; + + if (!aAugment) { + // Clear our selection. + if (mFirstRange) { + mFirstRange->Invalidate(); + delete mFirstRange; + mFirstRange = nullptr; + } + } + + if (aStartIndex == -1) { + if (mShiftSelectPivot != -1) + aStartIndex = mShiftSelectPivot; + else if (mCurrentIndex != -1) + aStartIndex = mCurrentIndex; + else + aStartIndex = aEndIndex; + } + + mShiftSelectPivot = aStartIndex; + rv = SetCurrentIndex(aEndIndex); + if (NS_FAILED(rv)) + return rv; + + int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex; + int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex; + + if (aAugment && mFirstRange) { + // We need to remove all the items within our selected range from the selection, + // and then we insert our new range into the list. + nsresult rv = mFirstRange->RemoveRange(start, end); + if (NS_FAILED(rv)) + return rv; + } + + nsTreeRange* range = new nsTreeRange(this, start, end); + if (!range) + return NS_ERROR_OUT_OF_MEMORY; + + range->Invalidate(); + + if (aAugment && mFirstRange) + mFirstRange->Insert(range); + else + mFirstRange = range; + + FireOnSelectHandler(); + + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::ClearRange(int32_t aStartIndex, int32_t aEndIndex) +{ + nsresult rv = SetCurrentIndex(aEndIndex); + if (NS_FAILED(rv)) + return rv; + + if (mFirstRange) { + int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex; + int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex; + + mFirstRange->RemoveRange(start, end); + + if (mTree) + mTree->InvalidateRange(start, end); + } + + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::ClearSelection() +{ + if (mFirstRange) { + mFirstRange->Invalidate(); + delete mFirstRange; + mFirstRange = nullptr; + } + mShiftSelectPivot = -1; + + FireOnSelectHandler(); + + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::InvertSelection() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsTreeSelection::SelectAll() +{ + if (!mTree) + return NS_OK; + + nsCOMPtr<nsITreeView> view; + mTree->GetView(getter_AddRefs(view)); + if (!view) + return NS_OK; + + int32_t rowCount; + view->GetRowCount(&rowCount); + bool single; + nsresult rv = GetSingle(&single); + if (NS_FAILED(rv)) + return rv; + + if (rowCount == 0 || (rowCount > 1 && single)) + return NS_OK; + + mShiftSelectPivot = -1; + + // Invalidate not necessary when clearing selection, since + // we're going to invalidate the world on the SelectAll. + delete mFirstRange; + + mFirstRange = new nsTreeRange(this, 0, rowCount-1); + mFirstRange->Invalidate(); + + FireOnSelectHandler(); + + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::GetRangeCount(int32_t* aResult) +{ + int32_t count = 0; + nsTreeRange* curr = mFirstRange; + while (curr) { + count++; + curr = curr->mNext; + } + + *aResult = count; + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::GetRangeAt(int32_t aIndex, int32_t* aMin, int32_t* aMax) +{ + *aMin = *aMax = -1; + int32_t i = -1; + nsTreeRange* curr = mFirstRange; + while (curr) { + i++; + if (i == aIndex) { + *aMin = curr->mMin; + *aMax = curr->mMax; + break; + } + curr = curr->mNext; + } + + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::GetCount(int32_t *count) +{ + if (mFirstRange) + *count = mFirstRange->Count(); + else // No range available, so there's no selected row. + *count = 0; + + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::GetSelectEventsSuppressed(bool *aSelectEventsSuppressed) +{ + *aSelectEventsSuppressed = mSuppressed; + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::SetSelectEventsSuppressed(bool aSelectEventsSuppressed) +{ + mSuppressed = aSelectEventsSuppressed; + if (!mSuppressed) + FireOnSelectHandler(); + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::GetCurrentIndex(int32_t *aCurrentIndex) +{ + *aCurrentIndex = mCurrentIndex; + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::SetCurrentIndex(int32_t aIndex) +{ + if (!mTree) { + return NS_ERROR_UNEXPECTED; + } + if (mCurrentIndex == aIndex) { + return NS_OK; + } + if (mCurrentIndex != -1 && mTree) + mTree->InvalidateRow(mCurrentIndex); + + mCurrentIndex = aIndex; + if (!mTree) + return NS_OK; + + if (aIndex != -1) + mTree->InvalidateRow(aIndex); + + // Fire DOMMenuItemActive or DOMMenuItemInactive event for tree. + nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree); + NS_ASSERTION(boxObject, "no box object!"); + if (!boxObject) + return NS_ERROR_UNEXPECTED; + nsCOMPtr<nsIDOMElement> treeElt; + boxObject->GetElement(getter_AddRefs(treeElt)); + + nsCOMPtr<nsINode> treeDOMNode(do_QueryInterface(treeElt)); + NS_ENSURE_STATE(treeDOMNode); + + NS_NAMED_LITERAL_STRING(DOMMenuItemActive, "DOMMenuItemActive"); + NS_NAMED_LITERAL_STRING(DOMMenuItemInactive, "DOMMenuItemInactive"); + + nsRefPtr<nsAsyncDOMEvent> event = + new nsAsyncDOMEvent(treeDOMNode, + (aIndex != -1 ? DOMMenuItemActive : DOMMenuItemInactive), + true, false); + return event->PostDOMEvent(); +} + +NS_IMETHODIMP nsTreeSelection::GetCurrentColumn(nsITreeColumn** aCurrentColumn) +{ + NS_IF_ADDREF(*aCurrentColumn = mCurrentColumn); + return NS_OK; +} + +NS_IMETHODIMP nsTreeSelection::SetCurrentColumn(nsITreeColumn* aCurrentColumn) +{ + if (!mTree) { + return NS_ERROR_UNEXPECTED; + } + if (mCurrentColumn == aCurrentColumn) { + return NS_OK; + } + + if (mCurrentColumn) { + if (mFirstRange) + mTree->InvalidateCell(mFirstRange->mMin, mCurrentColumn); + if (mCurrentIndex != -1) + mTree->InvalidateCell(mCurrentIndex, mCurrentColumn); + } + + mCurrentColumn = aCurrentColumn; + + if (mCurrentColumn) { + if (mFirstRange) + mTree->InvalidateCell(mFirstRange->mMin, mCurrentColumn); + if (mCurrentIndex != -1) + mTree->InvalidateCell(mCurrentIndex, mCurrentColumn); + } + + return NS_OK; +} + +#define ADD_NEW_RANGE(macro_range, macro_selection, macro_start, macro_end) \ + { \ + int32_t start = macro_start; \ + int32_t end = macro_end; \ + if (start > end) { \ + end = start; \ + } \ + nsTreeRange* macro_new_range = new nsTreeRange(macro_selection, start, end); \ + if (macro_range) \ + macro_range->Insert(macro_new_range); \ + else \ + macro_range = macro_new_range; \ + } + +NS_IMETHODIMP +nsTreeSelection::AdjustSelection(int32_t aIndex, int32_t aCount) +{ + NS_ASSERTION(aCount != 0, "adjusting by zero"); + if (!aCount) return NS_OK; + + // adjust mShiftSelectPivot, if necessary + if ((mShiftSelectPivot != 1) && (aIndex <= mShiftSelectPivot)) { + // if we are deleting and the delete includes the shift select pivot, reset it + if (aCount < 0 && (mShiftSelectPivot <= (aIndex -aCount -1))) { + mShiftSelectPivot = -1; + } + else { + mShiftSelectPivot += aCount; + } + } + + // adjust mCurrentIndex, if necessary + if ((mCurrentIndex != -1) && (aIndex <= mCurrentIndex)) { + // if we are deleting and the delete includes the current index, reset it + if (aCount < 0 && (mCurrentIndex <= (aIndex -aCount -1))) { + mCurrentIndex = -1; + } + else { + mCurrentIndex += aCount; + } + } + + // no selection, so nothing to do. + if (!mFirstRange) return NS_OK; + + bool selChanged = false; + nsTreeRange* oldFirstRange = mFirstRange; + nsTreeRange* curr = mFirstRange; + mFirstRange = nullptr; + while (curr) { + if (aCount > 0) { + // inserting + if (aIndex > curr->mMax) { + // adjustment happens after the range, so no change + ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax); + } + else if (aIndex <= curr->mMin) { + // adjustment happens before the start of the range, so shift down + ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount, curr->mMax + aCount); + selChanged = true; + } + else { + // adjustment happen inside the range. + // break apart the range and create two ranges + ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1); + ADD_NEW_RANGE(mFirstRange, this, aIndex + aCount, curr->mMax + aCount); + selChanged = true; + } + } + else { + // deleting + if (aIndex > curr->mMax) { + // adjustment happens after the range, so no change + ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax); + } + else { + // remember, aCount is negative + selChanged = true; + int32_t lastIndexOfAdjustment = aIndex - aCount - 1; + if (aIndex <= curr->mMin) { + if (lastIndexOfAdjustment < curr->mMin) { + // adjustment happens before the start of the range, so shift up + ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount, curr->mMax + aCount); + } + else if (lastIndexOfAdjustment >= curr->mMax) { + // adjustment contains the range. remove the range by not adding it to the newRange + } + else { + // adjustment starts before the range, and ends in the middle of it, so trim the range + ADD_NEW_RANGE(mFirstRange, this, aIndex, curr->mMax + aCount) + } + } + else if (lastIndexOfAdjustment >= curr->mMax) { + // adjustment starts in the middle of the current range, and contains the end of the range, so trim the range + ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1) + } + else { + // range contains the adjustment, so shorten the range + ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax + aCount) + } + } + } + curr = curr->mNext; + } + + delete oldFirstRange; + + // Fire the select event + if (selChanged) + FireOnSelectHandler(); + + return NS_OK; +} + +NS_IMETHODIMP +nsTreeSelection::InvalidateSelection() +{ + if (mFirstRange) + mFirstRange->Invalidate(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeSelection::GetShiftSelectPivot(int32_t* aIndex) +{ + *aIndex = mShiftSelectPivot; + return NS_OK; +} + + +nsresult +nsTreeSelection::FireOnSelectHandler() +{ + if (mSuppressed || !mTree) + return NS_OK; + + nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree); + NS_ASSERTION(boxObject, "no box object!"); + if (!boxObject) + return NS_ERROR_UNEXPECTED; + nsCOMPtr<nsIDOMElement> elt; + boxObject->GetElement(getter_AddRefs(elt)); + NS_ENSURE_STATE(elt); + + nsCOMPtr<nsINode> node(do_QueryInterface(elt)); + NS_ENSURE_STATE(node); + + nsRefPtr<nsAsyncDOMEvent> event = + new nsAsyncDOMEvent(node, NS_LITERAL_STRING("select"), true, false); + event->RunDOMEventWhenSafe(); + return NS_OK; +} + +void +nsTreeSelection::SelectCallback(nsITimer *aTimer, void *aClosure) +{ + nsRefPtr<nsTreeSelection> self = static_cast<nsTreeSelection*>(aClosure); + if (self) { + self->FireOnSelectHandler(); + aTimer->Cancel(); + self->mSelectTimer = nullptr; + } +} + +/////////////////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewTreeSelection(nsITreeBoxObject* aTree, nsITreeSelection** aResult) +{ + *aResult = new nsTreeSelection(aTree); + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/layout/xul/tree/nsTreeSelection.h b/layout/xul/tree/nsTreeSelection.h new file mode 100644 index 000000000..83425c77b --- /dev/null +++ b/layout/xul/tree/nsTreeSelection.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTreeSelection_h__ +#define nsTreeSelection_h__ + +#include "nsITreeSelection.h" +#include "nsITreeColumns.h" +#include "nsITimer.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" + +class nsITreeBoxObject; +struct nsTreeRange; + +class nsTreeSelection MOZ_FINAL : public nsINativeTreeSelection +{ +public: + nsTreeSelection(nsITreeBoxObject* aTree); + ~nsTreeSelection(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsTreeSelection) + NS_DECL_NSITREESELECTION + + // nsINativeTreeSelection: Untrusted code can use us + NS_IMETHOD EnsureNative() MOZ_OVERRIDE { return NS_OK; } + + friend struct nsTreeRange; + +protected: + nsresult FireOnSelectHandler(); + static void SelectCallback(nsITimer *aTimer, void *aClosure); + +protected: + // Members + nsCOMPtr<nsITreeBoxObject> mTree; // The tree will hold on to us through the view and let go when it dies. + + bool mSuppressed; // Whether or not we should be firing onselect events. + int32_t mCurrentIndex; // The item to draw the rect around. The last one clicked, etc. + nsCOMPtr<nsITreeColumn> mCurrentColumn; + int32_t mShiftSelectPivot; // Used when multiple SHIFT+selects are performed to pivot on. + + nsTreeRange* mFirstRange; // Our list of ranges. + + nsCOMPtr<nsITimer> mSelectTimer; +}; + +nsresult +NS_NewTreeSelection(nsITreeBoxObject* aTree, nsITreeSelection** aResult); + +#endif diff --git a/layout/xul/tree/nsTreeStyleCache.cpp b/layout/xul/tree/nsTreeStyleCache.cpp new file mode 100644 index 000000000..140a0f602 --- /dev/null +++ b/layout/xul/tree/nsTreeStyleCache.cpp @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTreeStyleCache.h" +#include "nsStyleSet.h" +#include "mozilla/dom/Element.h" + +// The style context cache impl +nsStyleContext* +nsTreeStyleCache::GetStyleContext(nsICSSPseudoComparator* aComparator, + nsPresContext* aPresContext, + nsIContent* aContent, + nsStyleContext* aContext, + nsIAtom* aPseudoElement, + const AtomArray & aInputWord) +{ + uint32_t count = aInputWord.Length(); + nsDFAState startState(0); + nsDFAState* currState = &startState; + + // Go ahead and init the transition table. + if (!mTransitionTable) { + // Automatic miss. Build the table + mTransitionTable = + new nsObjectHashtable(nullptr, nullptr, DeleteDFAState, nullptr); + } + + // The first transition is always made off the supplied pseudo-element. + nsTransitionKey key(currState->GetStateID(), aPseudoElement); + currState = static_cast<nsDFAState*>(mTransitionTable->Get(&key)); + + if (!currState) { + // We had a miss. Make a new state and add it to our hash. + currState = new nsDFAState(mNextState); + mNextState++; + mTransitionTable->Put(&key, currState); + } + + for (uint32_t i = 0; i < count; i++) { + nsTransitionKey key(currState->GetStateID(), aInputWord[i]); + currState = static_cast<nsDFAState*>(mTransitionTable->Get(&key)); + + if (!currState) { + // We had a miss. Make a new state and add it to our hash. + currState = new nsDFAState(mNextState); + mNextState++; + mTransitionTable->Put(&key, currState); + } + } + + // We're in a final state. + // Look up our style context for this state. + nsStyleContext* result = nullptr; + if (mCache) + result = static_cast<nsStyleContext*>(mCache->Get(currState)); + if (!result) { + // We missed the cache. Resolve this pseudo-style. + result = aPresContext->StyleSet()-> + ResolveXULTreePseudoStyle(aContent->AsElement(), aPseudoElement, + aContext, aComparator).get(); + + // Put the style context in our table, transferring the owning reference to the table. + if (!mCache) { + mCache = new nsObjectHashtable(nullptr, nullptr, ReleaseStyleContext, nullptr); + } + mCache->Put(currState, result); + } + + return result; +} + +bool +nsTreeStyleCache::DeleteDFAState(nsHashKey *aKey, + void *aData, + void *closure) +{ + nsDFAState* entry = static_cast<nsDFAState*>(aData); + delete entry; + return true; +} + +bool +nsTreeStyleCache::ReleaseStyleContext(nsHashKey *aKey, + void *aData, + void *closure) +{ + nsStyleContext* context = static_cast<nsStyleContext*>(aData); + context->Release(); + return true; +} diff --git a/layout/xul/tree/nsTreeStyleCache.h b/layout/xul/tree/nsTreeStyleCache.h new file mode 100644 index 000000000..68923c1bd --- /dev/null +++ b/layout/xul/tree/nsTreeStyleCache.h @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTreeStyleCache_h__ +#define nsTreeStyleCache_h__ + +#include "mozilla/Attributes.h" +#include "nsHashtable.h" +#include "nsIAtom.h" +#include "nsCOMArray.h" +#include "nsICSSPseudoComparator.h" +#include "nsStyleContext.h" + +typedef nsCOMArray<nsIAtom> AtomArray; + +class nsDFAState : public nsHashKey +{ +public: + uint32_t mStateID; + + nsDFAState(uint32_t aID) :mStateID(aID) {} + + uint32_t GetStateID() { return mStateID; } + + uint32_t HashCode(void) const MOZ_OVERRIDE { + return mStateID; + } + + bool Equals(const nsHashKey *aKey) const MOZ_OVERRIDE { + nsDFAState* key = (nsDFAState*)aKey; + return key->mStateID == mStateID; + } + + nsHashKey *Clone(void) const MOZ_OVERRIDE { + return new nsDFAState(mStateID); + } +}; + +class nsTransitionKey : public nsHashKey +{ +public: + uint32_t mState; + nsCOMPtr<nsIAtom> mInputSymbol; + + nsTransitionKey(uint32_t aState, nsIAtom* aSymbol) :mState(aState), mInputSymbol(aSymbol) {} + + uint32_t HashCode(void) const MOZ_OVERRIDE { + // Make a 32-bit integer that combines the low-order 16 bits of the state and the input symbol. + int32_t hb = mState << 16; + int32_t lb = (NS_PTR_TO_INT32(mInputSymbol.get()) << 16) >> 16; + return hb+lb; + } + + bool Equals(const nsHashKey *aKey) const MOZ_OVERRIDE { + nsTransitionKey* key = (nsTransitionKey*)aKey; + return key->mState == mState && key->mInputSymbol == mInputSymbol; + } + + nsHashKey *Clone(void) const MOZ_OVERRIDE { + return new nsTransitionKey(mState, mInputSymbol); + } +}; + +class nsTreeStyleCache +{ +public: + nsTreeStyleCache() :mTransitionTable(nullptr), mCache(nullptr), mNextState(0) {} + ~nsTreeStyleCache() { Clear(); } + + void Clear() { delete mTransitionTable; mTransitionTable = nullptr; delete mCache; mCache = nullptr; mNextState = 0; } + + nsStyleContext* GetStyleContext(nsICSSPseudoComparator* aComparator, + nsPresContext* aPresContext, + nsIContent* aContent, + nsStyleContext* aContext, + nsIAtom* aPseudoElement, + const AtomArray & aInputWord); + + static bool DeleteDFAState(nsHashKey *aKey, void *aData, void *closure); + + static bool ReleaseStyleContext(nsHashKey *aKey, void *aData, void *closure); + +protected: + // A transition table for a deterministic finite automaton. The DFA + // takes as its input a single pseudoelement and an ordered set of properties. + // It transitions on an input word that is the concatenation of the pseudoelement supplied + // with the properties in the array. + // + // It transitions from state to state by looking up entries in the transition table (which is + // a mapping from (S,i)->S', where S is the current state, i is the next + // property in the input word, and S' is the state to transition to. + // + // If S' is not found, it is constructed and entered into the hashtable + // under the key (S,i). + // + // Once the entire word has been consumed, the final state is used + // to reference the cache table to locate the style context. + nsObjectHashtable* mTransitionTable; + + // The cache of all active style contexts. This is a hash from + // a final state in the DFA, Sf, to the resultant style context. + nsObjectHashtable* mCache; + + // An integer counter that is used when we need to make new states in the + // DFA. + uint32_t mNextState; +}; + +#endif // nsTreeStyleCache_h__ diff --git a/layout/xul/tree/nsTreeUtils.cpp b/layout/xul/tree/nsTreeUtils.cpp new file mode 100644 index 000000000..f14b3c973 --- /dev/null +++ b/layout/xul/tree/nsTreeUtils.cpp @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsReadableUtils.h" +#include "nsTreeUtils.h" +#include "nsChildIterator.h" +#include "nsCRT.h" +#include "nsIAtom.h" +#include "nsINameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsINodeInfo.h" + +nsresult +nsTreeUtils::TokenizeProperties(const nsAString& aProperties, AtomArray & aPropertiesArray) +{ + nsAString::const_iterator end; + aProperties.EndReading(end); + + nsAString::const_iterator iter; + aProperties.BeginReading(iter); + + do { + // Skip whitespace + while (iter != end && nsCRT::IsAsciiSpace(*iter)) + ++iter; + + // If only whitespace, we're done + if (iter == end) + break; + + // Note the first non-whitespace character + nsAString::const_iterator first = iter; + + // Advance to the next whitespace character + while (iter != end && ! nsCRT::IsAsciiSpace(*iter)) + ++iter; + + // XXX this would be nonsensical + NS_ASSERTION(iter != first, "eh? something's wrong here"); + if (iter == first) + break; + + nsCOMPtr<nsIAtom> atom = do_GetAtom(Substring(first, iter)); + aPropertiesArray.AppendElement(atom); + } while (iter != end); + + return NS_OK; +} + +nsIContent* +nsTreeUtils::GetImmediateChild(nsIContent* aContainer, nsIAtom* aTag) +{ + ChildIterator iter, last; + for (ChildIterator::Init(aContainer, &iter, &last); iter != last; ++iter) { + nsIContent* child = *iter; + + if (child->Tag() == aTag) { + return child; + } + } + + return nullptr; +} + +nsIContent* +nsTreeUtils::GetDescendantChild(nsIContent* aContainer, nsIAtom* aTag) +{ + ChildIterator iter, last; + for (ChildIterator::Init(aContainer, &iter, &last); iter != last; ++iter) { + nsIContent* child = *iter; + if (child->Tag() == aTag) { + return child; + } + + child = GetDescendantChild(child, aTag); + if (child) { + return child; + } + } + + return nullptr; +} + +nsresult +nsTreeUtils::UpdateSortIndicators(nsIContent* aColumn, const nsAString& aDirection) +{ + aColumn->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, aDirection, true); + aColumn->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive, NS_LITERAL_STRING("true"), true); + + // Unset sort attribute(s) on the other columns + nsCOMPtr<nsIContent> parentContent = aColumn->GetParent(); + if (parentContent && + parentContent->NodeInfo()->Equals(nsGkAtoms::treecols, + kNameSpaceID_XUL)) { + uint32_t i, numChildren = parentContent->GetChildCount(); + for (i = 0; i < numChildren; ++i) { + nsCOMPtr<nsIContent> childContent = parentContent->GetChildAt(i); + + if (childContent && + childContent != aColumn && + childContent->NodeInfo()->Equals(nsGkAtoms::treecol, + kNameSpaceID_XUL)) { + childContent->UnsetAttr(kNameSpaceID_None, + nsGkAtoms::sortDirection, true); + childContent->UnsetAttr(kNameSpaceID_None, + nsGkAtoms::sortActive, true); + } + } + } + + return NS_OK; +} + +nsresult +nsTreeUtils::GetColumnIndex(nsIContent* aColumn, int32_t* aResult) +{ + nsIContent* parentContent = aColumn->GetParent(); + if (parentContent && + parentContent->NodeInfo()->Equals(nsGkAtoms::treecols, + kNameSpaceID_XUL)) { + uint32_t i, numChildren = parentContent->GetChildCount(); + int32_t colIndex = 0; + for (i = 0; i < numChildren; ++i) { + nsIContent *childContent = parentContent->GetChildAt(i); + if (childContent && + childContent->NodeInfo()->Equals(nsGkAtoms::treecol, + kNameSpaceID_XUL)) { + if (childContent == aColumn) { + *aResult = colIndex; + return NS_OK; + } + ++colIndex; + } + } + } + + *aResult = -1; + return NS_OK; +} diff --git a/layout/xul/tree/nsTreeUtils.h b/layout/xul/tree/nsTreeUtils.h new file mode 100644 index 000000000..f2ed2df68 --- /dev/null +++ b/layout/xul/tree/nsTreeUtils.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTreeUtils_h__ +#define nsTreeUtils_h__ + +#include "nsError.h" +#include "nsString.h" +#include "nsTreeStyleCache.h" + +class nsIAtom; +class nsIContent; + +class nsTreeUtils +{ + public: + /** + * Parse a whitespace separated list of properties into an array + * of atoms. + */ + static nsresult + TokenizeProperties(const nsAString& aProperties, AtomArray & aPropertiesArray); + + static nsIContent* + GetImmediateChild(nsIContent* aContainer, nsIAtom* aTag); + + static nsIContent* + GetDescendantChild(nsIContent* aContainer, nsIAtom* aTag); + + static nsresult + UpdateSortIndicators(nsIContent* aColumn, const nsAString& aDirection); + + static nsresult + GetColumnIndex(nsIContent* aColumn, int32_t* aResult); +}; + +#endif // nsTreeUtils_h__ |