diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /layout/xul | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/xul')
380 files changed, 54040 insertions, 0 deletions
diff --git a/layout/xul/BoxObject.cpp b/layout/xul/BoxObject.cpp new file mode 100644 index 000000000..6636a6d62 --- /dev/null +++ b/layout/xul/BoxObject.cpp @@ -0,0 +1,614 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "mozilla/dom/BoxObject.h" +#include "nsCOMPtr.h" +#include "nsIDocument.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsContainerFrame.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" +#include "nsComponentManagerUtils.h" +#include "mozilla/dom/BoxObjectBinding.h" + +// Implementation ///////////////////////////////////////////////////////////////// + +namespace mozilla { +namespace dom { + +// Static member variable initialization + +// Implement our nsISupports methods +NS_IMPL_CYCLE_COLLECTION_CLASS(BoxObject) +NS_IMPL_CYCLE_COLLECTING_ADDREF(BoxObject) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BoxObject) + +// QueryInterface implementation for BoxObject +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BoxObject) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsIBoxObject) + NS_INTERFACE_MAP_ENTRY(nsPIBoxObject) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BoxObject) + // XXX jmorton: why aren't we unlinking mPropertyTable? + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BoxObject) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + if (tmp->mPropertyTable) { + for (auto iter = tmp->mPropertyTable->Iter(); !iter.Done(); iter.Next()) { + cb.NoteXPCOMChild(iter.UserData()); + } + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(BoxObject) + +// Constructors/Destructors +BoxObject::BoxObject() + : mContent(nullptr) +{ +} + +BoxObject::~BoxObject() +{ +} + +NS_IMETHODIMP +BoxObject::GetElement(nsIDOMElement** aResult) +{ + if (mContent) { + return CallQueryInterface(mContent, aResult); + } + + *aResult = nullptr; + return NS_OK; +} + +// nsPIBoxObject ////////////////////////////////////////////////////////////////////////// + +nsresult +BoxObject::Init(nsIContent* aContent) +{ + mContent = aContent; + return NS_OK; +} + +void +BoxObject::Clear() +{ + mPropertyTable = nullptr; + mContent = nullptr; +} + +void +BoxObject::ClearCachedValues() +{ +} + +nsIFrame* +BoxObject::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* +BoxObject::GetPresShell(bool aFlushLayout) +{ + if (!mContent) { + return nullptr; + } + + nsCOMPtr<nsIDocument> doc = mContent->GetUncomposedDoc(); + if (!doc) { + return nullptr; + } + + if (aFlushLayout) { + doc->FlushPendingNotifications(Flush_Layout); + } + + return doc->GetShell(); +} + +nsresult +BoxObject::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->GetComposedDoc()->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 +BoxObject::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 +BoxObject::GetX(int32_t* aResult) +{ + nsIntRect rect; + GetOffsetRect(rect); + *aResult = rect.x; + return NS_OK; +} + +NS_IMETHODIMP +BoxObject::GetY(int32_t* aResult) +{ + nsIntRect rect; + GetOffsetRect(rect); + *aResult = rect.y; + return NS_OK; +} + +NS_IMETHODIMP +BoxObject::GetWidth(int32_t* aResult) +{ + nsIntRect rect; + GetOffsetRect(rect); + *aResult = rect.width; + return NS_OK; +} + +NS_IMETHODIMP +BoxObject::GetHeight(int32_t* aResult) +{ + nsIntRect rect; + GetOffsetRect(rect); + *aResult = rect.height; + return NS_OK; +} + +NS_IMETHODIMP +BoxObject::GetScreenX(int32_t *_retval) +{ + nsIntPoint position; + nsresult rv = GetScreenPosition(position); + if (NS_FAILED(rv)) return rv; + *_retval = position.x; + return NS_OK; +} + +NS_IMETHODIMP +BoxObject::GetScreenY(int32_t *_retval) +{ + nsIntPoint position; + nsresult rv = GetScreenPosition(position); + if (NS_FAILED(rv)) return rv; + *_retval = position.y; + return NS_OK; +} + +NS_IMETHODIMP +BoxObject::GetPropertyAsSupports(const char16_t* 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 +BoxObject::SetPropertyAsSupports(const char16_t* aPropertyName, nsISupports* aValue) +{ + NS_ENSURE_ARG(aPropertyName && *aPropertyName); + + if (!mPropertyTable) { + mPropertyTable = new nsInterfaceHashtable<nsStringHashKey,nsISupports>(4); + } + + nsDependentString propertyName(aPropertyName); + mPropertyTable->Put(propertyName, aValue); + return NS_OK; +} + +NS_IMETHODIMP +BoxObject::GetProperty(const char16_t* aPropertyName, char16_t** 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 +BoxObject::SetProperty(const char16_t* aPropertyName, const char16_t* 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 +BoxObject::RemoveProperty(const char16_t* aPropertyName) +{ + NS_ENSURE_ARG(aPropertyName && *aPropertyName); + + if (!mPropertyTable) return NS_OK; + + nsDependentString propertyName(aPropertyName); + mPropertyTable->Remove(propertyName); + return NS_OK; +} + +NS_IMETHODIMP +BoxObject::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 +BoxObject::GetFirstChild(nsIDOMElement * *aFirstVisibleChild) +{ + *aFirstVisibleChild = nullptr; + nsIFrame* frame = GetFrame(false); + if (!frame) return NS_OK; + nsIFrame* firstFrame = frame->PrincipalChildList().FirstChild(); + 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 +BoxObject::GetLastChild(nsIDOMElement * *aLastVisibleChild) +{ + *aLastVisibleChild = nullptr; + nsIFrame* frame = GetFrame(false); + if (!frame) return NS_OK; + return GetPreviousSibling(frame, nullptr, aLastVisibleChild); +} + +NS_IMETHODIMP +BoxObject::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 +BoxObject::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 +BoxObject::GetPreviousSibling(nsIFrame* aParentFrame, nsIFrame* aFrame, + nsIDOMElement** aResult) +{ + *aResult = nullptr; + nsIFrame* nextFrame = aParentFrame->PrincipalChildList().FirstChild(); + 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; +} + +nsIContent* +BoxObject::GetParentObject() const +{ + return mContent; +} + +JSObject* +BoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return BoxObjectBinding::Wrap(aCx, this, aGivenProto); +} + +Element* +BoxObject::GetElement() const +{ + return mContent && mContent->IsElement() ? mContent->AsElement() : nullptr; +} + +int32_t +BoxObject::X() +{ + int32_t ret = 0; + GetX(&ret); + return ret; +} + +int32_t +BoxObject::Y() +{ + int32_t ret = 0; + GetY(&ret); + return ret; +} + +int32_t +BoxObject::GetScreenX(ErrorResult& aRv) +{ + int32_t ret = 0; + aRv = GetScreenX(&ret); + return ret; +} + +int32_t +BoxObject::GetScreenY(ErrorResult& aRv) +{ + int32_t ret = 0; + aRv = GetScreenY(&ret); + return ret; +} + +int32_t +BoxObject::Width() +{ + int32_t ret = 0; + GetWidth(&ret); + return ret; +} + +int32_t +BoxObject::Height() +{ + int32_t ret = 0; + GetHeight(&ret); + return ret; +} + +already_AddRefed<nsISupports> +BoxObject::GetPropertyAsSupports(const nsAString& propertyName) +{ + nsCOMPtr<nsISupports> ret; + GetPropertyAsSupports(PromiseFlatString(propertyName).get(), getter_AddRefs(ret)); + return ret.forget(); +} + +void +BoxObject::SetPropertyAsSupports(const nsAString& propertyName, nsISupports* value) +{ + SetPropertyAsSupports(PromiseFlatString(propertyName).get(), value); +} + +void +BoxObject::GetProperty(const nsAString& propertyName, nsString& aRetVal, ErrorResult& aRv) +{ + nsCOMPtr<nsISupports> data(GetPropertyAsSupports(propertyName)); + if (!data) { + return; + } + + nsCOMPtr<nsISupportsString> supportsStr(do_QueryInterface(data)); + if (!supportsStr) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + supportsStr->GetData(aRetVal); +} + +void +BoxObject::SetProperty(const nsAString& propertyName, const nsAString& propertyValue) +{ + SetProperty(PromiseFlatString(propertyName).get(), PromiseFlatString(propertyValue).get()); +} + +void +BoxObject::RemoveProperty(const nsAString& propertyName) +{ + RemoveProperty(PromiseFlatString(propertyName).get()); +} + +already_AddRefed<Element> +BoxObject::GetParentBox() +{ + nsCOMPtr<nsIDOMElement> el; + GetParentBox(getter_AddRefs(el)); + nsCOMPtr<Element> ret(do_QueryInterface(el)); + return ret.forget(); +} + +already_AddRefed<Element> +BoxObject::GetFirstChild() +{ + nsCOMPtr<nsIDOMElement> el; + GetFirstChild(getter_AddRefs(el)); + nsCOMPtr<Element> ret(do_QueryInterface(el)); + return ret.forget(); +} + +already_AddRefed<Element> +BoxObject::GetLastChild() +{ + nsCOMPtr<nsIDOMElement> el; + GetLastChild(getter_AddRefs(el)); + nsCOMPtr<Element> ret(do_QueryInterface(el)); + return ret.forget(); +} + +already_AddRefed<Element> +BoxObject::GetNextSibling() +{ + nsCOMPtr<nsIDOMElement> el; + GetNextSibling(getter_AddRefs(el)); + nsCOMPtr<Element> ret(do_QueryInterface(el)); + return ret.forget(); +} + +already_AddRefed<Element> +BoxObject::GetPreviousSibling() +{ + nsCOMPtr<nsIDOMElement> el; + GetPreviousSibling(getter_AddRefs(el)); + nsCOMPtr<Element> ret(do_QueryInterface(el)); + return ret.forget(); +} + +} // namespace dom +} // namespace mozilla + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +using namespace mozilla::dom; + +nsresult +NS_NewBoxObject(nsIBoxObject** aResult) +{ + NS_ADDREF(*aResult = new BoxObject()); + return NS_OK; +} diff --git a/layout/xul/BoxObject.h b/layout/xul/BoxObject.h new file mode 100644 index 000000000..ac3df420b --- /dev/null +++ b/layout/xul/BoxObject.h @@ -0,0 +1,91 @@ +/* -*- 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 mozilla_dom_BoxObject_h__ +#define mozilla_dom_BoxObject_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.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" +#include "nsWrapperCache.h" +#include "nsRect.h" + +class nsIFrame; +class nsIPresShell; + +namespace mozilla { +namespace dom { + +class Element; + +class BoxObject : public nsPIBoxObject, + public nsWrapperCache +{ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BoxObject) + NS_DECL_NSIBOXOBJECT + +public: + BoxObject(); + + // nsPIBoxObject + virtual nsresult Init(nsIContent* aContent) override; + virtual void Clear() override; + virtual void ClearCachedValues() 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); + + // WebIDL (wraps old impls) + nsIContent* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + Element* GetElement() const; + + int32_t X(); + int32_t Y(); + int32_t GetScreenX(ErrorResult& aRv); + int32_t GetScreenY(ErrorResult& aRv); + int32_t Width(); + int32_t Height(); + + already_AddRefed<nsISupports> GetPropertyAsSupports(const nsAString& propertyName); + void SetPropertyAsSupports(const nsAString& propertyName, nsISupports* value); + void GetProperty(const nsAString& propertyName, nsString& aRetVal, ErrorResult& aRv); + void SetProperty(const nsAString& propertyName, const nsAString& propertyValue); + void RemoveProperty(const nsAString& propertyName); + + already_AddRefed<Element> GetParentBox(); + already_AddRefed<Element> GetFirstChild(); + already_AddRefed<Element> GetLastChild(); + already_AddRefed<Element> GetNextSibling(); + already_AddRefed<Element> GetPreviousSibling(); + +protected: + virtual ~BoxObject(); + + nsAutoPtr<nsInterfaceHashtable<nsStringHashKey,nsISupports> > mPropertyTable; //[OWNER] + + nsIContent* mContent; // [WEAK] +}; + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/layout/xul/ContainerBoxObject.cpp b/layout/xul/ContainerBoxObject.cpp new file mode 100644 index 000000000..0464a6fec --- /dev/null +++ b/layout/xul/ContainerBoxObject.cpp @@ -0,0 +1,75 @@ +/* -*- 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/dom/ContainerBoxObject.h" +#include "mozilla/dom/ContainerBoxObjectBinding.h" +#include "nsCOMPtr.h" +#include "nsIDocShell.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIFrame.h" +#include "nsSubDocumentFrame.h" + +namespace mozilla { +namespace dom { + +ContainerBoxObject::ContainerBoxObject() +{ +} + +ContainerBoxObject::~ContainerBoxObject() +{ +} + +JSObject* +ContainerBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return ContainerBoxObjectBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<nsIDocShell> +ContainerBoxObject::GetDocShell() +{ + nsSubDocumentFrame *subDocFrame = do_QueryFrame(GetFrame(false)); + if (subDocFrame) { + // Ok, the frame for mContent is an nsSubDocumentFrame, it knows how + // to reach the docshell, so ask it... + nsCOMPtr<nsIDocShell> ret; + subDocFrame->GetDocShell(getter_AddRefs(ret)); + return ret.forget(); + } + + if (!mContent) { + return nullptr; + } + + // No nsSubDocumentFrame available for mContent, try if there's a mapping + // between mContent's document to mContent's subdocument. + + nsIDocument *doc = mContent->GetComposedDoc(); + + if (!doc) { + return nullptr; + } + + nsIDocument *sub_doc = doc->GetSubDocumentFor(mContent); + + if (!sub_doc) { + return nullptr; + } + + nsCOMPtr<nsIDocShell> result = sub_doc->GetDocShell(); + return result.forget(); +} + +} // namespace dom +} // namespace mozilla + +nsresult +NS_NewContainerBoxObject(nsIBoxObject** aResult) +{ + NS_ADDREF(*aResult = new mozilla::dom::ContainerBoxObject()); + return NS_OK; +} diff --git a/layout/xul/ContainerBoxObject.h b/layout/xul/ContainerBoxObject.h new file mode 100644 index 000000000..b3da97991 --- /dev/null +++ b/layout/xul/ContainerBoxObject.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 mozilla_dom_ContainerBoxObject_h +#define mozilla_dom_ContainerBoxObject_h + +#include "mozilla/dom/BoxObject.h" + +namespace mozilla { +namespace dom { + +class ContainerBoxObject final : public BoxObject +{ +public: + ContainerBoxObject(); + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + already_AddRefed<nsIDocShell> GetDocShell(); + +private: + ~ContainerBoxObject(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ContainerBoxObject_h diff --git a/layout/xul/ListBoxObject.cpp b/layout/xul/ListBoxObject.cpp new file mode 100644 index 000000000..435065f5b --- /dev/null +++ b/layout/xul/ListBoxObject.cpp @@ -0,0 +1,238 @@ +/* -*- 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/dom/ListBoxObject.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsGkAtoms.h" +#include "nsIScrollableFrame.h" +#include "nsListBoxBodyFrame.h" +#include "ChildIterator.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ListBoxObjectBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS_INHERITED(ListBoxObject, BoxObject, nsIListBoxObject, + nsPIListBoxObject) + +ListBoxObject::ListBoxObject() + : mListBoxBody(nullptr) +{ +} + +ListBoxObject::~ListBoxObject() +{ +} + +JSObject* ListBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return ListBoxObjectBinding::Wrap(aCx, this, aGivenProto); +} + +// nsIListBoxObject +NS_IMETHODIMP +ListBoxObject::GetRowCount(int32_t *aResult) +{ + *aResult = GetRowCount(); + return NS_OK; +} + +NS_IMETHODIMP +ListBoxObject::GetItemAtIndex(int32_t index, nsIDOMElement **_retval) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) { + return body->GetItemAtIndex(index, _retval); + } + return NS_OK; + } + +NS_IMETHODIMP +ListBoxObject::GetIndexOfItem(nsIDOMElement* aElement, int32_t *aResult) +{ + *aResult = 0; + + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) { + return body->GetIndexOfItem(aElement, aResult); + } + return NS_OK; +} + +// ListBoxObject + +int32_t +ListBoxObject::GetRowCount() +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) { + return body->GetRowCount(); + } + return 0; +} + +int32_t +ListBoxObject::GetNumberOfVisibleRows() +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) { + return body->GetNumberOfVisibleRows(); + } + return 0; +} + +int32_t +ListBoxObject::GetIndexOfFirstVisibleRow() +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) { + return body->GetIndexOfFirstVisibleRow(); + } + return 0; +} + +void +ListBoxObject::EnsureIndexIsVisible(int32_t aRowIndex) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) { + body->EnsureIndexIsVisible(aRowIndex); + } +} + +void +ListBoxObject::ScrollToIndex(int32_t aRowIndex) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) { + body->ScrollToIndex(aRowIndex); + } +} + +void +ListBoxObject::ScrollByLines(int32_t aNumLines) +{ + nsListBoxBodyFrame* body = GetListBoxBody(true); + if (body) { + body->ScrollByLines(aNumLines); + } +} + +already_AddRefed<Element> +ListBoxObject::GetItemAtIndex(int32_t index) +{ + nsCOMPtr<nsIDOMElement> el; + GetItemAtIndex(index, getter_AddRefs(el)); + nsCOMPtr<Element> ret(do_QueryInterface(el)); + return ret.forget(); +} + +int32_t +ListBoxObject::GetIndexOfItem(Element& aElement) +{ + int32_t ret; + nsCOMPtr<nsIDOMElement> el(do_QueryInterface(&aElement)); + GetIndexOfItem(el, &ret); + return ret; +} + +////////////////////// + +static nsIContent* +FindBodyContent(nsIContent* aParent) +{ + if (aParent->IsXULElement(nsGkAtoms::listboxbody)) { + return aParent; + } + + mozilla::dom::FlattenedChildIterator iter(aParent); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + nsIContent* result = FindBodyContent(child); + if (result) { + return result; + } + } + + return nullptr; +} + +nsListBoxBodyFrame* +ListBoxObject::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()); + + 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 +ListBoxObject::Clear() +{ + ClearCachedValues(); + BoxObject::Clear(); +} + +void +ListBoxObject::ClearCachedValues() +{ + mListBoxBody = nullptr; +} + +} // namespace dom +} // namespace mozilla + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewListBoxObject(nsIBoxObject** aResult) +{ + NS_ADDREF(*aResult = new mozilla::dom::ListBoxObject()); + return NS_OK; +} diff --git a/layout/xul/ListBoxObject.h b/layout/xul/ListBoxObject.h new file mode 100644 index 000000000..39cdf5c8c --- /dev/null +++ b/layout/xul/ListBoxObject.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 mozilla_dom_ListBoxObject_h +#define mozilla_dom_ListBoxObject_h + +#include "mozilla/dom/BoxObject.h" +#include "nsPIListBoxObject.h" + +namespace mozilla { +namespace dom { + +class ListBoxObject final : public BoxObject, + public nsPIListBoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSILISTBOXOBJECT + + ListBoxObject(); + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + // nsPIListBoxObject + virtual nsListBoxBodyFrame* GetListBoxBody(bool aFlush) override; + + // nsPIBoxObject + virtual void Clear() override; + virtual void ClearCachedValues() override; + + // ListBoxObject.webidl + int32_t GetRowCount(); + int32_t GetNumberOfVisibleRows(); + int32_t GetIndexOfFirstVisibleRow(); + void EnsureIndexIsVisible(int32_t rowIndex); + void ScrollToIndex(int32_t rowIndex); + void ScrollByLines(int32_t numLines); + already_AddRefed<Element> GetItemAtIndex(int32_t index); + int32_t GetIndexOfItem(Element& item); + +protected: + nsListBoxBodyFrame *mListBoxBody; + +private: + ~ListBoxObject(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ListBoxObject_h diff --git a/layout/xul/MenuBoxObject.cpp b/layout/xul/MenuBoxObject.cpp new file mode 100644 index 000000000..ab105fd17 --- /dev/null +++ b/layout/xul/MenuBoxObject.cpp @@ -0,0 +1,151 @@ +/* -*- 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/dom/MenuBoxObject.h" +#include "mozilla/dom/MenuBoxObjectBinding.h" + +#include "mozilla/dom/KeyboardEvent.h" +#include "mozilla/dom/Element.h" +#include "nsIDOMKeyEvent.h" +#include "nsIFrame.h" +#include "nsMenuBarFrame.h" +#include "nsMenuBarListener.h" +#include "nsMenuFrame.h" +#include "nsMenuPopupFrame.h" + +namespace mozilla { +namespace dom { + +MenuBoxObject::MenuBoxObject() +{ +} + +MenuBoxObject::~MenuBoxObject() +{ +} + +JSObject* MenuBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return MenuBoxObjectBinding::Wrap(aCx, this, aGivenProto); +} + +void MenuBoxObject::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, false); + } + } + } + } +} + +already_AddRefed<Element> +MenuBoxObject::GetActiveChild() +{ + nsMenuFrame* menu = do_QueryFrame(GetFrame(false)); + if (menu) { + nsCOMPtr<nsIDOMElement> el; + menu->GetActiveChild(getter_AddRefs(el)); + nsCOMPtr<Element> ret(do_QueryInterface(el)); + return ret.forget(); + } + return nullptr; +} + +void MenuBoxObject::SetActiveChild(Element* arg) +{ + nsMenuFrame* menu = do_QueryFrame(GetFrame(false)); + if (menu) { + nsCOMPtr<nsIDOMElement> el(do_QueryInterface(arg)); + menu->SetActiveChild(el); + } +} + +bool MenuBoxObject::HandleKeyPress(KeyboardEvent& keyEvent) +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) { + return false; + } + + // if event has already been handled, bail + bool eventHandled = false; + keyEvent.GetDefaultPrevented(&eventHandled); + if (eventHandled) { + return false; + } + + if (nsMenuBarListener::IsAccessKeyPressed(&keyEvent)) + return false; + + nsMenuFrame* menu = do_QueryFrame(GetFrame(false)); + if (!menu) { + return false; + } + + nsMenuPopupFrame* popupFrame = menu->GetPopup(); + if (!popupFrame) { + return false; + } + + uint32_t keyCode = keyEvent.KeyCode(); + switch (keyCode) { + case nsIDOMKeyEvent::DOM_VK_UP: + case nsIDOMKeyEvent::DOM_VK_DOWN: + case nsIDOMKeyEvent::DOM_VK_HOME: + case nsIDOMKeyEvent::DOM_VK_END: + { + nsNavigationDirection theDirection; + theDirection = NS_DIRECTION_FROM_KEY_CODE(popupFrame, keyCode); + return pm->HandleKeyboardNavigationInPopup(popupFrame, theDirection); + } + default: + return pm->HandleShortcutNavigation(&keyEvent, popupFrame); + } +} + +bool MenuBoxObject::OpenedWithKey() +{ + nsMenuFrame* menuframe = do_QueryFrame(GetFrame(false)); + if (!menuframe) { + return false; + } + + nsIFrame* frame = menuframe->GetParent(); + while (frame) { + nsMenuBarFrame* menubar = do_QueryFrame(frame); + if (menubar) { + return menubar->IsActiveByKeyboard(); + } + frame = frame->GetParent(); + } + return false; +} + +} // namespace dom +} // namespace mozilla + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +using namespace mozilla::dom; + +nsresult +NS_NewMenuBoxObject(nsIBoxObject** aResult) +{ + NS_ADDREF(*aResult = new MenuBoxObject()); + return NS_OK; +} diff --git a/layout/xul/MenuBoxObject.h b/layout/xul/MenuBoxObject.h new file mode 100644 index 000000000..652e3231f --- /dev/null +++ b/layout/xul/MenuBoxObject.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 mozilla_dom_MenuBoxObject_h +#define mozilla_dom_MenuBoxObject_h + +#include "mozilla/dom/BoxObject.h" + +namespace mozilla { +namespace dom { + +class KeyboardEvent; + +class MenuBoxObject final : public BoxObject +{ +public: + + MenuBoxObject(); + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + void OpenMenu(bool aOpenFlag); + already_AddRefed<Element> GetActiveChild(); + void SetActiveChild(Element* arg); + bool HandleKeyPress(KeyboardEvent& keyEvent); + bool OpenedWithKey(); + +private: + ~MenuBoxObject(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_MenuBoxObject_h diff --git a/layout/xul/PopupBoxObject.cpp b/layout/xul/PopupBoxObject.cpp new file mode 100644 index 000000000..00ecc943d --- /dev/null +++ b/layout/xul/PopupBoxObject.cpp @@ -0,0 +1,384 @@ +/* -*- 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 "nsIRootBox.h" +#include "nsIPresShell.h" +#include "nsIContent.h" +#include "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsMenuPopupFrame.h" +#include "nsView.h" +#include "mozilla/AppUnits.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/PopupBoxObject.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/PopupBoxObjectBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ADDREF_INHERITED(PopupBoxObject, BoxObject) +NS_IMPL_RELEASE_INHERITED(PopupBoxObject, BoxObject) +NS_INTERFACE_MAP_BEGIN(PopupBoxObject) +NS_INTERFACE_MAP_END_INHERITING(BoxObject) + +PopupBoxObject::PopupBoxObject() +{ +} + +PopupBoxObject::~PopupBoxObject() +{ +} + +nsIContent* PopupBoxObject::GetParentObject() const +{ + return BoxObject::GetParentObject(); +} + +JSObject* PopupBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return PopupBoxObjectBinding::Wrap(aCx, this, aGivenProto); +} + +nsPopupSetFrame* +PopupBoxObject::GetPopupSetFrame() +{ + nsIRootBox* rootBox = nsIRootBox::GetRootBox(GetPresShell(false)); + if (!rootBox) + return nullptr; + + return rootBox->GetPopupSetFrame(); +} + +void +PopupBoxObject::HidePopup(bool aCancel) +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && mContent) { + pm->HidePopup(mContent, false, true, false, aCancel); + } +} + +void +PopupBoxObject::ShowPopup(Element* aAnchorElement, + Element& aPopupElement, + int32_t aXPos, int32_t aYPos, + const nsAString& aPopupType, + const nsAString& aAnchorAlignment, + const nsAString& aPopupAlignment) +{ + 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")); + } +} + +void +PopupBoxObject::OpenPopup(Element* aAnchorElement, + const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + bool aAttributesOverride, + Event* 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); + } +} + +void +PopupBoxObject::OpenPopupAtScreen(int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + Event* aTriggerEvent) +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && mContent) + pm->ShowPopupAtScreen(mContent, aXPos, aYPos, aIsContextMenu, aTriggerEvent); +} + +void +PopupBoxObject::OpenPopupAtScreenRect(const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + int32_t aWidth, int32_t aHeight, + bool aIsContextMenu, + bool aAttributesOverride, + Event* aTriggerEvent) +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && mContent) { + pm->ShowPopupAtScreenRect(mContent, aPosition, + nsIntRect(aXPos, aYPos, aWidth, aHeight), + aIsContextMenu, aAttributesOverride, aTriggerEvent); + } +} + +void +PopupBoxObject::MoveTo(int32_t aLeft, int32_t aTop) +{ + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (menuPopupFrame) { + menuPopupFrame->MoveTo(CSSIntPoint(aLeft, aTop), true); + } +} + +void +PopupBoxObject::MoveToAnchor(Element* 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->IsVisible()) { + menuPopupFrame->MoveToAnchor(anchorContent, aPosition, aXPos, aYPos, aAttributesOverride); + } + } +} + +void +PopupBoxObject::SizeTo(int32_t aWidth, int32_t aHeight) +{ + if (!mContent) + return; + + 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); +} + +bool +PopupBoxObject::AutoPosition() +{ + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (menuPopupFrame) { + return menuPopupFrame->GetAutoPosition(); + } + return true; +} + +void +PopupBoxObject::SetAutoPosition(bool aShouldAutoPosition) +{ + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (menuPopupFrame) { + menuPopupFrame->SetAutoPosition(aShouldAutoPosition); + } +} + +void +PopupBoxObject::EnableRollup(bool aShouldRollup) +{ + // this does nothing now +} + +void +PopupBoxObject::SetConsumeRollupEvent(uint32_t aConsume) +{ + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false)); + if (menuPopupFrame) { + menuPopupFrame->SetConsumeRollupEvent(aConsume); + } +} + +void +PopupBoxObject::EnableKeyboardNavigator(bool aEnableKeyboardNavigator) +{ + if (!mContent) + return; + + // 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); +} + +void +PopupBoxObject::GetPopupState(nsString& 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 ePopupShown: + aState.AssignLiteral("open"); + break; + case ePopupShowing: + case ePopupPositioning: + case ePopupOpening: + case ePopupVisible: + aState.AssignLiteral("showing"); + break; + case ePopupHiding: + case ePopupInvisible: + aState.AssignLiteral("hiding"); + break; + case ePopupClosed: + break; + default: + NS_NOTREACHED("Bad popup state"); + break; + } + } +} + +nsINode* +PopupBoxObject::GetTriggerNode() const +{ + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + return nsMenuPopupFrame::GetTriggerContent(menuPopupFrame); +} + +Element* +PopupBoxObject::GetAnchorNode() const +{ + nsMenuPopupFrame *menuPopupFrame = mContent ? do_QueryFrame(mContent->GetPrimaryFrame()) : nullptr; + if (!menuPopupFrame) { + return nullptr; + } + + nsIContent* anchor = menuPopupFrame->GetAnchor(); + return anchor && anchor->IsElement() ? anchor->AsElement() : nullptr; +} + +already_AddRefed<DOMRect> +PopupBoxObject::GetOuterScreenRect() +{ + RefPtr<DOMRect> rect = new DOMRect(mContent); + + // Return an empty rectangle if the popup is not open. + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false)); + if (!menuPopupFrame || !menuPopupFrame->IsOpen()) { + return rect.forget(); + } + + nsView* view = menuPopupFrame->GetView(); + if (view) { + nsIWidget* widget = view->GetWidget(); + if (widget) { + LayoutDeviceIntRect screenRect = widget->GetScreenBounds(); + + int32_t pp = menuPopupFrame->PresContext()->AppUnitsPerDevPixel(); + rect->SetLayoutRect(LayoutDeviceIntRect::ToAppUnits(screenRect, pp)); + } + } + return rect.forget(); +} + +void +PopupBoxObject::GetAlignmentPosition(nsString& positionStr) +{ + positionStr.Truncate(); + + // This needs to flush layout. + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(true)); + if (!menuPopupFrame) + return; + + 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; + case POPUPPOSITION_SELECTION: + positionStr.AssignLiteral("selection"); + break; + default: + // Leave as an empty string. + break; + } +} + +int32_t +PopupBoxObject::AlignmentOffset() +{ + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false)); + if (!menuPopupFrame) + return 0; + + int32_t pp = mozilla::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); + return popupOffset.x; +} + +void +PopupBoxObject::SetConstraintRect(dom::DOMRectReadOnly& aRect) +{ + nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(false)); + if (menuPopupFrame) { + menuPopupFrame->SetOverrideConstraintRect( + LayoutDeviceIntRect::Truncate(aRect.Left(), aRect.Top(), aRect.Width(), aRect.Height())); + } +} + +} // namespace dom +} // namespace mozilla + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewPopupBoxObject(nsIBoxObject** aResult) +{ + *aResult = new mozilla::dom::PopupBoxObject(); + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/layout/xul/PopupBoxObject.h b/layout/xul/PopupBoxObject.h new file mode 100644 index 000000000..a660393b5 --- /dev/null +++ b/layout/xul/PopupBoxObject.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 mozilla_dom_PopupBoxObject_h +#define mozilla_dom_PopupBoxObject_h + +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/BoxObject.h" +#include "nsString.h" + +struct JSContext; +class nsPopupSetFrame; + +namespace mozilla { +namespace dom { + +class DOMRect; +class Element; +class Event; + +class PopupBoxObject final : public BoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + // also in PopupBoxObject.webidl + static const uint32_t ROLLUP_DEFAULT = 0; /* widget/platform default */ + static const uint32_t ROLLUP_CONSUME = 1; /* consume the rollup event */ + static const uint32_t ROLLUP_NO_CONSUME = 2; /* don't consume the rollup event */ + + PopupBoxObject(); + + nsIContent* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + void ShowPopup(Element* aAnchorElement, + Element& aPopupElement, + int32_t aXPos, + int32_t aYPos, + const nsAString& aPopupType, + const nsAString& aAnchorAlignment, + const nsAString& aPopupAlignment); + + void HidePopup(bool aCancel); + + bool AutoPosition(); + + void SetAutoPosition(bool aShouldAutoPosition); + + void EnableKeyboardNavigator(bool aEnableKeyboardNavigator); + + void EnableRollup(bool aShouldRollup); + + void SetConsumeRollupEvent(uint32_t aConsume); + + void SizeTo(int32_t aWidth, int32_t aHeight); + + void MoveTo(int32_t aLeft, int32_t aTop); + + void OpenPopup(Element* aAnchorElement, + const nsAString& aPosition, + int32_t aXPos, + int32_t aYPos, + bool aIsContextMenu, bool aAttributesOverride, + Event* aTriggerEvent); + + void OpenPopupAtScreen(int32_t aXPos, + int32_t aYPos, + bool aIsContextMenu, + Event* aTriggerEvent); + + void OpenPopupAtScreenRect(const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + int32_t aWidth, int32_t aHeight, + bool aIsContextMenu, + bool aAttributesOverride, + Event* aTriggerEvent); + + void GetPopupState(nsString& aState); + + nsINode* GetTriggerNode() const; + + Element* GetAnchorNode() const; + + already_AddRefed<DOMRect> GetOuterScreenRect(); + + void MoveToAnchor(Element* aAnchorElement, + const nsAString& aPosition, + int32_t aXPos, + int32_t aYPos, + bool aAttributesOverride); + + void GetAlignmentPosition(nsString& positionStr); + + int32_t AlignmentOffset(); + + void SetConstraintRect(dom::DOMRectReadOnly& aRect); + +private: + ~PopupBoxObject(); + +protected: + nsPopupSetFrame* GetPopupSetFrame(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PopupBoxObject_h diff --git a/layout/xul/ScrollBoxObject.cpp b/layout/xul/ScrollBoxObject.cpp new file mode 100644 index 000000000..26f7bc9bb --- /dev/null +++ b/layout/xul/ScrollBoxObject.cpp @@ -0,0 +1,384 @@ +/* -*- 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/dom/ScrollBoxObject.h" +#include "mozilla/dom/ScrollBoxObjectBinding.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsCOMPtr.h" +#include "nsIPresShell.h" +#include "nsIContent.h" +#include "nsIDOMElement.h" +#include "nsPresContext.h" +#include "nsBox.h" +#include "nsIScrollableFrame.h" + +namespace mozilla { +namespace dom { + +ScrollBoxObject::ScrollBoxObject() +{ +} + +ScrollBoxObject::~ScrollBoxObject() +{ +} + +JSObject* ScrollBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return ScrollBoxObjectBinding::Wrap(aCx, this, aGivenProto); +} + +nsIScrollableFrame* ScrollBoxObject::GetScrollFrame() +{ + return do_QueryFrame(GetFrame(false)); +} + +void ScrollBoxObject::ScrollTo(int32_t x, int32_t y, ErrorResult& aRv) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + sf->ScrollToCSSPixels(CSSIntPoint(x, y)); +} + +void ScrollBoxObject::ScrollBy(int32_t dx, int32_t dy, ErrorResult& aRv) +{ + CSSIntPoint pt; + GetPosition(pt, aRv); + + if (aRv.Failed()) { + return; + } + + ScrollTo(pt.x + dx, pt.y + dy, aRv); +} + +void ScrollBoxObject::ScrollByLine(int32_t dlines, ErrorResult& aRv) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + sf->ScrollBy(nsIntPoint(0, dlines), nsIScrollableFrame::LINES, + nsIScrollableFrame::SMOOTH); +} + +// 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(BoxObject* aScrollBox) { + nsIFrame* frame = aScrollBox->GetFrame(false); + if (!frame) { + return nullptr; + } + + nsIScrollableFrame* scrollFrame = do_QueryFrame(frame); + if (!scrollFrame) { + NS_WARNING("ScrollBoxObject attached to something that's not a scroll frame!"); + return nullptr; + } + + nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); + if (!scrolledFrame) + return nullptr; + return nsBox::GetChildXULBox(scrolledFrame); +} + +void ScrollBoxObject::ScrollByIndex(int32_t dindexes, ErrorResult& aRv) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + nsIFrame* scrolledBox = GetScrolledBox(this); + if (!scrolledBox) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + nsRect rect; + + // now get the scrolled boxes first child. + nsIFrame* child = nsBox::GetChildXULBox(scrolledBox); + + bool horiz = scrolledBox->IsXULHorizontal(); + nsPoint cp = sf->GetScrollPosition(); + nscoord diff = 0; + int32_t curIndex = 0; + bool isLTR = scrolledBox->IsXULNormalDirection(); + + int32_t frameWidth = 0; + if (!isLTR && horiz) { + GetWidth(&frameWidth); + nsCOMPtr<nsIPresShell> shell = GetPresShell(false); + if (!shell) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + 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 = nsBox::GetNextXULBox(child); + curIndex++; + } + + int32_t count = 0; + + if (dindexes == 0) + return; + + if (dindexes > 0) { + while(child) { + child = nsBox::GetNextXULBox(child); + if (child) { + rect = child->GetRect(); + } + count++; + if (count >= dindexes) { + break; + } + } + + } else if (dindexes < 0) { + child = nsBox::GetChildXULBox(scrolledBox); + while(child) { + rect = child->GetRect(); + if (count >= curIndex + dindexes) { + break; + } + + count++; + child = nsBox::GetNextXULBox(child); + + } + } + + 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); + } +} + +void ScrollBoxObject::ScrollToLine(int32_t line, ErrorResult& aRv) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + 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); +} + +void ScrollBoxObject::ScrollToElement(Element& child, ErrorResult& aRv) +{ + nsCOMPtr<nsIPresShell> shell = GetPresShell(false); + if (!shell) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + shell->ScrollContentIntoView(&child, + 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); +} + +void ScrollBoxObject::ScrollToIndex(int32_t index, ErrorResult& aRv) +{ + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); +} + +int32_t ScrollBoxObject::GetPositionX(ErrorResult& aRv) +{ + CSSIntPoint pt; + GetPosition(pt, aRv); + return pt.x; +} + +int32_t ScrollBoxObject::GetPositionY(ErrorResult& aRv) +{ + CSSIntPoint pt; + GetPosition(pt, aRv); + return pt.y; +} + +int32_t ScrollBoxObject::GetScrolledWidth(ErrorResult& aRv) +{ + nsRect scrollRect; + GetScrolledSize(scrollRect, aRv); + return scrollRect.width; +} + +int32_t ScrollBoxObject::GetScrolledHeight(ErrorResult& aRv) +{ + nsRect scrollRect; + GetScrolledSize(scrollRect, aRv); + return scrollRect.height; +} + +/* private helper */ +void ScrollBoxObject::GetPosition(CSSIntPoint& aPos, ErrorResult& aRv) +{ + nsIScrollableFrame* sf = GetScrollFrame(); + if (!sf) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + aPos = sf->GetScrollPositionCSSPixels(); +} + +/* private helper */ +void ScrollBoxObject::GetScrolledSize(nsRect& aRect, ErrorResult& aRv) +{ + nsIFrame* scrolledBox = GetScrolledBox(this); + if (!scrolledBox) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + aRect = scrolledBox->GetRect(); + aRect.width = nsPresContext::AppUnitsToIntCSSPixels(aRect.width); + aRect.height = nsPresContext::AppUnitsToIntCSSPixels(aRect.height); +} + +void ScrollBoxObject::GetPosition(JSContext* cx, + JS::Handle<JSObject*> x, + JS::Handle<JSObject*> y, + ErrorResult& aRv) +{ + CSSIntPoint pt; + GetPosition(pt, aRv); + JS::Rooted<JS::Value> v(cx); + if (!ToJSValue(cx, pt.x, &v) || + !JS_SetProperty(cx, x, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } + if (!ToJSValue(cx, pt.y, &v) || + !JS_SetProperty(cx, y, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } +} + +void ScrollBoxObject::GetScrolledSize(JSContext* cx, + JS::Handle<JSObject*> width, + JS::Handle<JSObject*> height, + ErrorResult& aRv) +{ + nsRect rect; + GetScrolledSize(rect, aRv); + JS::Rooted<JS::Value> v(cx); + if (!ToJSValue(cx, rect.width, &v) || + !JS_SetProperty(cx, width, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } + if (!ToJSValue(cx, rect.height, &v) || + !JS_SetProperty(cx, height, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } +} + +void ScrollBoxObject::EnsureElementIsVisible(Element& child, ErrorResult& aRv) +{ + nsCOMPtr<nsIPresShell> shell = GetPresShell(false); + if (!shell) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + shell->ScrollContentIntoView(&child, + nsIPresShell::ScrollAxis(), + nsIPresShell::ScrollAxis(), + nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY | + nsIPresShell::SCROLL_OVERFLOW_HIDDEN); +} + +void ScrollBoxObject::EnsureIndexIsVisible(int32_t index, ErrorResult& aRv) +{ + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); +} + +void ScrollBoxObject::EnsureLineIsVisible(int32_t line, ErrorResult& aRv) +{ + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); +} + +} // namespace dom +} // namespace mozilla + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +using namespace mozilla::dom; + +nsresult +NS_NewScrollBoxObject(nsIBoxObject** aResult) +{ + NS_ADDREF(*aResult = new ScrollBoxObject()); + return NS_OK; +} diff --git a/layout/xul/ScrollBoxObject.h b/layout/xul/ScrollBoxObject.h new file mode 100644 index 000000000..3344bf3f0 --- /dev/null +++ b/layout/xul/ScrollBoxObject.h @@ -0,0 +1,64 @@ +/* -*- 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 mozilla_dom_ScrollBoxObject_h +#define mozilla_dom_ScrollBoxObject_h + +#include "mozilla/dom/BoxObject.h" +#include "Units.h" + +class nsIScrollableFrame; +struct nsRect; + +namespace mozilla { +namespace dom { + +class ScrollBoxObject final : public BoxObject +{ +public: + ScrollBoxObject(); + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + virtual nsIScrollableFrame* GetScrollFrame(); + + void ScrollTo(int32_t x, int32_t y, ErrorResult& aRv); + void ScrollBy(int32_t dx, int32_t dy, ErrorResult& aRv); + void ScrollByLine(int32_t dlines, ErrorResult& aRv); + void ScrollByIndex(int32_t dindexes, ErrorResult& aRv); + void ScrollToLine(int32_t line, ErrorResult& aRv); + void ScrollToElement(Element& child, ErrorResult& aRv); + void ScrollToIndex(int32_t index, ErrorResult& aRv); + int32_t GetPositionX(ErrorResult& aRv); + int32_t GetPositionY(ErrorResult& aRv); + int32_t GetScrolledWidth(ErrorResult& aRv); + int32_t GetScrolledHeight(ErrorResult& aRv); + void EnsureElementIsVisible(Element& child, ErrorResult& aRv); + void EnsureIndexIsVisible(int32_t index, ErrorResult& aRv); + void EnsureLineIsVisible(int32_t line, ErrorResult& aRv); + + // Deprecated APIs from old IDL + void GetPosition(JSContext* cx, + JS::Handle<JSObject*> x, + JS::Handle<JSObject*> y, + ErrorResult& aRv); + + void GetScrolledSize(JSContext* cx, + JS::Handle<JSObject*> width, + JS::Handle<JSObject*> height, + ErrorResult& aRv); + +protected: + void GetScrolledSize(nsRect& aRect, ErrorResult& aRv); + void GetPosition(CSSIntPoint& aPos, ErrorResult& aRv); + +private: + ~ScrollBoxObject(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ScrollBoxObject_h diff --git a/layout/xul/crashtests/131008-1.xul b/layout/xul/crashtests/131008-1.xul new file mode 100644 index 000000000..d505f8696 --- /dev/null +++ b/layout/xul/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/crashtests/137216-1.xul b/layout/xul/crashtests/137216-1.xul new file mode 100644 index 000000000..a3fc043c8 --- /dev/null +++ b/layout/xul/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/crashtests/140218-1.xml b/layout/xul/crashtests/140218-1.xml new file mode 100644 index 000000000..311afc218 --- /dev/null +++ b/layout/xul/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/crashtests/151826-1.xul b/layout/xul/crashtests/151826-1.xul new file mode 100644 index 000000000..1115dae72 --- /dev/null +++ b/layout/xul/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/crashtests/168724-1.xul b/layout/xul/crashtests/168724-1.xul new file mode 100644 index 000000000..8456406c2 --- /dev/null +++ b/layout/xul/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/crashtests/189814-1.xul b/layout/xul/crashtests/189814-1.xul new file mode 100644 index 000000000..79462348c --- /dev/null +++ b/layout/xul/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/crashtests/237787-1.xul b/layout/xul/crashtests/237787-1.xul new file mode 100644 index 000000000..96cebca9f --- /dev/null +++ b/layout/xul/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/crashtests/265161-1.xul b/layout/xul/crashtests/265161-1.xul new file mode 100644 index 000000000..75a995790 --- /dev/null +++ b/layout/xul/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/crashtests/289410-1.xul b/layout/xul/crashtests/289410-1.xul new file mode 100644 index 000000000..0de792f2c --- /dev/null +++ b/layout/xul/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/crashtests/290743.html b/layout/xul/crashtests/290743.html new file mode 100644 index 000000000..fd273d8bf --- /dev/null +++ b/layout/xul/crashtests/290743.html @@ -0,0 +1,6 @@ +<html>
+<head><title>Testcase bug 290743 - This display:-moz-grid testcase freezes Mozilla</title></head>
+<body style="display:-moz-grid;">
+<input type="radio"><input type="radio">
+</body>
+</html>
\ No newline at end of file diff --git a/layout/xul/crashtests/291702-1.xul b/layout/xul/crashtests/291702-1.xul new file mode 100644 index 000000000..4c052630d --- /dev/null +++ b/layout/xul/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/crashtests/291702-2.xul b/layout/xul/crashtests/291702-2.xul new file mode 100644 index 000000000..53d8a51f7 --- /dev/null +++ b/layout/xul/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/crashtests/291702-3.xul b/layout/xul/crashtests/291702-3.xul new file mode 100644 index 000000000..b34404f36 --- /dev/null +++ b/layout/xul/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/crashtests/294371-1.xul b/layout/xul/crashtests/294371-1.xul new file mode 100644 index 000000000..ca5b54914 --- /dev/null +++ b/layout/xul/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/crashtests/311457-1.html b/layout/xul/crashtests/311457-1.html new file mode 100644 index 000000000..e5b6ecdd6 --- /dev/null +++ b/layout/xul/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/crashtests/321056-1.xhtml b/layout/xul/crashtests/321056-1.xhtml new file mode 100644 index 000000000..a7ba11793 --- /dev/null +++ b/layout/xul/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/crashtests/322786-1.xul b/layout/xul/crashtests/322786-1.xul new file mode 100644 index 000000000..79bb092c4 --- /dev/null +++ b/layout/xul/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/crashtests/325377.xul b/layout/xul/crashtests/325377.xul new file mode 100644 index 000000000..8ea30473d --- /dev/null +++ b/layout/xul/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/crashtests/326834-1-inner.xul b/layout/xul/crashtests/326834-1-inner.xul new file mode 100644 index 000000000..0fbdca7ab --- /dev/null +++ b/layout/xul/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/crashtests/326834-1.html b/layout/xul/crashtests/326834-1.html new file mode 100644 index 000000000..ca531caa6 --- /dev/null +++ b/layout/xul/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/crashtests/326879-1.xul b/layout/xul/crashtests/326879-1.xul new file mode 100644 index 000000000..84d74c30c --- /dev/null +++ b/layout/xul/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/crashtests/327776-1.xul b/layout/xul/crashtests/327776-1.xul new file mode 100644 index 000000000..af889493c --- /dev/null +++ b/layout/xul/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/crashtests/328135-1.xul b/layout/xul/crashtests/328135-1.xul new file mode 100644 index 000000000..77a467909 --- /dev/null +++ b/layout/xul/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/crashtests/329327-1.xul b/layout/xul/crashtests/329327-1.xul new file mode 100644 index 000000000..fcfed07c4 --- /dev/null +++ b/layout/xul/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/crashtests/329407-1.xml b/layout/xul/crashtests/329407-1.xml new file mode 100644 index 000000000..0d41c0185 --- /dev/null +++ b/layout/xul/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/crashtests/329477-1.xhtml b/layout/xul/crashtests/329477-1.xhtml new file mode 100644 index 000000000..fcbd3da87 --- /dev/null +++ b/layout/xul/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/crashtests/336962-1.xul b/layout/xul/crashtests/336962-1.xul new file mode 100644 index 000000000..5ad4ad22b --- /dev/null +++ b/layout/xul/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/crashtests/344228-1.xul b/layout/xul/crashtests/344228-1.xul new file mode 100644 index 000000000..d6015707b --- /dev/null +++ b/layout/xul/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/crashtests/346083-1.xul b/layout/xul/crashtests/346083-1.xul new file mode 100644 index 000000000..e04d610a4 --- /dev/null +++ b/layout/xul/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/crashtests/346281-1.xul b/layout/xul/crashtests/346281-1.xul new file mode 100644 index 000000000..4ef670155 --- /dev/null +++ b/layout/xul/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/crashtests/350460.xul b/layout/xul/crashtests/350460.xul new file mode 100644 index 000000000..b13de6c97 --- /dev/null +++ b/layout/xul/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/crashtests/360642-1.xul b/layout/xul/crashtests/360642-1.xul new file mode 100644 index 000000000..5e37020a5 --- /dev/null +++ b/layout/xul/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/crashtests/365151.xul b/layout/xul/crashtests/365151.xul new file mode 100644 index 000000000..074c8d398 --- /dev/null +++ b/layout/xul/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/crashtests/366112-1.xul b/layout/xul/crashtests/366112-1.xul new file mode 100644 index 000000000..4a03ea2cf --- /dev/null +++ b/layout/xul/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/crashtests/366203-1.xul b/layout/xul/crashtests/366203-1.xul new file mode 100644 index 000000000..3e2b96d30 --- /dev/null +++ b/layout/xul/crashtests/366203-1.xul @@ -0,0 +1,40 @@ +<?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="setTimeout(boom, 500);"> + +<script> +function boom() +{ + tc1 = document.getElementById("tc1"); + tc1.parentNode.removeChild(tc1); +} +</script> + +<tree rows="6"> + <treecols> + <treecol id="firstname" label="First Name" primary="true" flex="3"/> + <treecol id="lastname" label="Last Name" flex="7"/> + </treecols> + + <treechildren id="tc1"> + <treeitem container="true" open="true"> + <treerow> + <treecell label="Foo"/> + </treerow> + </treeitem> + </treechildren> + + <treechildren> + <treeitem container="true" open="true"> + <treerow> + <treecell label="Bar"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + +</window> + diff --git a/layout/xul/crashtests/367185-1.xhtml b/layout/xul/crashtests/367185-1.xhtml new file mode 100644 index 000000000..08fd39fa1 --- /dev/null +++ b/layout/xul/crashtests/367185-1.xhtml @@ -0,0 +1,11 @@ +<?xml version="1.0"?>
+<!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" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<title>Testcase bug - ASSERTION: shouldn't use unconstrained widths anymore with nested marquees</title>
+</head>
+<body>
+<xul:hbox style="margin: 0 100%;"><span><xul:hbox style="margin: 0 100%;"></xul:hbox></span></xul:hbox>
+</body>
+</html>
diff --git a/layout/xul/crashtests/369942-1.xhtml b/layout/xul/crashtests/369942-1.xhtml new file mode 100644 index 000000000..a05705843 --- /dev/null +++ b/layout/xul/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/crashtests/374102-1.xul b/layout/xul/crashtests/374102-1.xul new file mode 100644 index 000000000..7e85f0d21 --- /dev/null +++ b/layout/xul/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/crashtests/376137-1.html b/layout/xul/crashtests/376137-1.html new file mode 100644 index 000000000..23b39d900 --- /dev/null +++ b/layout/xul/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/crashtests/376137-2.html b/layout/xul/crashtests/376137-2.html new file mode 100644 index 000000000..160c61ed3 --- /dev/null +++ b/layout/xul/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/crashtests/377592-1.svg b/layout/xul/crashtests/377592-1.svg new file mode 100644 index 000000000..7371708f2 --- /dev/null +++ b/layout/xul/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/crashtests/378961.html b/layout/xul/crashtests/378961.html new file mode 100644 index 000000000..b4da857ff --- /dev/null +++ b/layout/xul/crashtests/378961.html @@ -0,0 +1,9 @@ +<html>
+<head>
+<title>Testcase bug 378961 - Crash [@ nsSplitterFrameInner::RemoveListener] when dragging splitter and DOMAttrModified event removing window</title>
+</head>
+<body>
+<iframe src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3C%3Fxml-stylesheet%20href%3D%22chrome%3A//global/skin%22%20type%3D%22text/css%22%3F%3E%0A%3Cwindow%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%20orient%3D%22horizontal%22%3E%0A%3Ctextbox/%3E%3Csplitter/%3E%3Cbox/%3E%0A%0A%3Cscript%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%3E%0Afunction%20doe%28%29%20%7B%0Awindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%0A%7D%0Adocument.addEventListener%28%27DOMAttrModified%27%2C%20doe%2C%20true%29%3B%0A%3C/script%3E%0A%3C/window%3E" style="width: 500px;height:200px;"></iframe>
+
+</body>
+</html>
diff --git a/layout/xul/crashtests/381862.html b/layout/xul/crashtests/381862.html new file mode 100644 index 000000000..e26fa357e --- /dev/null +++ b/layout/xul/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/crashtests/382746-1.xul b/layout/xul/crashtests/382746-1.xul new file mode 100644 index 000000000..9bb14f24f --- /dev/null +++ b/layout/xul/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/crashtests/382899-1.xul b/layout/xul/crashtests/382899-1.xul new file mode 100644 index 000000000..7dab931f7 --- /dev/null +++ b/layout/xul/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/crashtests/383236-1.xul b/layout/xul/crashtests/383236-1.xul new file mode 100644 index 000000000..244df65f1 --- /dev/null +++ b/layout/xul/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/crashtests/384037-1.xhtml b/layout/xul/crashtests/384037-1.xhtml new file mode 100644 index 000000000..04bac671c --- /dev/null +++ b/layout/xul/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/crashtests/384105-1-inner.xul b/layout/xul/crashtests/384105-1-inner.xul new file mode 100644 index 000000000..4ea6e0391 --- /dev/null +++ b/layout/xul/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/crashtests/384105-1.html b/layout/xul/crashtests/384105-1.html new file mode 100644 index 000000000..fe468a906 --- /dev/null +++ b/layout/xul/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/crashtests/384373-1.xul b/layout/xul/crashtests/384373-1.xul new file mode 100644 index 000000000..603b53cde --- /dev/null +++ b/layout/xul/crashtests/384373-1.xul @@ -0,0 +1,10 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+onerror="var x=document.getElementsByTagName('*');x[Math.floor(Math.random()*x.length)].focus()"
+onblur="event.originalTarget.parentNode.parentNode.removeChild(event.originalTarget.parentNode)">
+<script xmlns="http://www.w3.org/1999/xhtml">setTimeout(function() {window.location.reload()}, 200);</script>
+
+<broadcasterset style="display: block;">
+ <broadcaster style="display: block;"></broadcaster>
+</broadcasterset>
+<preferences></preferences>
+</window>
\ No newline at end of file diff --git a/layout/xul/crashtests/384373-2.xul b/layout/xul/crashtests/384373-2.xul new file mode 100644 index 000000000..1d56394e3 --- /dev/null +++ b/layout/xul/crashtests/384373-2.xul @@ -0,0 +1,4 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onerror="document.getElementsByTagName('*')[1].focus()" onfocus="event.target.parentNode.removeChild(event.target)">
+<broadcaster style="display: block;"/>
+<preferences/>
+</window>
\ No newline at end of file diff --git a/layout/xul/crashtests/384373.html b/layout/xul/crashtests/384373.html new file mode 100644 index 000000000..c3bc92f16 --- /dev/null +++ b/layout/xul/crashtests/384373.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"><head> + <meta charset="utf-8"> + <title>Testcase for bug 384373</title> +<script> +function reload() { + this.location.reload(); +} +// Run the test for 1 second +setTimeout(function() { + document.body.getBoundingClientRect(); + document.documentElement.removeChild(document.body); + document.documentElement.className = ""; + }, 2000); +</script> +</head> +<body onload="document.body.getBoundingClientRect()"> + +<iframe src="384373-1.xul"></iframe> +<iframe onload="this.contentWindow.setTimeout(reload,500)" src="384373-2.xul"></iframe> + +</body> +</html> diff --git a/layout/xul/crashtests/384491-1.xhtml b/layout/xul/crashtests/384491-1.xhtml new file mode 100644 index 000000000..2eb065f8d --- /dev/null +++ b/layout/xul/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/crashtests/384871-1-inner.xul b/layout/xul/crashtests/384871-1-inner.xul new file mode 100644 index 000000000..62efdb260 --- /dev/null +++ b/layout/xul/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/crashtests/384871-1.html b/layout/xul/crashtests/384871-1.html new file mode 100644 index 000000000..6bb2a9e07 --- /dev/null +++ b/layout/xul/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/crashtests/386642.xul b/layout/xul/crashtests/386642.xul new file mode 100644 index 000000000..50db21a09 --- /dev/null +++ b/layout/xul/crashtests/386642.xul @@ -0,0 +1,31 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Bug 386642 Crash [@ IsCanvasFrame] while opening context menu or changing styles"> +<toolbarbutton type="menu" id="a"> +<menupopup id="b"/> +</toolbarbutton> + +<style xmlns="http://www.w3.org/1999/xhtml"> +.one image { +display: -moz-box; +} +image{ +display: none; +} + +</style> +<script><![CDATA[ +var gg=0; +function doe() { + var a = document.getElementById('a'); + if (!a.hasAttribute('class')) { + a.setAttribute('class', 'one'); + } else { + a.removeAttribute('class'); + } +document.getElementById('b').hidePopup(); +} + +doe(); +setInterval(doe, 200); +]]></script> +</window> diff --git a/layout/xul/crashtests/387033-1.xhtml b/layout/xul/crashtests/387033-1.xhtml new file mode 100644 index 000000000..58325b1a7 --- /dev/null +++ b/layout/xul/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/crashtests/387080-1.xul b/layout/xul/crashtests/387080-1.xul new file mode 100644 index 000000000..4eb9bd784 --- /dev/null +++ b/layout/xul/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/crashtests/391974-1-inner.xul b/layout/xul/crashtests/391974-1-inner.xul new file mode 100644 index 000000000..f13aa2110 --- /dev/null +++ b/layout/xul/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/crashtests/391974-1.html b/layout/xul/crashtests/391974-1.html new file mode 100644 index 000000000..c72a1a73c --- /dev/null +++ b/layout/xul/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/crashtests/394120-1.xhtml b/layout/xul/crashtests/394120-1.xhtml new file mode 100644 index 000000000..9df447862 --- /dev/null +++ b/layout/xul/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/crashtests/397293.xhtml b/layout/xul/crashtests/397293.xhtml new file mode 100644 index 000000000..cfd181921 --- /dev/null +++ b/layout/xul/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/crashtests/397304-1.html b/layout/xul/crashtests/397304-1.html new file mode 100644 index 000000000..3501f0581 --- /dev/null +++ b/layout/xul/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/crashtests/398326-1.xhtml b/layout/xul/crashtests/398326-1.xhtml new file mode 100644 index 000000000..a265ae4e0 --- /dev/null +++ b/layout/xul/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/crashtests/399013.xul b/layout/xul/crashtests/399013.xul new file mode 100644 index 000000000..a2349aff8 --- /dev/null +++ b/layout/xul/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/crashtests/400779-1.xhtml b/layout/xul/crashtests/400779-1.xhtml new file mode 100644 index 000000000..c0f5d493c --- /dev/null +++ b/layout/xul/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/crashtests/402912-1.xhtml b/layout/xul/crashtests/402912-1.xhtml new file mode 100644 index 000000000..b2cb98dc5 --- /dev/null +++ b/layout/xul/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/crashtests/404192.xhtml b/layout/xul/crashtests/404192.xhtml new file mode 100644 index 000000000..4ad5af348 --- /dev/null +++ b/layout/xul/crashtests/404192.xhtml @@ -0,0 +1,12 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+<xul:titlebar id="a" style="overflow: auto;"/>
+
+<script>
+function doe() {
+document.getElementsByTagName('*')[1].focus();
+document.getElementsByTagName('*')[0].focus();
+document.documentElement.removeAttribute("class");
+}
+setTimeout(doe, 200);
+</script>
+</html>
diff --git a/layout/xul/crashtests/407152.xul b/layout/xul/crashtests/407152.xul new file mode 100644 index 000000000..0a5adf69e --- /dev/null +++ b/layout/xul/crashtests/407152.xul @@ -0,0 +1,7 @@ +<triple xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="display: block;"> +<box style="display: block; position: relative; -moz-binding: url(data:text/xml;charset=utf-8,%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%3Cbinding%20id%3D%22a%22%3E%0A%3Cimplementation%3E%0A%3Cfield%20name%3D%22_field%22%3Ethis.ownerDocument.getBoxObjectFor%28this.ownerDocument.documentElement%29%3B%3C/field%3E%0A%3C/implementation%3E%0A%3Ccontent%3E%0A%3C/binding%3E%0A%3C/bindings%3E);"> +<box style="position: fixed; -moz-binding: url(data:text/xml;charset=utf-8,%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%3Cbinding%20id%3D%22a%22%3E%0A%3Ccontent%3E%0A%3Cchildren%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22/%3E%3C/content%3E%3C/binding%3E%3C/bindings%3E);"/> +<iframe/> +</box> +<scrollbox onoverflow="document.documentElement.removeAttribute('style')"/> +</triple>
\ No newline at end of file diff --git a/layout/xul/crashtests/408904-1.xul b/layout/xul/crashtests/408904-1.xul new file mode 100644 index 000000000..59f215c73 --- /dev/null +++ b/layout/xul/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/crashtests/412479-1.xhtml b/layout/xul/crashtests/412479-1.xhtml new file mode 100644 index 000000000..b1086a816 --- /dev/null +++ b/layout/xul/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/crashtests/415394-1.xhtml b/layout/xul/crashtests/415394-1.xhtml new file mode 100644 index 000000000..7dc24dc9f --- /dev/null +++ b/layout/xul/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/crashtests/417509.xul b/layout/xul/crashtests/417509.xul new file mode 100644 index 000000000..81703ada3 --- /dev/null +++ b/layout/xul/crashtests/417509.xul @@ -0,0 +1,7 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<span id="a" datasources="" xmlns="http://www.w3.org/1999/xhtml"/> +<script xmlns="http://www.w3.org/1999/xhtml"> +document.documentElement.appendChild(document.getElementById('a')); + +</script> +</window>
\ No newline at end of file diff --git a/layout/xul/crashtests/420424-1.xul b/layout/xul/crashtests/420424-1.xul new file mode 100644 index 000000000..e60841706 --- /dev/null +++ b/layout/xul/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/crashtests/430356-1.xhtml b/layout/xul/crashtests/430356-1.xhtml new file mode 100644 index 000000000..6e5717ae9 --- /dev/null +++ b/layout/xul/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/crashtests/431738.xhtml b/layout/xul/crashtests/431738.xhtml new file mode 100644 index 000000000..9ce917a3f --- /dev/null +++ b/layout/xul/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/crashtests/432058-1.xul b/layout/xul/crashtests/432058-1.xul new file mode 100644 index 000000000..a7f63adf8 --- /dev/null +++ b/layout/xul/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/crashtests/432068-1.xul b/layout/xul/crashtests/432068-1.xul new file mode 100644 index 000000000..02c3114e6 --- /dev/null +++ b/layout/xul/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/crashtests/432068-2.xul b/layout/xul/crashtests/432068-2.xul new file mode 100644 index 000000000..c75984bae --- /dev/null +++ b/layout/xul/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/crashtests/433296-1.xul b/layout/xul/crashtests/433296-1.xul new file mode 100644 index 000000000..10fd0ec7b --- /dev/null +++ b/layout/xul/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/crashtests/433429.xul b/layout/xul/crashtests/433429.xul new file mode 100644 index 000000000..a52bce68f --- /dev/null +++ b/layout/xul/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/crashtests/434458-1.xul b/layout/xul/crashtests/434458-1.xul new file mode 100644 index 000000000..fbec2a413 --- /dev/null +++ b/layout/xul/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/crashtests/452185.html b/layout/xul/crashtests/452185.html new file mode 100644 index 000000000..d4981ffdf --- /dev/null +++ b/layout/xul/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/crashtests/452185.xml b/layout/xul/crashtests/452185.xml new file mode 100644 index 000000000..655c43a8d --- /dev/null +++ b/layout/xul/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/crashtests/460900-1.xul b/layout/xul/crashtests/460900-1.xul new file mode 100644 index 000000000..bff7c5c36 --- /dev/null +++ b/layout/xul/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/crashtests/464149-1.xul b/layout/xul/crashtests/464149-1.xul new file mode 100644 index 000000000..556656f02 --- /dev/null +++ b/layout/xul/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/crashtests/464407-1.xhtml b/layout/xul/crashtests/464407-1.xhtml new file mode 100644 index 000000000..83666a6a4 --- /dev/null +++ b/layout/xul/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/crashtests/467080.xul b/layout/xul/crashtests/467080.xul new file mode 100644 index 000000000..bc579b0ee --- /dev/null +++ b/layout/xul/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/crashtests/467481-1.xul b/layout/xul/crashtests/467481-1.xul new file mode 100644 index 000000000..56fbd4441 --- /dev/null +++ b/layout/xul/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/crashtests/470063-1.html b/layout/xul/crashtests/470063-1.html new file mode 100644 index 000000000..11c01b30e --- /dev/null +++ b/layout/xul/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/crashtests/470272.html b/layout/xul/crashtests/470272.html new file mode 100644 index 000000000..f23de269b --- /dev/null +++ b/layout/xul/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/crashtests/472189.xul b/layout/xul/crashtests/472189.xul new file mode 100644 index 000000000..e276d8fc7 --- /dev/null +++ b/layout/xul/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/crashtests/475133.html b/layout/xul/crashtests/475133.html new file mode 100644 index 000000000..ea4ee6325 --- /dev/null +++ b/layout/xul/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/crashtests/488210-1.xhtml b/layout/xul/crashtests/488210-1.xhtml new file mode 100644 index 000000000..9c8e2640c --- /dev/null +++ b/layout/xul/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/crashtests/495728-1.xul b/layout/xul/crashtests/495728-1.xul new file mode 100644 index 000000000..ee8498d05 --- /dev/null +++ b/layout/xul/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/crashtests/508927-1.xul b/layout/xul/crashtests/508927-1.xul new file mode 100644 index 000000000..98faff4a6 --- /dev/null +++ b/layout/xul/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/crashtests/508927-2.xul b/layout/xul/crashtests/508927-2.xul new file mode 100644 index 000000000..5bf4f9a0c --- /dev/null +++ b/layout/xul/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/crashtests/514300-1.xul b/layout/xul/crashtests/514300-1.xul new file mode 100644 index 000000000..d0d655011 --- /dev/null +++ b/layout/xul/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/crashtests/536931-1.xhtml b/layout/xul/crashtests/536931-1.xhtml new file mode 100644 index 000000000..6f3fc1396 --- /dev/null +++ b/layout/xul/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/crashtests/538308-1.xul b/layout/xul/crashtests/538308-1.xul new file mode 100644 index 000000000..a96f3fa4e --- /dev/null +++ b/layout/xul/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/crashtests/557174-1.xml b/layout/xul/crashtests/557174-1.xml new file mode 100644 index 000000000..02850a2db --- /dev/null +++ b/layout/xul/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/crashtests/564705-1.xul b/layout/xul/crashtests/564705-1.xul new file mode 100644 index 000000000..b0f29bef7 --- /dev/null +++ b/layout/xul/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/crashtests/583957-1.html b/layout/xul/crashtests/583957-1.html new file mode 100644 index 000000000..85b51bf0c --- /dev/null +++ b/layout/xul/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/crashtests/617089.html b/layout/xul/crashtests/617089.html new file mode 100644 index 000000000..22e5f6d53 --- /dev/null +++ b/layout/xul/crashtests/617089.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <body> + <div style="display: -moz-inline-box;"> + <table style="height: 101%;"><tbody><tr><td><div></div></td></tr></tbody></table> + <table style="height: 101%;"><tbody><tr><td><div></div></td></tr></tbody></table> + </div> + </body> +</html> diff --git a/layout/xul/crashtests/716503.html b/layout/xul/crashtests/716503.html new file mode 100644 index 000000000..250ad2ba4 --- /dev/null +++ b/layout/xul/crashtests/716503.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<style> +div::before { + content: "j"; + display:-moz-inline-box; +} +</style> +<body><div></div></body> +</html> diff --git a/layout/xul/crashtests/crashtests.list b/layout/xul/crashtests/crashtests.list new file mode 100644 index 000000000..4ee6654c6 --- /dev/null +++ b/layout/xul/crashtests/crashtests.list @@ -0,0 +1,99 @@ +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 290743.html +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 322786-1.xul +load 325377.xul +load 326834-1.html +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 +asserts(0-50) load 366203-1.xul # bug 1217984 +asserts(24) asserts-if(Android&&!asyncPan,9) load 367185-1.xhtml # bug 1220345 +load 369942-1.xhtml +load 374102-1.xul +load 376137-1.html +load 376137-2.html +load 377592-1.svg +load 378961.html +load 381862.html +load 382746-1.xul +load 382899-1.xul +load 383236-1.xul +load 384037-1.xhtml +load 384105-1.html +load 384373.html +load 384491-1.xhtml +load 384871-1.html +load 386642.xul +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 404192.xhtml +load 407152.xul +load 408904-1.xul +load 412479-1.xhtml +asserts(4) asserts-if(gtkWidget&&browserIsRemote,6) load 415394-1.xhtml # Bug 163838, bug 1195474 +load 417509.xul +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) asserts-if(Android,0-1) load 464407-1.xhtml # Bug 450974 on win, Bug 1267054 on Android +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 564705-1.xul +load 583957-1.html +load 617089.html +load menulist-focused.xhtml +load 716503.html diff --git a/layout/xul/crashtests/menulist-focused.xhtml b/layout/xul/crashtests/menulist-focused.xhtml new file mode 100644 index 000000000..7a09a838d --- /dev/null +++ b/layout/xul/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/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..074985aaf --- /dev/null +++ b/layout/xul/grid/moz.build @@ -0,0 +1,43 @@ +# -*- Mode: python; 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/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: XUL') + +EXPORTS += [ + 'nsGrid.h', + 'nsGridCell.h', + 'nsGridLayout2.h', + 'nsGridRow.h', + 'nsGridRowGroupLayout.h', + 'nsGridRowLayout.h', + 'nsGridRowLeafFrame.h', + 'nsGridRowLeafLayout.h', + 'nsIGridPart.h', +] + +UNIFIED_SOURCES += [ + 'nsGrid.cpp', + 'nsGridCell.cpp', + 'nsGridLayout2.cpp', + 'nsGridRow.cpp', + 'nsGridRowGroupFrame.cpp', + 'nsGridRowGroupLayout.cpp', + 'nsGridRowLayout.cpp', + 'nsGridRowLeafFrame.cpp', + 'nsGridRowLeafLayout.cpp', +] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '..', + '../../forms', + '../../generic', + '../../style', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/layout/xul/grid/nsGrid.cpp b/layout/xul/grid/nsGrid.cpp new file mode 100644 index 000000000..762bbfcd7 --- /dev/null +++ b/layout/xul/grid/nsGrid.cpp @@ -0,0 +1,1276 @@ +/* -*- 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 "mozilla/ReflowInput.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. +*/ + +using namespace mozilla; + +nsGrid::nsGrid():mBox(nullptr), + mRowsBox(nullptr), + mColumnsBox(nullptr), + mNeedsRebuild(true), + mRowCount(0), + mColumnCount(0), + mExtraRowCount(0), + mExtraColumnCount(0), + 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 + mRows = BuildRows(mRowsBox, rowCount, true); + mColumns = BuildRows(mColumnsBox, columnCount, 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.get(), mColumns.get(), mRowCount, mColumnCount, true); + PopulateCellMap(mColumns.get(), mRows.get(), mColumnCount, mRowCount, false); +} + +void +nsGrid::FreeMap() +{ + 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 = nsBox::GetChildXULBox(mBox); + + 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::IsXULHorizontal(child); + if (isHorizontal) + *aRows = child; + else + *aColumns = child; + + if (*aRows && *aColumns) + return; + } + } + + if (scrollFrame) { + child = oldBox; + } + + child = nsBox::GetNextXULBox(child); + } +} + +/** + * 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. + */ +UniquePtr<nsGridRow[]> +nsGrid::BuildRows(nsIFrame* aBox, int32_t aRowCount, bool aIsHorizontal) +{ + // if no rows then return null + if (aRowCount == 0) { + return nullptr; + } + + // create the array + UniquePtr<nsGridRow[]> row; + + // only create new rows if we have to. Reuse old rows. + if (aIsHorizontal) + { + if (aRowCount > mRowCount) { + row = MakeUnique<nsGridRow[]>(aRowCount); + } else { + for (int32_t i=0; i < mRowCount; i++) + mRows[i].Init(nullptr, false); + + row = Move(mRows); + } + } else { + if (aRowCount > mColumnCount) { + row = MakeUnique<nsGridRow[]>(aRowCount); + } else { + for (int32_t i=0; i < mColumnCount; i++) + mColumns[i].Init(nullptr, false); + + row = Move(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.get()); + } + } + + return row; +} + + +/** + * Given the number of rows and columns. Build a cellmap + */ +UniquePtr<nsGridCell[]> +nsGrid::BuildCellMap(int32_t aRows, int32_t aColumns) +{ + int32_t size = aRows*aColumns; + int32_t oldsize = mRowCount*mColumnCount; + if (size == 0) { + return nullptr; + } + + if (size > oldsize) { + return MakeUnique<nsGridCell[]>(size); + } + + // clear out cellmap + for (int32_t i=0; i < oldsize; i++) { + mCellMap[i].SetBoxInRow(nullptr); + mCellMap[i].SetBoxInColumn(nullptr); + } + return Move(mCellMap); +} + +/** + * 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 = nsBox::GetChildXULBox(child); + + 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 = nsBox::GetNextXULBox(child); + + 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->GetXULLayoutManager(); + 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(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->IsXULCollapsed()) { + aFirstIndex = i; + aFirstRow = row; + break; + } + } + + // see if we are last + for (i=count-1; i >= 0; i--) + { + nsGridRow* row = GetRowAt(i,aIsHorizontal); + if (!row->IsXULCollapsed()) { + 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(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->IsXULCollapsed()) + { + // get real border and padding. GetXULBorderAndPadding + // is redefined on nsGridRowLeafFrame. If we called it here + // we would be in finite recurson. + box->GetXULBorder(border); + box->GetXULPadding(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(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->IsXULCollapsed()) + { + // 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. GetXULBorderAndPadding + // is redefined on nsGridRowLeafFrame. If we called it here + // we would be in finite recurson. + box->GetXULBorder(border); + box->GetXULPadding(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->IsXULCollapsed()) + 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::AddXULPrefSize(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(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->GetXULPrefSize(aState); + nsBox::AddMargin(box, size); + nsGridLayout2::AddOffset(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->IsXULCollapsed()) + { + nsSize childSize = child->GetXULPrefSize(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->IsXULCollapsed()) + 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::AddXULMinSize(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(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->GetXULPrefSize(aState); + nsBox::AddMargin(box, size); + nsGridLayout2::AddOffset(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->IsXULCollapsed()) + { + nsSize childSize = child->GetXULMinSize(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->IsXULCollapsed()) + 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::AddXULMaxSize(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(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->GetXULPrefSize(aState); + nsBox::AddMargin(box, size); + nsGridLayout2::AddOffset(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->IsXULCollapsed()) + { + nsSize min = child->GetXULMinSize(aState); + nsSize childSize = nsBox::BoundsCheckMinMax(min, child->GetXULMaxSize(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(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 = nsBox::GetParentXULBox(box); + nsIFrame* parentsParent=nullptr; + + while(parent) + { + parent = GetScrollBox(parent); + parentsParent = nsBox::GetParentXULBox(parent); + + // 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->GetXULFlex(); + nsIFrame::AddXULFlex(parent, flex); + if (flex == 0) { + row->mFlex = 0; + return row->mFlex; + } + } else + break; + } + + parent = parentsParent; + } + + // get the row flex. + row->mFlex = box->GetXULFlex(); + nsIFrame::AddXULFlex(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 = nsBox::GetParentXULBox(aChild); + + // 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 = nsBox::GetParentXULBox(parent); + } + + 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..d8726a946 --- /dev/null +++ b/layout/xul/grid/nsGrid.h @@ -0,0 +1,132 @@ +/* -*- 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" +#include "mozilla/UniquePtr.h" + +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(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(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(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); + mozilla::UniquePtr<nsGridRow[]> BuildRows(nsIFrame* aBox, int32_t aSize, + bool aIsHorizontal = true); + mozilla::UniquePtr<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 + mozilla::UniquePtr<nsGridRow[]> mRows; + + // an array of columns objects. + mozilla::UniquePtr<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 + mozilla::UniquePtr<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..c54256297 --- /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::GetXULPrefSize(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->GetXULPrefSize(aState); + + nsBox::AddMargin(mBoxInColumn, pref); + nsGridLayout2::AddOffset(mBoxInColumn, pref); + + nsBoxLayout::AddLargestSize(sum, pref); + } + + if (mBoxInRow) { + nsSize pref = mBoxInRow->GetXULPrefSize(aState); + + nsBox::AddMargin(mBoxInRow, pref); + nsGridLayout2::AddOffset(mBoxInRow, pref); + + nsBoxLayout::AddLargestSize(sum, pref); + } + + return sum; +} + +nsSize +nsGridCell::GetXULMinSize(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->GetXULMinSize(aState); + + nsBox::AddMargin(mBoxInColumn, min); + nsGridLayout2::AddOffset(mBoxInColumn, min); + + nsBoxLayout::AddLargestSize(sum, min); + } + + if (mBoxInRow) { + nsSize min = mBoxInRow->GetXULMinSize(aState); + + nsBox::AddMargin(mBoxInRow, min); + nsGridLayout2::AddOffset(mBoxInRow, min); + + nsBoxLayout::AddLargestSize(sum, min); + } + + return sum; +} + +nsSize +nsGridCell::GetXULMaxSize(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->GetXULMaxSize(aState); + + nsBox::AddMargin(mBoxInColumn, max); + nsGridLayout2::AddOffset(mBoxInColumn, max); + + nsBoxLayout::AddSmallestSize(sum, max); + } + + if (mBoxInRow) { + nsSize max = mBoxInRow->GetXULMaxSize(aState); + + nsBox::AddMargin(mBoxInRow, max); + nsGridLayout2::AddOffset(mBoxInRow, max); + + nsBoxLayout::AddSmallestSize(sum, max); + } + + return sum; +} + + +bool +nsGridCell::IsXULCollapsed() +{ + return ((mBoxInColumn && mBoxInColumn->IsXULCollapsed()) || + (mBoxInRow && mBoxInRow->IsXULCollapsed())); +} + + diff --git a/layout/xul/grid/nsGridCell.h b/layout/xul/grid/nsGridCell.h new file mode 100644 index 000000000..eca652776 --- /dev/null +++ b/layout/xul/grid/nsGridCell.h @@ -0,0 +1,53 @@ +/* -*- 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___ + +#include "mozilla/Attributes.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 final +{ +public: + nsGridCell(); + ~nsGridCell(); + + nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState); + nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState); + nsSize GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState); + bool IsXULCollapsed(); + +// 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..75408dce3 --- /dev/null +++ b/layout/xul/grid/nsGridLayout2.cpp @@ -0,0 +1,266 @@ +/* -*- 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 "mozilla/ReflowInput.h" + +nsresult +NS_NewGridLayout2( nsIPresShell* aPresShell, nsBoxLayout** aNewLayout) +{ + *aNewLayout = new nsGridLayout2(aPresShell); + NS_IF_ADDREF(*aNewLayout); + + return NS_OK; + +} + +nsGridLayout2::nsGridLayout2(nsIPresShell* aPresShell):nsStackLayout() +{ +} + +nsGridLayout2::~nsGridLayout2() +{ +} + +// static +void +nsGridLayout2::AddOffset(nsIFrame* aChild, nsSize& aSize) +{ + nsMargin offset; + GetOffset(aChild, offset); + aSize.width += offset.left; + aSize.height += offset.top; +} + +NS_IMETHODIMP +nsGridLayout2::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + // XXX This should be set a better way! + mGrid.SetBox(aBox); + NS_ASSERTION(aBox->GetXULLayoutManager() == this, "setting incorrect box"); + + nsresult rv = nsStackLayout::XULLayout(aBox, aBoxLayoutState); +#ifdef DEBUG_grid + mGrid.PrintCellMap(); +#endif + return rv; +} + +void +nsGridLayout2::IntrinsicISizesDirty(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsStackLayout::IntrinsicISizesDirty(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->GetXULLayoutManager() == 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::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize = nsStackLayout::GetXULMinSize(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(aBox, total); + AddLargestSize(minSize, total); + } + + return minSize; +} + +nsSize +nsGridLayout2::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize pref = nsStackLayout::GetXULPrefSize(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(aBox, total); + AddLargestSize(pref, total); + } + + return pref; +} + +nsSize +nsGridLayout2::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize maxSize = nsStackLayout::GetXULMaxSize(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(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..eb696faf8 --- /dev/null +++ b/layout/xul/grid/nsGridLayout2.h @@ -0,0 +1,78 @@ +/* -*- 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 nsGridRowGroupLayout; +class nsGridRowLayout; +class nsGridRow; +class nsBoxLayoutState; + +/** + * The nsBoxLayout implementation for a grid. + */ +class nsGridLayout2 final : public nsStackLayout, + public nsIGridPart +{ +public: + + friend nsresult NS_NewGridLayout2(nsIPresShell* aPresShell, nsBoxLayout** aNewLayout); + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual void IntrinsicISizesDirty(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + + virtual nsGridRowGroupLayout* CastToRowGroupLayout() override { return nullptr; } + virtual nsGridLayout2* CastToGridLayout() override { return this; } + virtual nsGrid* GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor=nullptr) override; + virtual nsIGridPart* GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) override { + NS_NOTREACHED("Should not be called"); return nullptr; + } + virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) override { aRowCount++; } + virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) override { } + virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) override; + virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) override; + virtual Type GetType() override { return eGrid; } + virtual void ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aPrevBox, + const nsFrameList::Slice& aNewChildren) override; + virtual void ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState, + const nsFrameList::Slice& aNewChildren) override; + virtual void ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aChildList) override; + virtual void ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aChildList) override; + + virtual nsIGridPart* AsGridPart() override { return this; } + + static void AddOffset(nsIFrame* aChild, nsSize& aSize); + +protected: + + explicit nsGridLayout2(nsIPresShell* aShell); + virtual ~nsGridLayout2(); + 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..2a2a64016 --- /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::IsXULCollapsed() +{ + return mBox && mBox->IsXULCollapsed(); +} + diff --git a/layout/xul/grid/nsGridRow.h b/layout/xul/grid/nsGridRow.h new file mode 100644 index 000000000..a5bc39158 --- /dev/null +++ b/layout/xul/grid/nsGridRow.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/. */ + +/** + + Author: + Eric D Vaughan + +**/ + +#ifndef nsGridRow_h___ +#define nsGridRow_h___ + +#include "nsCoord.h" + +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 IsXULCollapsed(); + +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..5b72d3753 --- /dev/null +++ b/layout/xul/grid/nsGridRowGroupFrame.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 "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(); + return new (aPresShell) nsGridRowGroupFrame(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::GetXULFlex() +{ + // 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::GetXULFlex() == 0) + return 0; + + // ok we are flexible add up our children + nscoord totalFlex = 0; + nsIFrame* child = nsBox::GetChildXULBox(this); + while (child) + { + totalFlex += child->GetXULFlex(); + child = GetNextXULBox(child); + } + + mFlex = totalFlex; + + return totalFlex; +} + + diff --git a/layout/xul/grid/nsGridRowGroupFrame.h b/layout/xul/grid/nsGridRowGroupFrame.h new file mode 100644 index 000000000..f367e2121 --- /dev/null +++ b/layout/xul/grid/nsGridRowGroupFrame.h @@ -0,0 +1,49 @@ +/* -*- 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_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("nsGridRowGroup"), aResult); + } +#endif + + nsGridRowGroupFrame(nsStyleContext* aContext, + nsBoxLayout* aLayoutManager): + nsBoxFrame(aContext, false, aLayoutManager) {} + + virtual nscoord GetXULFlex() override; + +}; // class nsGridRowGroupFrame + + + +#endif + diff --git a/layout/xul/grid/nsGridRowGroupLayout.cpp b/layout/xul/grid/nsGridRowGroupLayout.cpp new file mode 100644 index 000000000..1c600cef5 --- /dev/null +++ b/layout/xul/grid/nsGridRowGroupLayout.cpp @@ -0,0 +1,265 @@ +/* -*- 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 "nsBox.h" +#include "nsBoxLayoutState.h" +#include "nsGridLayout2.h" +#include "nsGridRow.h" +#include "mozilla/ReflowInput.h" + +already_AddRefed<nsBoxLayout> NS_NewGridRowGroupLayout() +{ + RefPtr<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 = IsXULHorizontal(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::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize vpref = nsGridRowLayout::GetXULPrefSize(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 = IsXULHorizontal(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::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize maxSize = nsGridRowLayout::GetXULMaxSize(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 = IsXULHorizontal(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::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize = nsGridRowLayout::GetXULMinSize(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 = IsXULHorizontal(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 MarkIntrinsicISizesDirty for every row group. + aState.PresShell()->FrameNeedsReflow(aBox, nsIPresShell::eTreeChange, + NS_FRAME_IS_DIRTY); + nsIFrame* child = nsBox::GetChildXULBox(aBox); + + 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 = nsBox::GetNextXULBox(child); + } + } +} + + +void +nsGridRowGroupLayout::CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) +{ + if (aBox) { + int32_t startCount = aRowCount; + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + + 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 = nsBox::GetNextXULBox(child); + deepChild = child; + continue; + } + + child = nsBox::GetNextXULBox(child); + + // 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 = nsBox::GetChildXULBox(aBox); + + 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 = nsBox::GetNextXULBox(child); + deepChild = child; + continue; + } + + aRows[rowCount].Init(child, true); + + child = nsBox::GetNextXULBox(child); + + // 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->GetXULBorderAndPadding(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..07f2aa1eb --- /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() override { return this; } + virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) override; + virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) override; + virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) override; + virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) override; + virtual int32_t GetRowCount() override { return mRowCount; } + virtual Type GetType() override { return eRowGroup; } + +protected: + nsGridRowGroupLayout(); + virtual ~nsGridRowGroupLayout(); + + virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState) 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..a658e088e --- /dev/null +++ b/layout/xul/grid/nsGridRowLayout.cpp @@ -0,0 +1,197 @@ +/* -*- 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() +{ +} + +nsGridRowLayout::~nsGridRowLayout() +{ +} + +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 = nsBox::GetParentXULBox(aBox); + + 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 = nsBox::GetChildXULBox(aBox); + 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->GetXULLayoutManager(); + nsIGridPart* gridRow = nsGrid::GetPartFromBox(childBox); + if (gridRow) + { + if (layout == aRequestor) { + index = count; + break; + } + count += gridRow->GetRowCount(); + } else + count++; + + child = nsBox::GetNextXULBox(child); + } + + // 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 = nsBox::GetNextXULBox(aBox); + + // get the parent first child to see if we are first + nsIFrame* child = nsBox::GetChildXULBox(parent); + + 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->GetXULMargin(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..22a6f12c9 --- /dev/null +++ b/layout/xul/grid/nsGridRowLayout.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/. */ + +/** + + 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() override { return nullptr; } + virtual nsGridLayout2* CastToGridLayout() override { return nullptr; } + virtual nsGrid* GetGrid(nsIFrame* aBox, int32_t* aIndex, nsGridRowLayout* aRequestor=nullptr) override; + virtual nsIGridPart* GetParentGridPart(nsIFrame* aBox, nsIFrame** aParentBox) override; + virtual void ChildrenInserted(nsIFrame* aBox, nsBoxLayoutState& aState, + nsIFrame* aPrevBox, + const nsFrameList::Slice& aNewChildren) override; + virtual void ChildrenAppended(nsIFrame* aBox, nsBoxLayoutState& aState, + const nsFrameList::Slice& aNewChildren) override; + virtual void ChildrenRemoved(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) override; + virtual void ChildrenSet(nsIFrame* aBox, nsBoxLayoutState& aState, nsIFrame* aChildList) override; + virtual nsMargin GetTotalMargin(nsIFrame* aBox, bool aIsHorizontal) override; + + virtual nsIGridPart* AsGridPart() override { return this; } + +protected: + virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState)=0; + + nsGridRowLayout(); + virtual ~nsGridRowLayout(); +}; + +#endif + diff --git a/layout/xul/grid/nsGridRowLeafFrame.cpp b/layout/xul/grid/nsGridRowLeafFrame.cpp new file mode 100644 index 000000000..e973877a4 --- /dev/null +++ b/layout/xul/grid/nsGridRowLeafFrame.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 "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(); + return new (aPresShell) nsGridRowLeafFrame(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. + */ +nsresult +nsGridRowLeafFrame::GetXULBorderAndPadding(nsMargin& aBorderAndPadding) +{ + // if our columns have made our padding larger add it in. + nsresult rv = nsBoxFrame::GetXULBorderAndPadding(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 = IsXULHorizontal(); + + int32_t firstIndex = 0; + int32_t lastIndex = 0; + nsGridRow* firstRow = nullptr; + nsGridRow* lastRow = nullptr; + grid->GetFirstAndLastRow(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(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(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..dd4ee6835 --- /dev/null +++ b/layout/xul/grid/nsGridRowLeafFrame.h @@ -0,0 +1,54 @@ +/* -*- 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_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("nsGridRowLeaf"), aResult); + } +#endif + + nsGridRowLeafFrame(nsStyleContext* aContext, + bool aIsRoot, + nsBoxLayout* aLayoutManager): + nsBoxFrame(aContext, aIsRoot, aLayoutManager) {} + + virtual nsresult GetXULBorderAndPadding(nsMargin& aBorderAndPadding) override; + +}; // class nsGridRowLeafFrame + + + +#endif + diff --git a/layout/xul/grid/nsGridRowLeafLayout.cpp b/layout/xul/grid/nsGridRowLeafLayout.cpp new file mode 100644 index 000000000..2a089af0c --- /dev/null +++ b/layout/xul/grid/nsGridRowLeafLayout.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/. */ + +// +// 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() +{ + RefPtr<nsBoxLayout> layout = new nsGridRowLeafLayout(); + return layout.forget(); +} + +nsGridRowLeafLayout::nsGridRowLeafLayout():nsGridRowLayout() +{ +} + +nsGridRowLeafLayout::~nsGridRowLeafLayout() +{ +} + +nsSize +nsGridRowLeafLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsXULHorizontal(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::GetXULPrefSize(aBox, aState); + } + else { + return grid->GetPrefRowSize(aState, index, isHorizontal); + //AddBorderAndPadding(aBox, pref); + } +} + +nsSize +nsGridRowLeafLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsXULHorizontal(aBox); + + if (!grid) + return nsGridRowLayout::GetXULMinSize(aBox, aState); + else { + nsSize minSize = grid->GetMinRowSize(aState, index, isHorizontal); + AddBorderAndPadding(aBox, minSize); + return minSize; + } +} + +nsSize +nsGridRowLeafLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + int32_t index = 0; + nsGrid* grid = GetGrid(aBox, &index); + bool isHorizontal = IsXULHorizontal(aBox); + + if (!grid) + return nsGridRowLayout::GetXULMaxSize(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 = IsXULHorizontal(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 = IsXULHorizontal(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 = nsBox::GetChildXULBox(aBox); + 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(i, !isHorizontal); // GetColumnFlex + nscoord left = 0; + nscoord right = 0; + grid->GetRowOffsets(i, left, right, !isHorizontal); // GetColumnOffsets + nsIFrame* box = column->GetBox(); + bool collapsed = false; + nscoord topMargin = column->mTopMargin; + nscoord bottomMargin = column->mBottomMargin; + + if (box) + collapsed = box->IsXULCollapsed(); + + 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(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->GetXULBorder(border); + offset += border; + aBox->GetXULPadding(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 = nsBox::GetNextXULBox(child); + + } + 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->IsXULHorizontal(); + + // 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::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + return nsGridRowLayout::XULLayout(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 MarkIntrinsicISizesDirty 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 = nsBox::GetChildXULBox(aBox); + + // count the children + int32_t columnCount = 0; + while(child) { + child = nsBox::GetNextXULBox(child); + 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..b103ab176 --- /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 final : public nsGridRowLayout +{ +public: + + friend already_AddRefed<nsBoxLayout> NS_NewGridRowLeafLayout(); + + virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual void ChildAddedOrRemoved(nsIFrame* aBox, nsBoxLayoutState& aState) override; + NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual void CountRowsColumns(nsIFrame* aBox, int32_t& aRowCount, int32_t& aComputedColumnCount) override; + virtual void DirtyRows(nsIFrame* aBox, nsBoxLayoutState& aState) override; + virtual int32_t BuildRows(nsIFrame* aBox, nsGridRow* aRows) override; + virtual Type GetType() override { return eRowLeaf; } + +protected: + + virtual void PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState, + nsBoxSize*& aBoxSizes, nscoord& aMinSize, + nscoord& aMaxSize, int32_t& aFlexes) override; + virtual void ComputeChildSizes(nsIFrame* aBox, + nsBoxLayoutState& aState, + nscoord& aGivenSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize*& aComputedBoxSizes) 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-stylo.list b/layout/xul/grid/reftests/reftest-stylo.list new file mode 100644 index 000000000..eb73955c9 --- /dev/null +++ b/layout/xul/grid/reftests/reftest-stylo.list @@ -0,0 +1,38 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +skip-if((B2G&&browserIsRemote)||Mulet) == row-sizing-1.xul row-sizing-1.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == column-sizing-1.xul column-sizing-1.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == row-or-column-sizing-1.xul row-or-column-sizing-1.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == row-or-column-sizing-1.xul row-or-column-sizing-1.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == row-or-column-sizing-1.xul row-or-column-sizing-1.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,60000) == z-order-1.xul z-order-1.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,60000) == z-order-2.xul z-order-2.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,60000) == not-full-basic.xul not-full-basic.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,110000) == not-full-grid-pack-align.xul not-full-grid-pack-align.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,30000) == not-full-row-group-align.xul not-full-row-group-align.xul +# does anyone want/need this behavior? +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,10000) == not-full-row-group-pack.xul not-full-row-group-pack.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,50000) == not-full-row-group-direction.xul not-full-row-group-direction.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,60000) == not-full-row-leaf-align.xul not-full-row-leaf-align.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,50000) == not-full-row-leaf-pack.xul not-full-row-leaf-pack.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) fuzzy-if(skiaContent,1,80000) == not-full-row-leaf-direction.xul not-full-row-leaf-direction.xul +skip-if(B2G||Mulet) random-if(transparentScrollbars) fuzzy-if(OSX==1010,1,565) == scrollable-columns.xul scrollable-columns.xul +# bug 650597 +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == scrollable-rows.xul scrollable-rows.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == sizing-2d.xul sizing-2d.xul +# Initial mulet triage: parity with B2G/B2G Desktop diff --git a/layout/xul/grid/reftests/reftest.list b/layout/xul/grid/reftests/reftest.list new file mode 100644 index 000000000..eb414f906 --- /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 +fuzzy-if(skiaContent,1,60000) == z-order-1.xul z-order-1-ref.xul +fuzzy-if(skiaContent,1,60000) == z-order-2.xul z-order-2-ref.xul +fuzzy-if(skiaContent,1,60000) == not-full-basic.xul not-full-basic-ref.xhtml +fuzzy-if(skiaContent,1,110000) == not-full-grid-pack-align.xul not-full-basic-ref.xhtml +fuzzy-if(skiaContent,1,30000) == not-full-row-group-align.xul not-full-row-group-align-ref.xhtml # does anyone want/need this behavior? +fuzzy-if(skiaContent,1,10000) == not-full-row-group-pack.xul not-full-row-group-pack-ref.xhtml +fuzzy-if(skiaContent,1,50000) == not-full-row-group-direction.xul not-full-row-group-direction-ref.xhtml +fuzzy-if(skiaContent,1,60000) == not-full-row-leaf-align.xul not-full-basic-ref.xhtml +fuzzy-if(skiaContent,1,50000) == not-full-row-leaf-pack.xul not-full-row-leaf-pack-ref.xhtml +fuzzy-if(skiaContent,1,80000) == not-full-row-leaf-direction.xul not-full-row-leaf-pack-ref.xhtml +random-if(transparentScrollbars) fuzzy-if(OSX==1010,1,565) == scrollable-columns.xul scrollable-columns-ref.xhtml # bug 650597 +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/moz.build b/layout/xul/moz.build new file mode 100644 index 000000000..8ed304c9f --- /dev/null +++ b/layout/xul/moz.build @@ -0,0 +1,107 @@ +# -*- Mode: python; 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/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: XUL') + +with Files('*Menu*'): + BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: Menus') + +if CONFIG['ENABLE_TESTS']: + MOCHITEST_MANIFESTS += ['test/mochitest.ini'] + MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini'] + BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] + +XPIDL_SOURCES += [ + 'nsIBoxObject.idl', + 'nsIBrowserBoxObject.idl', + 'nsIContainerBoxObject.idl', + 'nsIListBoxObject.idl', + 'nsIMenuBoxObject.idl', + 'nsIScrollBoxObject.idl', + 'nsISliderListener.idl', +] + +XPIDL_MODULE = 'layout_xul' + +EXPORTS += [ + 'nsBox.h', + 'nsIScrollbarMediator.h', + 'nsPIBoxObject.h', + 'nsPIListBoxObject.h', + 'nsXULPopupManager.h', +] + +EXPORTS.mozilla.dom += [ + 'BoxObject.h', + 'ContainerBoxObject.h', + 'ListBoxObject.h', + 'MenuBoxObject.h', + 'PopupBoxObject.h', + 'ScrollBoxObject.h', +] + +UNIFIED_SOURCES += [ + 'BoxObject.cpp', + 'nsBox.cpp', + 'nsBoxFrame.cpp', + 'nsBoxLayout.cpp', + 'nsBoxLayoutState.cpp', + 'nsButtonBoxFrame.cpp', + 'nsRepeatService.cpp', + 'nsRootBoxFrame.cpp', + 'nsScrollbarButtonFrame.cpp', + 'nsScrollbarFrame.cpp', + 'nsScrollBoxFrame.cpp', + 'nsSliderFrame.cpp', + 'nsSprocketLayout.cpp', + 'nsStackFrame.cpp', + 'nsStackLayout.cpp', + 'nsXULTooltipListener.cpp', +] + +if CONFIG['MOZ_XUL']: + UNIFIED_SOURCES += [ + 'ContainerBoxObject.cpp', + 'ListBoxObject.cpp', + 'MenuBoxObject.cpp', + 'nsDeckFrame.cpp', + 'nsDocElementBoxFrame.cpp', + 'nsGroupBoxFrame.cpp', + 'nsImageBoxFrame.cpp', + 'nsLeafBoxFrame.cpp', + 'nsListBoxBodyFrame.cpp', + 'nsListBoxLayout.cpp', + 'nsListItemFrame.cpp', + 'nsMenuBarFrame.cpp', + 'nsMenuBarListener.cpp', + 'nsMenuFrame.cpp', + 'nsMenuPopupFrame.cpp', + 'nsPopupSetFrame.cpp', + 'nsProgressMeterFrame.cpp', + 'nsResizerFrame.cpp', + 'nsSplitterFrame.cpp', + 'nsTextBoxFrame.cpp', + 'nsTitleBarFrame.cpp', + 'nsXULLabelFrame.cpp', + 'nsXULPopupManager.cpp', + 'PopupBoxObject.cpp', + 'ScrollBoxObject.cpp', + ] + +if CONFIG['MOZ_XUL']: + DIRS += ['tree', 'grid'] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '../base', + '../generic', + '../style', + '/dom/base', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/layout/xul/nsBox.cpp b/layout/xul/nsBox.cpp new file mode 100644 index 000000000..f7ec5fead --- /dev/null +++ b/layout/xul/nsBox.cpp @@ -0,0 +1,981 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsGkAtoms.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.Append(' '); + + 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); + } + } +} + +nsresult +nsBox::XULDumpBox(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) + SetXULDebug(aState, true); + else + SetXULDebug(aState, false); + } else if (mState & NS_STATE_IS_ROOT) { + SetXULDebug(aState, gDebug); + } +} +#endif + +#ifdef DEBUG_LAYOUT +void +nsBox::GetBoxName(nsAutoString& aName) +{ + aName.AssignLiteral("Box"); +} +#endif + +nsresult +nsBox::BeginXULLayout(nsBoxLayoutState& aState) +{ +#ifdef DEBUG_LAYOUT + + nsBoxAddIndents(); + printf("XULLayout: "); + XULDumpBox(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 (ReflowInput + // does this too). + nsIFrame* box; + for (box = GetChildXULBox(this); box; box = GetNextXULBox(box)) + box->AddStateBits(NS_FRAME_IS_DIRTY); + } + + // Another copy-over from ReflowInput. + // 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::DoXULLayout(nsBoxLayoutState& aState) +{ + return NS_OK; +} + +nsresult +nsBox::EndXULLayout(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); +} + +nsresult +nsBox::XULRelayoutChildAtOrdinal(nsIFrame* aChild) +{ + return NS_OK; +} + +nsresult +nsIFrame::GetXULClientRect(nsRect& aClientRect) +{ + aClientRect = mRect; + aClientRect.MoveTo(0,0); + + nsMargin borderPadding; + GetXULBorderAndPadding(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::SetXULBounds(nsBoxLayoutState& aState, const nsRect& aRect, bool aRemoveOverflowAreas) +{ + NS_BOX_ASSERTION(this, aRect.width >=0 && aRect.height >= 0, "SetXULBounds Size < 0"); + + nsRect rect(mRect); + + uint32_t flags = GetXULLayoutFlags(); + + uint32_t stateFlags = aState.LayoutFlags(); + + flags |= stateFlags; + + if ((flags & NS_FRAME_NO_MOVE_FRAME) == NS_FRAME_NO_MOVE_FRAME) + SetSize(aRect.Size()); + 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); + } + } + */ +} + +nsresult +nsIFrame::GetXULBorderAndPadding(nsMargin& aBorderAndPadding) +{ + aBorderAndPadding.SizeTo(0, 0, 0, 0); + nsresult rv = GetXULBorder(aBorderAndPadding); + if (NS_FAILED(rv)) + return rv; + + nsMargin padding; + rv = GetXULPadding(padding); + if (NS_FAILED(rv)) + return rv; + + aBorderAndPadding += padding; + + return rv; +} + +nsresult +nsBox::GetXULBorder(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; +} + +nsresult +nsBox::GetXULPadding(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; +} + +nsresult +nsBox::GetXULMargin(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::GetXULPrefSize(nsBoxLayoutState& aState) +{ + NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); + + nsSize pref(0,0); + DISPLAY_PREF_SIZE(this, pref); + + if (IsXULCollapsed()) + return pref; + + AddBorderAndPadding(pref); + bool widthSet, heightSet; + nsIFrame::AddXULPrefSize(this, pref, widthSet, heightSet); + + nsSize minSize = GetXULMinSize(aState); + nsSize maxSize = GetXULMaxSize(aState); + return BoundsCheck(minSize, pref, maxSize); +} + +nsSize +nsBox::GetXULMinSize(nsBoxLayoutState& aState) +{ + NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); + + nsSize min(0,0); + DISPLAY_MIN_SIZE(this, min); + + if (IsXULCollapsed()) + return min; + + AddBorderAndPadding(min); + bool widthSet, heightSet; + nsIFrame::AddXULMinSize(aState, this, min, widthSet, heightSet); + return min; +} + +nsSize +nsBox::GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) +{ + return nsSize(0, 0); +} + +nsSize +nsBox::GetXULMaxSize(nsBoxLayoutState& aState) +{ + NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); + + nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE); + DISPLAY_MAX_SIZE(this, maxSize); + + if (IsXULCollapsed()) + return maxSize; + + AddBorderAndPadding(maxSize); + bool widthSet, heightSet; + nsIFrame::AddXULMaxSize(this, maxSize, widthSet, heightSet); + return maxSize; +} + +nscoord +nsBox::GetXULFlex() +{ + nscoord flex = 0; + + nsIFrame::AddXULFlex(this, flex); + + return flex; +} + +uint32_t +nsIFrame::GetXULOrdinal() +{ + uint32_t ordinal = StyleXUL()->mBoxOrdinal; + + // When present, attribute value overrides CSS. + nsIContent* content = GetContent(); + if (content && content->IsXULElement()) { + nsresult error; + nsAutoString value; + + content->GetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, value); + if (!value.IsEmpty()) { + ordinal = value.ToInteger(&error); + } + } + + return ordinal; +} + +nscoord +nsBox::GetXULBoxAscent(nsBoxLayoutState& aState) +{ + if (IsXULCollapsed()) + return 0; + + return GetXULPrefSize(aState).height; +} + +bool +nsBox::IsXULCollapsed() +{ + return StyleVisibility()->mVisible == NS_STYLE_VISIBILITY_COLLAPSE; +} + +nsresult +nsIFrame::XULLayout(nsBoxLayoutState& aState) +{ + NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); + + nsBox *box = static_cast<nsBox*>(this); + DISPLAY_LAYOUT(box); + + box->BeginXULLayout(aState); + + box->DoXULLayout(aState); + + box->EndXULLayout(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 (IsXULCollapsed()) { + CollapseChild(aState, this, true); + return NS_OK; + } + */ + + + if (GetStateBits() & NS_FRAME_IS_DIRTY) + XULRedraw(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 = GetXULLayoutFlags(); + + 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() && !IsXULCollapsed()) { + // 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::XULRedraw(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::AddXULPrefSize(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 + // GetXULPrefSize/GetXULMinSize 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->IsXULElement()) { + 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::AddXULMinSize(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)) { + LayoutDeviceIntSize size; + theme->GetMinimumWidgetSize(aState.PresContext(), 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 + // GetXULPrefSize/GetXULMinSize 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->IsXULElement()) { + 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::AddXULMaxSize(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 + // GetXULPrefSize/GetXULMinSize 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->IsXULElement()) { + 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::AddXULFlex(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->IsXULElement()) { + 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->GetXULBorderAndPadding(borderPadding); + AddMargin(aSize, borderPadding); +} + +void +nsBox::AddMargin(nsIFrame* aChild, nsSize& aSize) +{ + nsMargin margin(0,0,0,0); + aChild->GetXULMargin(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)); +} + +/*static*/ nsIFrame* +nsBox::GetChildXULBox(const nsIFrame* aFrame) +{ + // box layout ends at box-wrapped frames, so don't allow these frames + // to report child boxes. + return aFrame->IsXULBoxFrame() ? aFrame->PrincipalChildList().FirstChild() : nullptr; +} + +/*static*/ nsIFrame* +nsBox::GetNextXULBox(const nsIFrame* aFrame) +{ + return aFrame->GetParent() && + aFrame->GetParent()->IsXULBoxFrame() ? aFrame->GetNextSibling() : nullptr; +} + +/*static*/ nsIFrame* +nsBox::GetParentXULBox(const nsIFrame* aFrame) +{ + return aFrame->GetParent() && + aFrame->GetParent()->IsXULBoxFrame() ? aFrame->GetParent() : nullptr; +} + +#ifdef DEBUG_LAYOUT +nsresult +nsBox::SetXULDebug(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 = nsBox::GetChildXULBox(this); + 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 = GetNextXULBox(child); + } + + // found a child + if (*aBox) { + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + + +nsresult +nsBox::GetXULDebug(bool& aDebug) +{ + aDebug = false; + return NS_OK; +} + +#endif diff --git a/layout/xul/nsBox.h b/layout/xul/nsBox.h new file mode 100644 index 000000000..bf019c174 --- /dev/null +++ b/layout/xul/nsBox.h @@ -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/. */ + +#ifndef nsBox_h___ +#define nsBox_h___ + +#include "mozilla/Attributes.h" +#include "nsIFrame.h" + +class nsITheme; + +class nsBox : public nsIFrame { + +public: + + friend class nsIFrame; + + static void Shutdown(); + + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nscoord GetXULFlex() override; + virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override; + + virtual nsSize GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) override; + + virtual bool IsXULCollapsed() override; + + virtual void SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, + bool aRemoveOverflowAreas = false) override; + + virtual nsresult GetXULBorder(nsMargin& aBorderAndPadding) override; + virtual nsresult GetXULPadding(nsMargin& aBorderAndPadding) override; + virtual nsresult GetXULMargin(nsMargin& aMargin) override; + + virtual Valignment GetXULVAlign() const override { return vAlign_Top; } + virtual Halignment GetXULHAlign() const override { return hAlign_Left; } + + virtual nsresult XULRelayoutChildAtOrdinal(nsIFrame* aChild) override; + +#ifdef DEBUG_LAYOUT + NS_IMETHOD GetDebugBoxAt(const nsPoint& aPoint, nsIFrame** aBox); + virtual nsresult GetXULDebug(bool& aDebug) override; + virtual nsresult SetXULDebug(nsBoxLayoutState& aState, bool aDebug) override; + + virtual nsresult XULDumpBox(FILE* out) override; + 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; + + 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); + + static nsIFrame* GetChildXULBox(const nsIFrame* aFrame); + static nsIFrame* GetNextXULBox(const nsIFrame* aFrame); + static nsIFrame* GetParentXULBox(const nsIFrame* aFrame); + +protected: + +#ifdef DEBUG_LAYOUT + virtual void AppendAttribute(const nsAutoString& aAttribute, const nsAutoString& aValue, nsAutoString& aResult); + + virtual void ListBox(nsAutoString& aResult); +#endif + + nsresult BeginXULLayout(nsBoxLayoutState& aState); + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState); + nsresult EndXULLayout(nsBoxLayoutState& aState); + +#ifdef DEBUG_LAYOUT + virtual void GetBoxName(nsAutoString& aName); + 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->XULDumpBox(stdout); \ + NS_DebugBreak(NSDebugAssertion, str, #expr, __FILE__, __LINE__); \ + } +#else +#define NS_BOX_ASSERTION(box,expr,str) {} +#endif + +#endif + diff --git a/layout/xul/nsBoxFrame.cpp b/layout/xul/nsBoxFrame.cpp new file mode 100644 index 000000000..9ca351d94 --- /dev/null +++ b/layout/xul/nsBoxFrame.cpp @@ -0,0 +1,2111 @@ +/* -*- 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 "nsBoxFrame.h" + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "nsBoxLayoutState.h" +#include "mozilla/dom/Touch.h" +#include "mozilla/Move.h" +#include "nsStyleContext.h" +#include "nsPlaceholderFrame.h" +#include "nsPresContext.h" +#include "nsCOMPtr.h" +#include "nsNameSpaceManager.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 "mozilla/EventStateManager.h" +#include "nsIDOMEvent.h" +#include "nsDisplayList.h" +#include "mozilla/Preferences.h" +#include "nsThemeConstants.h" +#include "nsLayoutUtils.h" +#include "nsSliderFrame.h" +#include <algorithm> + +// Needed for Print Preview +#include "nsIURI.h" + +#include "mozilla/TouchEvents.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +//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(aContext, aIsRoot, aLayoutManager); +} + +nsIFrame* +NS_NewBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsBoxFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsBoxFrame) + +#ifdef DEBUG +NS_QUERYFRAME_HEAD(nsBoxFrame) + NS_QUERYFRAME_ENTRY(nsBoxFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) +#endif + +nsBoxFrame::nsBoxFrame(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(layout); + } + + SetXULLayoutManager(layout); +} + +nsBoxFrame::~nsBoxFrame() +{ +} + +void +nsBoxFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + nsContainerFrame::SetInitialChildList(aListID, aChildList); + if (aListID == kPrincipalList) { + // initialize our list of infos. + nsBoxLayoutState state(PresContext()); + CheckBoxOrder(); + if (mLayoutManager) + mLayoutManager->ChildrenSet(this, state, mFrames.FirstChild()); + } +} + +/* 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, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + + if (GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER) { + AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); + } + + MarkIntrinsicISizesDirty(); + + CacheAttributes(); + +#ifdef DEBUG_LAYOUT + // if we are root and this + if (mState & NS_STATE_IS_ROOT) { + GetDebugPref(); + } +#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: "); + XULDumpBox(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 = IsXULHorizontal() ? 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 (IsXULHorizontal()) { + switch (boxInfo->mBoxPack) { + case StyleBoxPack::Start: + aHalign = nsBoxFrame::hAlign_Left; + return true; + case StyleBoxPack::Center: + aHalign = nsBoxFrame::hAlign_Center; + return true; + case StyleBoxPack::End: + aHalign = nsBoxFrame::hAlign_Right; + return true; + default: // Nonsensical value. Just bail. + return false; + } + } + else { + switch (boxInfo->mBoxAlign) { + case StyleBoxAlign::Start: + aHalign = nsBoxFrame::hAlign_Left; + return true; + case StyleBoxAlign::Center: + aHalign = nsBoxFrame::hAlign_Center; + return true; + case StyleBoxAlign::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 = IsXULHorizontal() ? 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 (IsXULHorizontal()) { + switch (boxInfo->mBoxAlign) { + case StyleBoxAlign::Start: + aValign = nsBoxFrame::vAlign_Top; + return true; + case StyleBoxAlign::Center: + aValign = nsBoxFrame::vAlign_Middle; + return true; + case StyleBoxAlign::Baseline: + aValign = nsBoxFrame::vAlign_BaseLine; + return true; + case StyleBoxAlign::End: + aValign = nsBoxFrame::vAlign_Bottom; + return true; + default: // Nonsensical value. Just bail. + return false; + } + } + else { + switch (boxInfo->mBoxPack) { + case StyleBoxPack::Start: + aValign = nsBoxFrame::vAlign_Top; + return true; + case StyleBoxPack::Center: + aValign = nsBoxFrame::vAlign_Middle; + return true; + case StyleBoxPack::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 == StyleBoxOrient::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 (IsXULHorizontal()) { + // 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 == StyleBoxDirection::Reverse) { + aIsNormal = !aIsNormal; // Invert our direction. + } + + // Now see if we have an attribute. The attribute overrides + // the style system value. + if (IsXULHorizontal()) { + 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 == StyleBoxAlign::Stretch); + + return true; +} + +void +nsBoxFrame::DidReflow(nsPresContext* aPresContext, + const ReflowInput* aReflowInput, + nsDidReflowStatus aStatus) +{ + nsFrameState preserveBits = + mState & (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); + nsFrame::DidReflow(aPresContext, aReflowInput, aStatus); + mState |= preserveBits; +} + +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::GetMinISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + + nsBoxLayoutState state(PresContext(), aRenderingContext); + nsSize minSize = GetXULMinSize(state); + + // GetXULMinSize 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 GetXULMinSize added, which is the + // result of GetXULBorderAndPadding. + nsMargin bp; + GetXULBorderAndPadding(bp); + + result = minSize.width - bp.LeftRight(); + result = std::max(result, 0); + + return result; +} + +/* virtual */ nscoord +nsBoxFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + + nsBoxLayoutState state(PresContext(), aRenderingContext); + nsSize prefSize = GetXULPrefSize(state); + + // GetXULPrefSize 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 GetXULPrefSize added, which is the + // result of GetXULBorderAndPadding. + nsMargin bp; + GetXULBorderAndPadding(bp); + + result = prefSize.width - bp.LeftRight(); + result = std::max(result, 0); + + return result; +} + +void +nsBoxFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + // 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, aReflowInput, aDesiredSize, aStatus); + + NS_ASSERTION(aReflowInput.ComputedWidth() >=0 && + aReflowInput.ComputedHeight() >= 0, "Computed Size < 0"); + +#ifdef DO_NOISY_REFLOW + printf("\n-------------Starting BoxFrame Reflow ----------------------------\n"); + printf("%p ** nsBF::Reflow %d ", this, myCounter++); + + printSize("AW", aReflowInput.AvailableWidth()); + printSize("AH", aReflowInput.AvailableHeight()); + printSize("CW", aReflowInput.ComputedWidth()); + printSize("CH", aReflowInput.ComputedHeight()); + + printf(" *\n"); + +#endif + + aStatus = NS_FRAME_COMPLETE; + + // create the layout state + nsBoxLayoutState state(aPresContext, aReflowInput.mRenderingContext, + &aReflowInput, aReflowInput.mReflowDepth); + + WritingMode wm = aReflowInput.GetWritingMode(); + LogicalSize computedSize(wm, aReflowInput.ComputedISize(), + aReflowInput.ComputedBSize()); + + LogicalMargin m = aReflowInput.ComputedLogicalBorderPadding(); + // GetXULBorderAndPadding(m); + + LogicalSize prefSize(wm); + + // if we are told to layout intrinsic then get our preferred size. + NS_ASSERTION(computedSize.ISize(wm) != NS_INTRINSICSIZE, + "computed inline size should always be computed"); + if (computedSize.BSize(wm) == NS_INTRINSICSIZE) { + nsSize physicalPrefSize = GetXULPrefSize(state); + nsSize minSize = GetXULMinSize(state); + nsSize maxSize = GetXULMaxSize(state); + // XXXbz isn't GetXULPrefSize supposed to bounds-check for us? + physicalPrefSize = BoundsCheck(minSize, physicalPrefSize, maxSize); + prefSize = LogicalSize(wm, physicalPrefSize); + } + + // get our desiredSize + computedSize.ISize(wm) += m.IStart(wm) + m.IEnd(wm); + + if (aReflowInput.ComputedBSize() == NS_INTRINSICSIZE) { + computedSize.BSize(wm) = prefSize.BSize(wm); + // prefSize is border-box but min/max constraints are content-box. + nscoord blockDirBorderPadding = + aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm); + nscoord contentBSize = computedSize.BSize(wm) - blockDirBorderPadding; + // Note: contentHeight might be negative, but that's OK because min-height + // is never negative. + computedSize.BSize(wm) = aReflowInput.ApplyMinMaxHeight(contentBSize) + + blockDirBorderPadding; + } else { + computedSize.BSize(wm) += m.BStart(wm) + m.BEnd(wm); + } + + nsSize physicalSize = computedSize.GetPhysicalSize(wm); + nsRect r(mRect.x, mRect.y, physicalSize.width, physicalSize.height); + + SetXULBounds(state, r); + + // layout our children + XULLayout(state); + + // ok our child could have gotten bigger. So lets get its bounds + + // get the ascent + LogicalSize boxSize = GetLogicalSize(wm); + nscoord ascent = boxSize.BSize(wm); + + // 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 = GetXULBoxAscent(state); + } + + aDesiredSize.SetSize(wm, boxSize); + aDesiredSize.SetBlockStartAscent(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, aReflowInput, aStatus); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +nsSize +nsBoxFrame::GetXULPrefSize(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 (IsXULCollapsed()) + return size; + + // if the size was not completely redefined in CSS then ask our children + bool widthSet, heightSet; + if (!nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet)) + { + if (mLayoutManager) { + nsSize layoutSize = mLayoutManager->GetXULPrefSize(this, aBoxLayoutState); + if (!widthSet) + size.width = layoutSize.width; + if (!heightSet) + size.height = layoutSize.height; + } + else { + size = nsBox::GetXULPrefSize(aBoxLayoutState); + } + } + + nsSize minSize = GetXULMinSize(aBoxLayoutState); + nsSize maxSize = GetXULMaxSize(aBoxLayoutState); + mPrefSize = BoundsCheck(minSize, size, maxSize); + + return mPrefSize; +} + +nscoord +nsBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) +{ + if (!DoesNeedRecalc(mAscent)) + return mAscent; + +#ifdef DEBUG_LAYOUT + PropagateDebug(aBoxLayoutState); +#endif + + if (IsXULCollapsed()) + return 0; + + if (mLayoutManager) + mAscent = mLayoutManager->GetAscent(this, aBoxLayoutState); + else + mAscent = nsBox::GetXULBoxAscent(aBoxLayoutState); + + return mAscent; +} + +nsSize +nsBoxFrame::GetXULMinSize(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 (IsXULCollapsed()) + return size; + + // if the size was not completely redefined in CSS then ask our children + bool widthSet, heightSet; + if (!nsIFrame::AddXULMinSize(aBoxLayoutState, this, size, widthSet, heightSet)) + { + if (mLayoutManager) { + nsSize layoutSize = mLayoutManager->GetXULMinSize(this, aBoxLayoutState); + if (!widthSet) + size.width = layoutSize.width; + if (!heightSet) + size.height = layoutSize.height; + } + else { + size = nsBox::GetXULMinSize(aBoxLayoutState); + } + } + + mMinSize = size; + + return size; +} + +nsSize +nsBoxFrame::GetXULMaxSize(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 (IsXULCollapsed()) + return size; + + // if the size was not completely redefined in CSS then ask our children + bool widthSet, heightSet; + if (!nsIFrame::AddXULMaxSize(this, size, widthSet, heightSet)) + { + if (mLayoutManager) { + nsSize layoutSize = mLayoutManager->GetXULMaxSize(this, aBoxLayoutState); + if (!widthSet) + size.width = layoutSize.width; + if (!heightSet) + size.height = layoutSize.height; + } + else { + size = nsBox::GetXULMaxSize(aBoxLayoutState); + } + } + + mMaxSize = size; + + return size; +} + +nscoord +nsBoxFrame::GetXULFlex() +{ + if (!DoesNeedRecalc(mFlex)) + return mFlex; + + mFlex = nsBox::GetXULFlex(); + + return mFlex; +} + +/** + * If subclassing please subclass this method not layout. + * layout will call this method. + */ +NS_IMETHODIMP +nsBoxFrame::DoXULLayout(nsBoxLayoutState& aState) +{ + uint32_t oldFlags = aState.LayoutFlags(); + aState.SetLayoutFlags(0); + + nsresult rv = NS_OK; + if (mLayoutManager) { + CoordNeedsRecalc(mAscent); + rv = mLayoutManager->XULLayout(this, aState); + } + + aState.SetLayoutFlags(oldFlags); + + if (HasAbsolutelyPositionedChildren()) { + // Set up a |reflowInput| to pass into ReflowAbsoluteFrames + WritingMode wm = GetWritingMode(); + ReflowInput reflowInput(aState.PresContext(), this, + aState.GetRenderingContext(), + LogicalSize(wm, GetLogicalSize().ISize(wm), + NS_UNCONSTRAINEDSIZE)); + + // Set up a |desiredSize| to pass into ReflowAbsoluteFrames + ReflowOutput desiredSize(reflowInput); + 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 = GetXULBoxAscent(aState); + } + desiredSize.SetBlockStartAscent(ascent); + desiredSize.mOverflowAreas = GetOverflowAreas(); + + AddStateBits(NS_FRAME_IN_REFLOW); + // 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, + reflowInput, reflowStatus); + RemoveStateBits(NS_FRAME_IN_REFLOW); + } + + return rv; +} + +void +nsBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // unregister access key + RegUnregAccessKey(false); + + // clean up the container box's layout manager and child boxes + SetXULLayoutManager(nullptr); + + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +#ifdef DEBUG_LAYOUT +nsresult +nsBoxFrame::SetXULDebug(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); + + MarkIntrinsicISizesDirty(); + } + + return NS_OK; +} +#endif + +/* virtual */ void +nsBoxFrame::MarkIntrinsicISizesDirty() +{ + SizeNeedsRecalc(mPrefSize); + SizeNeedsRecalc(mMinSize); + SizeNeedsRecalc(mMaxSize); + CoordNeedsRecalc(mFlex); + CoordNeedsRecalc(mAscent); + + if (mLayoutManager) { + nsBoxLayoutState state(PresContext()); + mLayoutManager->IntrinsicISizesDirty(this, state); + } + + // Don't call base class method, since everything it does is within an + // IsXULBoxWrapped check. +} + +void +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); +} + +void +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); +} + + +void +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); + } +} + +/* virtual */ nsContainerFrame* +nsBoxFrame::GetContentInsertionFrame() +{ + if (GetStateBits() & NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK) + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + return nsContainerFrame::GetContentInsertionFrame(); +} + +nsresult +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>. + if (mContent->IsAnyOfXULElements(nsGkAtoms::window, + nsGkAtoms::page, + nsGkAtoms::dialog, + 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) { + nsIFrame* parent = GetParentXULBox(this); + // 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 XULRelayoutChildAtOrdinal() only handles + // principal children. + if (parent && !(GetStateBits() & NS_FRAME_OUT_OF_FLOW) && + StyleDisplay()->mDisplay != mozilla::StyleDisplay::Popup) { + parent->XULRelayoutChildAtOrdinal(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); + } + else if (aAttribute == nsGkAtoms::rows && + mContent->IsXULElement(nsGkAtoms::tree)) { + // Reflow ourselves and all our children if "rows" changes, since + // nsTreeBodyFrame's layout reads this from its parent (this frame). + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + + return rv; +} + +#ifdef DEBUG_LAYOUT +void +nsBoxFrame::GetDebugPref() +{ + 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->GetDrawTarget(), ToReferenceFrame()); +} + +static void +PaintXULDebugBackground(nsIFrame* aFrame, DrawTarget* aDrawTarget, + const nsRect& aDirtyRect, nsPoint aPt) +{ + static_cast<nsBoxFrame*>(aFrame)->PaintXULDebugBackground(aDrawTarget, aPt); +} +#endif + +void +nsBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + bool forceLayer = false; + + if (GetContent()->IsXULElement()) { + // forcelayer is only supported on XUL elements with box layout + if (GetContent()->HasAttr(kNameSpaceID_None, nsGkAtoms::layer)) { + forceLayer = true; + } + // Check for frames that are marked as a part of the region used + // in calculating glass margins on Windows. + const nsStyleDisplay* styles = StyleDisplay(); + if (styles && styles->mAppearance == NS_THEME_WIN_EXCLUDE_GLASS) { + aBuilder->AddWindowExcludeGlassRegion( + nsRect(aBuilder->ToReferenceFrame(this), GetSize())); + } + } + + 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(DrawTarget* aDrawTarget, nsPoint aPt) +{ + nsMargin border; + GetXULBorder(border); + + nsMargin debugBorder; + nsMargin debugMargin; + nsMargin debugPadding; + + bool isHorizontal = IsXULHorizontal(); + + GetDebugBorder(debugBorder); + PixelMarginToTwips(debugBorder); + + GetDebugMargin(debugMargin); + PixelMarginToTwips(debugMargin); + + GetDebugPadding(debugPadding); + PixelMarginToTwips(debugPadding); + + nsRect inner(mRect); + inner.MoveTo(aPt); + inner.Deflate(debugMargin); + inner.Deflate(border); + //nsRect borderRect(inner); + + int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); + + ColorPattern color(ToDeviceColor(isHorizontal ? Color(0.f, 0.f, 1.f, 1.f) : + Color(1.f, 0.f, 0.f, 1.f))); + + //left + nsRect r(inner); + r.width = debugBorder.left; + aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), color); + + // top + r = inner; + r.height = debugBorder.top; + aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), color); + + //right + r = inner; + r.x = r.x + r.width - debugBorder.right; + r.width = debugBorder.right; + aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), color); + + //bottom + r = inner; + r.y = r.y + r.height - debugBorder.bottom; + r.height = debugBorder.bottom; + aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), color); + + // If we have dirty children or we are dirty place a green border around us. + if (NS_SUBTREE_DIRTY(this)) { + nsRect dirty(inner); + ColorPattern green(ToDeviceColor(Color(0.f, 1.f, 0.f, 1.f))); + aDrawTarget->StrokeRect(NSRectToRect(dirty, appUnitsPerDevPixel), green); + } +} + +void +nsBoxFrame::PaintXULDebugOverlay(DrawTarget& aDrawTarget, nsPoint aPt) +{ + nsMargin border; + GetXULBorder(border); + + nsMargin debugMargin; + GetDebugMargin(debugMargin); + PixelMarginToTwips(debugMargin); + + nsRect inner(mRect); + inner.MoveTo(aPt); + inner.Deflate(debugMargin); + inner.Deflate(border); + + nscoord onePixel = GetPresContext()->IntScaledPixelsToTwips(1); + + kid = nsBox::GetChildXULBox(this); + while (nullptr != kid) { + bool isHorizontal = IsXULHorizontal(); + + nscoord x, y, borderSize, spacerSize; + + nsRect cr(kid->mRect); + nsMargin margin; + kid->GetXULMargin(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; + } + + nscoord flex = kid->GetXULFlex(); + + if (!kid->IsXULCollapsed()) { + if (isHorizontal) + borderSize = cr.width; + else + borderSize = cr.height; + + DrawSpacer(GetPresContext(), aDrawTarget, isHorizontal, flex, x, y, borderSize, spacerSize); + } + + kid = GetNextXULBox(kid); + } +} +#endif + +#ifdef DEBUG_LAYOUT +void +nsBoxFrame::GetBoxName(nsAutoString& aName) +{ + GetFrameName(aName); +} +#endif + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("Box"), aResult); +} +#endif + +nsIAtom* +nsBoxFrame::GetType() const +{ + return nsGkAtoms::boxFrame; +} + +#ifdef DEBUG_LAYOUT +nsresult +nsBoxFrame::GetXULDebug(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(DrawTarget& aDrawTarget, bool aHorizontal, nscoord x1, nscoord y1, nscoord x2, nscoord y2) +{ + nsPoint p1(x1, y1); + nsPoint p2(x2, y2); + if (!aHorizontal) { + Swap(p1.x, p1.y); + Swap(p2.x, p2.y); + } + ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f))); + StrokeLineWithSnapping(p1, p2, PresContext()->AppUnitsPerDevPixel(), + aDrawTarget, color); +} + +void +nsBoxFrame::FillRect(DrawTarget& aDrawTarget, bool aHorizontal, nscoord x, nscoord y, nscoord width, nscoord height) +{ + Rect rect = NSRectToSnappedRect(aHorizontal ? nsRect(x, y, width, height) : + nsRect(y, x, height, width), + PresContext()->AppUnitsPerDevPixel(), + aDrawTarget); + ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f))); + aDrawTarget.FillRect(rect, white); +} + +void +nsBoxFrame::DrawSpacer(nsPresContext* aPresContext, DrawTarget& aDrawTarget, + 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(aDrawTarget, aHorizontal, x,y + spacerSize/2, x + size, y + spacerSize/2); + } else { + for (int i=0; i < coils; i++) + { + DrawLine(aDrawTarget, aHorizontal, offset, center+halfSpacer, offset+halfCoilSize, center-halfSpacer); + DrawLine(aDrawTarget, aHorizontal, offset+halfCoilSize, center-halfSpacer, offset+coilSize, center+halfSpacer); + + offset += coilSize; + } + } + + FillRect(aDrawTarget, aHorizontal, x + size - spacerSize/2, y, spacerSize/2, spacerSize); + FillRect(aDrawTarget, aHorizontal, x, y, spacerSize/2, spacerSize); +} + +void +nsBoxFrame::GetDebugBorder(nsMargin& aInset) +{ + aInset.SizeTo(2,2,2,2); + + if (IsXULHorizontal()) + 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(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->GetXULBorderAndPadding(border); + insideBorder.Deflate(border); + + bool isHorizontal = IsXULHorizontal(); + + if (!insideBorder.Contains(nsPoint(x,y))) + return NS_ERROR_FAILURE; + + //printf("%%%%%% inside box %%%%%%%\n"); + + int count = 0; + nsIFrame* child = nsBox::GetChildXULBox(aBox); + + nsMargin m; + nsMargin m2; + GetDebugBorder(m); + PixelMarginToTwips(m); + + GetDebugMargin(m2); + PixelMarginToTwips(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"); + XULDumpBox(stdout); + printf("\n"); + } + + if (child->GetContent()) { + printf("child #%d: ", count); + child->XULDumpBox(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::AddXULPrefSize(child, prefSizeCSS, widthSet, heightSet); + nsIFrame::AddXULMinSize (state, child, minSizeCSS, widthSet, heightSet); + nsIFrame::AddXULMaxSize (child, maxSizeCSS, widthSet, heightSet); + nsIFrame::AddXULFlex (child, flexCSS); + + nsSize prefSize = child->GetXULPrefSize(state); + nsSize minSize = child->GetXULMinSize(state); + nsSize maxSize = child->GetXULMaxSize(state); + nscoord flexSize = child->GetXULFlex(); + nscoord ascentSize = child->GetXULBoxAscent(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 = GetNextXULBox(child); + count++; + } + } else { + } + + mDebugChild = nullptr; + + return NS_OK; +} + +void +nsBoxFrame::SetDebugOnChildList(nsBoxLayoutState& aState, nsIFrame* aChild, bool aDebug) +{ + nsIFrame* child = nsBox::GetChildXULBox(this); + while (child) + { + child->SetXULDebug(aState, aDebug); + child = GetNextXULBox(child); + } +} + +nsresult +nsBoxFrame::GetFrameSizeWithMargin(nsIFrame* aBox, nsSize& aSize) +{ + nsRect rect(aBox->GetRect()); + nsMargin margin(0,0,0,0); + aBox->GetXULMargin(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); + + // only support accesskeys for the following elements + if (!mContent->IsAnyOfXULElements(nsGkAtoms::button, + nsGkAtoms::toolbarbutton, + nsGkAtoms::checkbox, + nsGkAtoms::textbox, + nsGkAtoms::tab, + 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 + EventStateManager* 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->GetXULOrdinal() <= aRealFrame2->GetXULOrdinal(); +} + +void +nsBoxFrame::CheckBoxOrder() +{ + if (SupportsOrdinalsInChildren() && + !nsIFrame::IsFrameListSorted<IsBoxOrdinalLEQ>(mFrames)) { + nsIFrame::SortFrameList<IsBoxOrdinalLEQ>(mFrames); + } +} + +nsresult +nsBoxFrame::LayoutChildAt(nsBoxLayoutState& aState, nsIFrame* aBox, const nsRect& aRect) +{ + // get the current rect + nsRect oldRect(aBox->GetRect()); + aBox->SetXULBounds(aState, aRect); + + bool layout = NS_SUBTREE_DIRTY(aBox); + + if (layout || (oldRect.width != aRect.width || oldRect.height != aRect.height)) { + return aBox->XULLayout(aState); + } + + return NS_OK; +} + +nsresult +nsBoxFrame::XULRelayoutChildAtOrdinal(nsIFrame* aChild) +{ + if (!SupportsOrdinalsInChildren()) + return NS_OK; + + uint32_t ord = aChild->GetXULOrdinal(); + + nsIFrame* child = mFrames.FirstChild(); + nsIFrame* newPrevSib = nullptr; + + while (child) { + if (ord < child->GetXULOrdinal()) { + break; + } + + if (child != aChild) { + newPrevSib = child; + } + + child = GetNextXULBox(child); + } + + 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) override; + virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { + return false; + } + 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: + explicit nsXULEventRedirectorWrapper(nsIFrame* aTargetFrame) + : mTargetFrame(aTargetFrame) {} + virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) override { + return new (aBuilder) + nsDisplayXULEventRedirector(aBuilder, aFrame, aList, mTargetFrame); + } + virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) override { + 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(WidgetGUIEvent* aEvent, nsPoint &aPoint) { + LayoutDeviceIntPoint refPoint; + bool res = GetEventPoint(aEvent, refPoint); + aPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo( + aEvent, refPoint, this); + return res; +} + +bool +nsBoxFrame::GetEventPoint(WidgetGUIEvent* aEvent, LayoutDeviceIntPoint& aPoint) { + NS_ENSURE_TRUE(aEvent, false); + + WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); + if (touchEvent) { + // return false if there is more than one touch on the page, or if + // we can't find a touch point + if (touchEvent->mTouches.Length() != 1) { + return false; + } + + dom::Touch* touch = touchEvent->mTouches.SafeElementAt(0); + if (!touch) { + return false; + } + aPoint = touch->mRefPoint; + } else { + aPoint = aEvent->mRefPoint; + } + return true; +} diff --git a/layout/xul/nsBoxFrame.h b/layout/xul/nsBoxFrame.h new file mode 100644 index 000000000..ad405222f --- /dev/null +++ b/layout/xul/nsBoxFrame.h @@ -0,0 +1,257 @@ +/* -*- 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; + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + bool aIsRoot, + nsBoxLayout* aLayoutManager); +nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + +class nsBoxFrame : public nsContainerFrame +{ +protected: + typedef mozilla::gfx::DrawTarget DrawTarget; + +public: + NS_DECL_FRAMEARENA_HELPERS +#ifdef DEBUG + NS_DECL_QUERYFRAME_TARGET(nsBoxFrame) + NS_DECL_QUERYFRAME +#endif + + 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 SetXULLayoutManager(nsBoxLayout* aLayout) override { mLayoutManager = aLayout; } + virtual nsBoxLayout* GetXULLayoutManager() override { return mLayoutManager; } + + virtual nsresult XULRelayoutChildAtOrdinal(nsIFrame* aChild) override; + + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nscoord GetXULFlex() override; + virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override; +#ifdef DEBUG_LAYOUT + virtual nsresult SetXULDebug(nsBoxLayoutState& aBoxLayoutState, bool aDebug) override; + virtual nsresult GetXULDebug(bool& aDebug) override; +#endif + virtual Valignment GetXULVAlign() const override { return mValign; } + virtual Halignment GetXULHAlign() const override { return mHalign; } + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override; + + virtual bool ComputesOwnOverflowArea() override { return false; } + + // ----- child and sibling operations --- + + // ----- public methods ------- + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void MarkIntrinsicISizesDirty() override; + virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override; + virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override; + + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + virtual void SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) override; + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + virtual nsContainerFrame* GetContentInsertionFrame() override; + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + virtual nsIAtom* GetType() const override; + + virtual bool IsFrameOfType(uint32_t aFlags) const 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 ReflowInput doesn't tell us to be + // NS_INTRINSICSIZE wide.) + return nsContainerFrame::IsFrameOfType(aFlags & + ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock | eXULBox | + nsIFrame::eExcludesIgnorableWhitespace)); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + virtual void DidReflow(nsPresContext* aPresContext, + const ReflowInput* aReflowInput, + nsDidReflowStatus aStatus) override; + + virtual bool HonorPrintBackgroundSettings() override; + + virtual ~nsBoxFrame(); + + explicit nsBoxFrame(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) 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) override; + void PaintXULDebugBackground(nsRenderingContext& aRenderingContext, + nsPoint aPt); + void PaintXULDebugOverlay(DrawTarget& 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) 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(mozilla::WidgetGUIEvent* 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(mozilla::WidgetGUIEvent* aEvent, + mozilla::LayoutDeviceIntPoint& aPoint); + +protected: + void RegUnregAccessKey(bool aDoReg); + + void CheckBoxOrder(); + +private: + +#ifdef DEBUG_LAYOUT + nsresult SetXULDebug(nsPresContext* aPresContext, bool aDebug); + bool GetInitialDebug(bool& aDebug); + void GetDebugPref(); + + void GetDebugBorder(nsMargin& aInset); + void GetDebugPadding(nsMargin& aInset); + void GetDebugMargin(nsMargin& aInset); + + nsresult GetFrameSizeWithMargin(nsIFrame* aBox, nsSize& aSize); + + void PixelMarginToTwips(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, DrawTarget& aDrawTarget, bool aHorizontal, int32_t flex, nscoord x, nscoord y, nscoord size, nscoord spacerSize); + void DrawLine(DrawTarget& aDrawTarget, bool aHorizontal, nscoord x1, nscoord y1, nscoord x2, nscoord y2); + void FillRect(DrawTarget& aDrawTarget, 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/nsBoxLayout.cpp b/layout/xul/nsBoxLayout.cpp new file mode 100644 index 000000000..60db32a7a --- /dev/null +++ b/layout/xul/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::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize pref (0, 0); + AddBorderAndPadding(aBox, pref); + + return pref; +} + +nsSize +nsBoxLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize minSize (0,0); + AddBorderAndPadding(aBox, minSize); + return minSize; +} + +nsSize +nsBoxLayout::GetXULMaxSize(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::XULLayout(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_ISUPPORTS(nsBoxLayout, nsBoxLayout) diff --git a/layout/xul/nsBoxLayout.h b/layout/xul/nsBoxLayout.h new file mode 100644 index 000000000..cb4d77170 --- /dev/null +++ b/layout/xul/nsBoxLayout.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 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 { + +protected: + virtual ~nsBoxLayout() {} + +public: + + nsBoxLayout() {} + + NS_DECL_ISUPPORTS + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_BOX_LAYOUT_IID) + + NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState); + + virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState); + virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState); + virtual nsSize GetXULMaxSize(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 IntrinsicISizesDirty(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/nsBoxLayoutState.cpp b/layout/xul/nsBoxLayoutState.cpp new file mode 100644 index 000000000..e1219534e --- /dev/null +++ b/layout/xul/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 ReflowInput* aOuterReflowInput, + uint16_t aReflowDepth) + : mPresContext(aPresContext) + , mRenderingContext(aRenderingContext) + , mOuterReflowInput(aOuterReflowInput) + , 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) + , mOuterReflowInput(aState.mOuterReflowInput) + , mLayoutFlags(aState.mLayoutFlags) + , mReflowDepth(aState.mReflowDepth + 1) + , mPaintingDisabled(aState.mPaintingDisabled) +{ + NS_ASSERTION(mPresContext, "PresContext must be non-null"); +} diff --git a/layout/xul/nsBoxLayoutState.h b/layout/xul/nsBoxLayoutState.h new file mode 100644 index 000000000..b857ad9c0 --- /dev/null +++ b/layout/xul/nsBoxLayoutState.h @@ -0,0 +1,77 @@ +/* -*- 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; +namespace mozilla { +struct ReflowInput; +} // namespace mozilla + + +class MOZ_STACK_CLASS nsBoxLayoutState +{ + using ReflowInput = mozilla::ReflowInput; + +public: + explicit nsBoxLayoutState(nsPresContext* aPresContext, + nsRenderingContext* aRenderingContext = nullptr, + // see OuterReflowInput() below + const ReflowInput* aOuterReflowInput = nullptr, + uint16_t aReflowDepth = 0); + nsBoxLayoutState(const nsBoxLayoutState& aState); + + 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 { + explicit 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 ReflowInput* OuterReflowInput() { return mOuterReflowInput; } + + uint16_t GetReflowDepth() { return mReflowDepth; } + +private: + RefPtr<nsPresContext> mPresContext; + nsRenderingContext *mRenderingContext; + const ReflowInput *mOuterReflowInput; + uint32_t mLayoutFlags; + uint16_t mReflowDepth; + bool mPaintingDisabled; +}; + +#endif + diff --git a/layout/xul/nsButtonBoxFrame.cpp b/layout/xul/nsButtonBoxFrame.cpp new file mode 100644 index 000000000..45d934516 --- /dev/null +++ b/layout/xul/nsButtonBoxFrame.cpp @@ -0,0 +1,230 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsIDOMElement.h" +#include "nsDisplayList.h" +#include "nsContentUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TextEvents.h" + +using namespace mozilla; + + +NS_IMPL_ISUPPORTS(nsButtonBoxFrame::nsButtonBoxListener, nsIDOMEventListener) + +nsresult +nsButtonBoxFrame::nsButtonBoxListener::HandleEvent(nsIDOMEvent* aEvent) +{ + if (!mButtonBoxFrame) { + return NS_OK; + } + + nsAutoString eventType; + aEvent->GetType(eventType); + + if (eventType.EqualsLiteral("blur")) { + mButtonBoxFrame->Blurred(); + return NS_OK; + } + + NS_ABORT(); + + return NS_OK; +} + +// +// NS_NewXULButtonFrame +// +// Creates a new Button frame and returns it +// +nsIFrame* +NS_NewButtonBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsButtonBoxFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsButtonBoxFrame) + +nsButtonBoxFrame::nsButtonBoxFrame(nsStyleContext* aContext) : + nsBoxFrame(aContext, false), + mButtonBoxListener(nullptr), + mIsHandlingKeyEvent(false) +{ + UpdateMouseThrough(); +} + +void +nsButtonBoxFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + mButtonBoxListener = new nsButtonBoxListener(this); + + mContent->AddSystemEventListener(NS_LITERAL_STRING("blur"), mButtonBoxListener, false); +} + +void +nsButtonBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + mContent->RemoveSystemEventListener(NS_LITERAL_STRING("blur"), mButtonBoxListener, false); + + mButtonBoxListener->mButtonBoxFrame = nullptr; + mButtonBoxListener = nullptr; + + nsBoxFrame::DestroyFrom(aDestructRoot); +} + +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); +} + +nsresult +nsButtonBoxFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + switch (aEvent->mMessage) { + case eKeyDown: { + WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); + if (!keyEvent) { + break; + } + if (NS_VK_SPACE == keyEvent->mKeyCode) { + EventStateManager* esm = aPresContext->EventStateManager(); + // :hover:active state + esm->SetContentState(mContent, NS_EVENT_STATE_HOVER); + esm->SetContentState(mContent, NS_EVENT_STATE_ACTIVE); + mIsHandlingKeyEvent = true; + } + break; + } + +// On mac, Return fires the default button, not the focused one. +#ifndef XP_MACOSX + case eKeyPress: { + WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); + if (!keyEvent) { + break; + } + if (NS_VK_RETURN == keyEvent->mKeyCode) { + nsCOMPtr<nsIDOMXULButtonElement> buttonEl(do_QueryInterface(mContent)); + if (buttonEl) { + MouseClicked(aEvent); + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } + } + break; + } +#endif + + case eKeyUp: { + WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); + if (!keyEvent) { + break; + } + if (NS_VK_SPACE == keyEvent->mKeyCode) { + mIsHandlingKeyEvent = false; + // only activate on keyup if we're already in the :hover:active state + NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?"); + EventStates buttonState = mContent->AsElement()->State(); + if (buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE | + NS_EVENT_STATE_HOVER)) { + // return to normal state + EventStateManager* esm = aPresContext->EventStateManager(); + esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE); + esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER); + MouseClicked(aEvent); + } + } + break; + } + + case eMouseClick: { + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent->IsLeftClickEvent()) { + MouseClicked(mouseEvent); + } + break; + } + + default: + break; + } + + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +void +nsButtonBoxFrame::Blurred() +{ + NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?"); + EventStates buttonState = mContent->AsElement()->State(); + if (mIsHandlingKeyEvent && + buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE | + NS_EVENT_STATE_HOVER)) { + // return to normal state + EventStateManager* esm = PresContext()->EventStateManager(); + esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE); + esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER); + } + mIsHandlingKeyEvent = false; +} + +void +nsButtonBoxFrame::DoMouseClick(WidgetGUIEvent* 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) { + WidgetInputEvent* inputEvent = aEvent->AsInputEvent(); + isShift = inputEvent->IsShift(); + isControl = inputEvent->IsControl(); + isAlt = inputEvent->IsAlt(); + isMeta = inputEvent->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->IsTrusted() : aTrustEvent, + nullptr, shell, + isControl, isAlt, isShift, isMeta); + } +} diff --git a/layout/xul/nsButtonBoxFrame.h b/layout/xul/nsButtonBoxFrame.h new file mode 100644 index 000000000..e9bfd99a5 --- /dev/null +++ b/layout/xul/nsButtonBoxFrame.h @@ -0,0 +1,75 @@ +/* -*- 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 "nsIDOMEventListener.h" +#include "nsBoxFrame.h" + +class nsButtonBoxFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewButtonBoxFrame(nsIPresShell* aPresShell); + + explicit nsButtonBoxFrame(nsStyleContext* aContext); + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual nsresult HandleEvent(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual void MouseClicked(mozilla::WidgetGUIEvent* aEvent) + { DoMouseClick(aEvent, false); } + + void Blurred(); + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const 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(mozilla::WidgetGUIEvent* aEvent, bool aTrustEvent); + void UpdateMouseThrough() override { AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); } + +private: + class nsButtonBoxListener final : public nsIDOMEventListener + { + public: + explicit nsButtonBoxListener(nsButtonBoxFrame* aButtonBoxFrame) : + mButtonBoxFrame(aButtonBoxFrame) + { } + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override; + + NS_DECL_ISUPPORTS + + private: + friend class nsButtonBoxFrame; + virtual ~nsButtonBoxListener() { } + nsButtonBoxFrame* mButtonBoxFrame; + }; + + RefPtr<nsButtonBoxListener> mButtonBoxListener; + bool mIsHandlingKeyEvent; +}; // class nsButtonBoxFrame + +#endif /* nsButtonBoxFrame_h___ */ diff --git a/layout/xul/nsDeckFrame.cpp b/layout/xul/nsDeckFrame.cpp new file mode 100644 index 000000000..b0c0296b2 --- /dev/null +++ b/layout/xul/nsDeckFrame.cpp @@ -0,0 +1,231 @@ +/* -*- 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 "nsNameSpaceManager.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" +#include "nsContentUtils.h" + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +nsIFrame* +NS_NewDeckFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsDeckFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsDeckFrame) + +NS_QUERYFRAME_HEAD(nsDeckFrame) + NS_QUERYFRAME_ENTRY(nsDeckFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + + +nsDeckFrame::nsDeckFrame(nsStyleContext* aContext) + : nsBoxFrame(aContext), mIndex(0) +{ + nsCOMPtr<nsBoxLayout> layout; + NS_NewStackLayout(layout); + SetXULLayoutManager(layout); +} + +nsIAtom* +nsDeckFrame::GetType() const +{ + return nsGkAtoms::deckFrame; +} + +nsresult +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, + nsContainerFrame* 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::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + nsIFrame* currentFrame = GetSelectedBox(); + if (currentFrame && + aOldFrame && + currentFrame != aOldFrame) { + // If the frame we're removing is at an index that's less + // than mIndex, that means we're going to be shifting indexes + // by 1. + // + // We attempt to keep the same child displayed by automatically + // updating our internal notion of the current index. + int32_t removedIndex = mFrames.IndexOf(aOldFrame); + MOZ_ASSERT(removedIndex >= 0, + "A deck child was removed that was not in mFrames."); + if (removedIndex < mIndex) { + mIndex--; + // This is going to cause us to handle the index change in IndexedChanged, + // but since the new index will match mIndex, it's essentially a noop. + nsContentUtils::AddScriptRunner(new nsSetAttrRunnable( + mContent, nsGkAtoms::selectedIndex, mIndex)); + } + } + nsBoxFrame::RemoveFrame(aListID, aOldFrame); +} + +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::DoXULLayout(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::DoXULLayout(aState); + + // run though each child. Hide all but the selected one + nsIFrame* box = nsBox::GetChildXULBox(this); + + nscoord count = 0; + while (box) + { + // make collapsed children not show up + if (count != mIndex) + HideBox(box); + + box = GetNextXULBox(box); + count++; + } + + aState.SetLayoutFlags(oldFlags); + + return rv; +} + diff --git a/layout/xul/nsDeckFrame.h b/layout/xul/nsDeckFrame.h new file mode 100644 index 000000000..2c7ae1445 --- /dev/null +++ b/layout/xul/nsDeckFrame.h @@ -0,0 +1,77 @@ +/* -*- 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); + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aState) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("Deck"), aResult); + } +#endif + + explicit nsDeckFrame(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/nsDocElementBoxFrame.cpp b/layout/xul/nsDocElementBoxFrame.cpp new file mode 100644 index 000000000..4d978617a --- /dev/null +++ b/layout/xul/nsDocElementBoxFrame.cpp @@ -0,0 +1,148 @@ +/* -*- 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 "nsIDOMEvent.h" +#include "nsStyleConsts.h" +#include "nsGkAtoms.h" +#include "nsIPresShell.h" +#include "nsBoxFrame.h" +#include "nsStackLayout.h" +#include "nsIAnonymousContentCreator.h" +#include "mozilla/dom/NodeInfo.h" +#include "nsIServiceManager.h" +#include "nsNodeInfoManager.h" +#include "nsContentCreatorFunctions.h" +#include "nsContentUtils.h" +#include "nsContentList.h" +#include "mozilla/dom/Element.h" + +//#define DEBUG_REFLOW + +using namespace mozilla::dom; + +class nsDocElementBoxFrame : public nsBoxFrame, + public nsIAnonymousContentCreator +{ +public: + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + explicit nsDocElementBoxFrame(nsStyleContext* aContext) + :nsBoxFrame(aContext, true) {} + + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // nsIAnonymousContentCreator + virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, + uint32_t aFilter) override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + // Override nsBoxFrame. + if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced)) + return false; + return nsBoxFrame::IsFrameOfType(aFlags); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif +private: + nsCOMPtr<Element> mPopupgroupContent; + nsCOMPtr<Element> mTooltipContent; +}; + +//---------------------------------------------------------------------- + +nsContainerFrame* +NS_NewDocElementBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsDocElementBoxFrame(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->GetComposedDoc(); + 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! + RefPtr<NodeInfo> 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(nsTArray<nsIContent*>& aElements, + uint32_t aFilter) +{ + if (mPopupgroupContent) { + aElements.AppendElement(mPopupgroupContent); + } + + if (mTooltipContent) { + aElements.AppendElement(mTooltipContent); + } +} + +NS_QUERYFRAME_HEAD(nsDocElementBoxFrame) + NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsDocElementBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("DocElementBox"), aResult); +} +#endif diff --git a/layout/xul/nsGroupBoxFrame.cpp b/layout/xul/nsGroupBoxFrame.cpp new file mode 100644 index 000000000..514287a24 --- /dev/null +++ b/layout/xul/nsGroupBoxFrame.cpp @@ -0,0 +1,311 @@ +/* -*- 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 "mozilla/gfx/2D.h" +#include "nsCSSRendering.h" +#include "nsLayoutUtils.h" +#include "nsRenderingContext.h" +#include "nsStyleContext.h" +#include "nsDisplayList.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +class nsGroupBoxFrame : public nsBoxFrame { +public: + NS_DECL_FRAMEARENA_HELPERS + + explicit nsGroupBoxFrame(nsStyleContext* aContext): + nsBoxFrame(aContext) {} + + virtual nsresult GetXULBorderAndPadding(nsMargin& aBorderAndPadding) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(NS_LITERAL_STRING("GroupBoxFrame"), aResult); + } +#endif + + virtual bool HonorPrintBackgroundSettings() override { return false; } + + DrawResult PaintBorder(nsRenderingContext& aRenderingContext, + nsPoint aPt, + const nsRect& aDirtyRect); + nsRect GetBackgroundRectRelativeToSelf(nscoord* aOutYOffset = nullptr, nsRect* aOutGroupRect = nullptr); + + // 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) override { aHorizontal = false; } + virtual bool GetInitialHAlignment(Halignment& aHalign) override { aHalign = hAlign_Left; return true; } + virtual bool GetInitialVAlignment(Valignment& aValign) override { aValign = vAlign_Top; return true; } + virtual bool GetInitialAutoStretch(bool& aStretch) override { aStretch = true; return true; } + + nsIFrame* GetCaptionBox(nsRect& aCaptionRect); +}; + +/* +class nsGroupBoxInnerFrame : public nsBoxFrame { +public: + + nsGroupBoxInnerFrame(nsIPresShell* aShell, nsStyleContext* aContext): + nsBoxFrame(aShell, aContext) {} + + +#ifdef DEBUG_FRAME_DUMP + 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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsGroupBoxFrame) + +class nsDisplayXULGroupBorder : public nsDisplayItem { +public: + nsDisplayXULGroupBorder(nsDisplayListBuilder* aBuilder, + nsGroupBoxFrame* aFrame) : + nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayXULGroupBorder); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayXULGroupBorder() { + MOZ_COUNT_DTOR(nsDisplayXULGroupBorder); + } +#endif + + nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override; + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion *aInvalidRegion) override; + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override { + aOutFrames->AppendElement(mFrame); + } + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + NS_DISPLAY_DECL_NAME("XULGroupBackground", TYPE_XUL_GROUP_BACKGROUND) +}; + +nsDisplayItemGeometry* +nsDisplayXULGroupBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder) +{ + return new nsDisplayItemGenericImageGeometry(this, aBuilder); +} + +void +nsDisplayXULGroupBorder::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) +{ + auto geometry = + static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + + nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); +} + +void +nsDisplayXULGroupBorder::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + DrawResult result = static_cast<nsGroupBoxFrame*>(mFrame) + ->PaintBorder(*aCtx, ToReferenceFrame(), mVisibleRect); + + nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); +} + +void +nsGroupBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // Paint our background and border + if (IsVisibleForPainting(aBuilder)) { + nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + aBuilder, this, GetBackgroundRectRelativeToSelf(), + aLists.BorderBackground()); + aLists.BorderBackground()->AppendNewToTop(new (aBuilder) + nsDisplayXULGroupBorder(aBuilder, this)); + + DisplayOutline(aBuilder, aLists); + } + + BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); +} + +nsRect +nsGroupBoxFrame::GetBackgroundRectRelativeToSelf(nscoord* aOutYOffset, nsRect* aOutGroupRect) +{ + const nsMargin& border = StyleBorder()->GetComputedBorder(); + + nsRect groupRect; + nsIFrame* groupBox = GetCaptionBox(groupRect); + + nscoord yoff = 0; + 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; + } + } + + if (aOutYOffset) { + *aOutYOffset = yoff; + } + if (aOutGroupRect) { + *aOutGroupRect = groupRect; + } + + return nsRect(0, yoff, mRect.width, mRect.height - yoff); +} + +DrawResult +nsGroupBoxFrame::PaintBorder(nsRenderingContext& aRenderingContext, + nsPoint aPt, const nsRect& aDirtyRect) { + + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + gfxContext* gfx = aRenderingContext.ThebesContext(); + + Sides skipSides; + const nsStyleBorder* borderStyleData = StyleBorder(); + const nsMargin& border = borderStyleData->GetComputedBorder(); + nsPresContext* presContext = PresContext(); + + nsRect groupRect; + nsIFrame* groupBox = GetCaptionBox(groupRect); + + nscoord yoff = 0; + nsRect rect = GetBackgroundRectRelativeToSelf(&yoff, &groupRect) + aPt; + groupRect += aPt; + + DrawResult result = DrawResult::SUCCESS; + if (groupBox) { + int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); + + // 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; + + gfx->Save(); + gfx->Clip(NSRectToSnappedRect(clipRect, appUnitsPerDevPixel, *drawTarget)); + result &= + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, rect, mStyleContext, + PaintBorderFlags::SYNC_DECODE_IMAGES, skipSides); + gfx->Restore(); + + // draw right side + clipRect = rect; + clipRect.x = groupRect.XMost(); + clipRect.width = rect.XMost() - groupRect.XMost(); + clipRect.height = border.top; + + gfx->Save(); + gfx->Clip(NSRectToSnappedRect(clipRect, appUnitsPerDevPixel, *drawTarget)); + result &= + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, rect, mStyleContext, + PaintBorderFlags::SYNC_DECODE_IMAGES, skipSides); + gfx->Restore(); + + // draw bottom + + clipRect = rect; + clipRect.y += border.top; + clipRect.height = mRect.height - (yoff + border.top); + + gfx->Save(); + gfx->Clip(NSRectToSnappedRect(clipRect, appUnitsPerDevPixel, *drawTarget)); + result &= + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, rect, mStyleContext, + PaintBorderFlags::SYNC_DECODE_IMAGES, skipSides); + gfx->Restore(); + + } else { + result &= + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, nsRect(aPt, GetSize()), + mStyleContext, + PaintBorderFlags::SYNC_DECODE_IMAGES, skipSides); + } + + return result; +} + +nsIFrame* +nsGroupBoxFrame::GetCaptionBox(nsRect& aCaptionRect) +{ + // first child is our grouped area + nsIFrame* box = nsBox::GetChildXULBox(this); + + // no area fail. + if (!box) + return nullptr; + + // get the first child in the grouped area, that is the caption + box = nsBox::GetChildXULBox(box); + + // nothing in the area? fail + if (!box) + return nullptr; + + // now get the caption itself. It is in the caption frame. + nsIFrame* child = nsBox::GetChildXULBox(box); + + if (child) { + // convert to our coordinates. + nsRect parentRect(box->GetRect()); + aCaptionRect = child->GetRect(); + aCaptionRect.x += parentRect.x; + aCaptionRect.y += parentRect.y; + } + + return child; +} + +nsresult +nsGroupBoxFrame::GetXULBorderAndPadding(nsMargin& aBorderAndPadding) +{ + aBorderAndPadding.SizeTo(0,0,0,0); + return NS_OK; +} + diff --git a/layout/xul/nsIBoxObject.idl b/layout/xul/nsIBoxObject.idl new file mode 100644 index 000000000..64b1a46f8 --- /dev/null +++ b/layout/xul/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/nsIBrowserBoxObject.idl b/layout/xul/nsIBrowserBoxObject.idl new file mode 100644 index 000000000..f3eee9159 --- /dev/null +++ b/layout/xul/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 ContainerBoxObject. + */ + +[uuid(db436f2f-c656-4754-b0fa-99bc353bd63f)] +interface nsIBrowserBoxObject : nsIContainerBoxObject +{ +}; + diff --git a/layout/xul/nsIContainerBoxObject.idl b/layout/xul/nsIContainerBoxObject.idl new file mode 100644 index 000000000..f2eab0fbd --- /dev/null +++ b/layout/xul/nsIContainerBoxObject.idl @@ -0,0 +1,14 @@ +/* -*- 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" + +// DEPRECATED: This file exists for shim purposes only, +// see ContainerBoxObject.webidl + +[uuid(35d4c04b-3bd3-4375-92e2-a818b4b4acb6)] +interface nsIContainerBoxObject : nsISupports +{ +}; diff --git a/layout/xul/nsIListBoxObject.idl b/layout/xul/nsIListBoxObject.idl new file mode 100644 index 000000000..198f76919 --- /dev/null +++ b/layout/xul/nsIListBoxObject.idl @@ -0,0 +1,19 @@ +/* -*- 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" + +// DEPRECATED: see ListBoxObject.webidl + +interface nsIDOMElement; + +[uuid(AA9DEF4E-2E59-412d-A6DF-B76F52167795)] +interface nsIListBoxObject : nsISupports +{ + long getRowCount(); + + nsIDOMElement getItemAtIndex(in long index); + long getIndexOfItem(in nsIDOMElement item); +}; diff --git a/layout/xul/nsIMenuBoxObject.idl b/layout/xul/nsIMenuBoxObject.idl new file mode 100644 index 000000000..63c7811c4 --- /dev/null +++ b/layout/xul/nsIMenuBoxObject.idl @@ -0,0 +1,14 @@ +/* -*- 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" + +// DEPRECATED: This file exists for shim purposes only, +// see MenuBoxObject.webidl + +[uuid(689ebf3d-0184-450a-9bfa-5a26be0e7a8c)] +interface nsIMenuBoxObject : nsISupports +{ +}; diff --git a/layout/xul/nsIRootBox.h b/layout/xul/nsIRootBox.h new file mode 100644 index 000000000..9311f547e --- /dev/null +++ b/layout/xul/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/nsIScrollBoxObject.idl b/layout/xul/nsIScrollBoxObject.idl new file mode 100644 index 000000000..fc16f61b1 --- /dev/null +++ b/layout/xul/nsIScrollBoxObject.idl @@ -0,0 +1,12 @@ +/* -*- 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" + + +[uuid(56E2ADA8-4631-11d4-BA11-001083023C1E)] +interface nsIScrollBoxObject : nsISupports +{ +}; diff --git a/layout/xul/nsIScrollbarMediator.h b/layout/xul/nsIScrollbarMediator.h new file mode 100644 index 000000000..87b82e3d9 --- /dev/null +++ b/layout/xul/nsIScrollbarMediator.h @@ -0,0 +1,91 @@ +/* -*- 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" +#include "nsCoord.h" + +class nsScrollbarFrame; +class nsIFrame; + +class nsIScrollbarMediator : public nsQueryFrame +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsIScrollbarMediator) + + /** + * The aScrollbar argument denotes the scrollbar that's firing the notification. + * aScrollbar is never null. + * aDirection is either -1, 0, or 1. + */ + + /** + * When set to ENABLE_SNAP, additional scrolling will be performed after the + * scroll operation to maintain the constraints set by CSS Scroll snapping. + * The additional scrolling may include asynchronous smooth scrolls that + * continue to animate after the initial scroll position has been set. + */ + enum ScrollSnapMode { DISABLE_SNAP, ENABLE_SNAP }; + + /** + * One of the following three methods is called when the scrollbar's button is + * clicked. + * @note These methods might destroy the frame, pres shell, and other objects. + */ + virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection, + ScrollSnapMode aSnap = DISABLE_SNAP) = 0; + virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection, + ScrollSnapMode aSnap = DISABLE_SNAP) = 0; + virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection, + ScrollSnapMode aSnap = DISABLE_SNAP) = 0; + /** + * RepeatButtonScroll is called when the scrollbar's button is held down. When the + * button is first clicked the increment is set; RepeatButtonScroll adds this + * increment to the current position. + * @note This method might destroy the frame, pres shell, and other objects. + */ + virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) = 0; + /** + * aOldPos and aNewPos are scroll positions. + * The scroll positions start with zero at the left edge; implementors that want + * zero at the right edge for RTL content will need to adjust accordingly. + * (See ScrollFrameHelper::ThumbMoved in nsGfxScrollFrame.cpp.) + * @note This method might destroy the frame, pres shell, and other objects. + */ + virtual void ThumbMoved(nsScrollbarFrame* aScrollbar, + nscoord aOldPos, + nscoord aNewPos) = 0; + /** + * Called when the scroll bar thumb, slider, or any other component is + * released. + */ + virtual void ScrollbarReleased(nsScrollbarFrame* aScrollbar) = 0; + virtual void VisibilityChanged(bool aVisible) = 0; + + /** + * Obtain the frame for the horizontal or vertical scrollbar, or null + * if there is no such box. + */ + virtual nsIFrame* GetScrollbarBox(bool aVertical) = 0; + /** + * Show or hide scrollbars on 2 fingers touch. + * Subclasses should call their ScrollbarActivity's corresponding methods. + */ + virtual void ScrollbarActivityStarted() const = 0; + virtual void ScrollbarActivityStopped() const = 0; + + virtual bool IsScrollbarOnRight() const = 0; + + /** + * Returns true if the mediator is asking the scrollbar to suppress + * repainting itself on changes. + */ + virtual bool ShouldSuppressScrollbarRepaints() const = 0; +}; + +#endif + diff --git a/layout/xul/nsISliderListener.idl b/layout/xul/nsISliderListener.idl new file mode 100644 index 000000000..9605782f0 --- /dev/null +++ b/layout/xul/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/nsImageBoxFrame.cpp b/layout/xul/nsImageBoxFrame.cpp new file mode 100644 index 000000000..fd7c7becf --- /dev/null +++ b/layout/xul/nsImageBoxFrame.cpp @@ -0,0 +1,828 @@ +/* -*- 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 "nsRenderingContext.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsStyleUtil.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 "nsNameSpaceManager.h" +#include "nsTextFragment.h" +#include "nsIDOMHTMLMapElement.h" +#include "nsTransform2D.h" +#include "nsITheme.h" + +#include "nsIServiceManager.h" +#include "nsIURI.h" +#include "nsThreadUtils.h" +#include "nsDisplayList.h" +#include "ImageLayers.h" +#include "ImageContainer.h" +#include "nsIContent.h" + +#include "nsContentUtils.h" +#include "nsSerializationHelper.h" + +#include "mozilla/BasicEvents.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/Maybe.h" + +#define ONLOAD_CALLED_TOO_EARLY 1 + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; +using namespace mozilla::layers; + +class nsImageBoxFrameEvent : public Runnable +{ +public: + nsImageBoxFrameEvent(nsIContent *content, EventMessage message) + : mContent(content), mMessage(message) {} + + NS_IMETHOD Run() override; + +private: + nsCOMPtr<nsIContent> mContent; + EventMessage mMessage; +}; + +NS_IMETHODIMP +nsImageBoxFrameEvent::Run() +{ + nsIPresShell *pres_shell = mContent->OwnerDoc()->GetShell(); + if (!pres_shell) { + return NS_OK; + } + + RefPtr<nsPresContext> pres_context = pres_shell->GetPresContext(); + if (!pres_context) { + return NS_OK; + } + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent event(true, mMessage); + + event.mFlags.mBubbles = false; + EventDispatcher::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, EventMessage aMessage) +{ + NS_ASSERTION(aMessage == eLoad || aMessage == eLoadError, + "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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsImageBoxFrame) + +nsresult +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(nsStyleContext* aContext): + nsLeafBoxFrame(aContext), + mIntrinsicSize(0,0), + mLoadFlags(nsIRequest::LOAD_NORMAL), + mRequestRegistered(false), + mUseSrcAttr(false), + mSuppressStyleCheck(false) +{ + MarkIntrinsicISizesDirty(); +} + +nsImageBoxFrame::~nsImageBoxFrame() +{ +} + + +/* virtual */ void +nsImageBoxFrame::MarkIntrinsicISizesDirty() +{ + SizeNeedsRecalc(mImageSize); + nsLeafBoxFrame::MarkIntrinsicISizesDirty(); +} + +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, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + if (!mListener) { + RefPtr<nsImageBoxListener> listener = new nsImageBoxListener(); + listener->SetFrame(this); + mListener = listener.forget(); + } + + mSuppressStyleCheck = true; + nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow); + mSuppressStyleCheck = false; + + UpdateLoadFlags(); + UpdateImage(); +} + +void +nsImageBoxFrame::UpdateImage() +{ + nsPresContext* presContext = PresContext(); + + RefPtr<imgRequestProxy> oldImageRequest = mImageRequest; + + 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->GetComposedDoc(); + if (doc) { + // Use the serialized loadingPrincipal from the image element. Fall back + // to mContent's principal (SystemPrincipal) if not available. + nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE; + nsCOMPtr<nsIPrincipal> loadingPrincipal = mContent->NodePrincipal(); + nsAutoString imageLoadingPrincipal; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::loadingprincipal, + imageLoadingPrincipal); + if (!imageLoadingPrincipal.IsEmpty()) { + nsCOMPtr<nsISupports> serializedPrincipal; + NS_DeserializeObject(NS_ConvertUTF16toUTF8(imageLoadingPrincipal), + getter_AddRefs(serializedPrincipal)); + loadingPrincipal = do_QueryInterface(serializedPrincipal); + + if (loadingPrincipal) { + // Set the content policy type to TYPE_INTERNAL_IMAGE_FAVICON for + // indicating it's a favicon loading. + contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON; + } else { + // Fallback if the deserialization is failed. + loadingPrincipal = mContent->NodePrincipal(); + } + } + + nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI(); + nsCOMPtr<nsIURI> uri; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), + src, + doc, + baseURI); + if (uri) { + nsresult rv = nsContentUtils::LoadImage(uri, mContent, doc, loadingPrincipal, + doc->GetDocumentURI(), doc->GetReferrerPolicy(), + mListener, mLoadFlags, + EmptyString(), getter_AddRefs(mImageRequest), + contentPolicyType); + + if (NS_SUCCEEDED(rv) && 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(); + } + + // Do this _after_ locking the new image in case they are the same image. + if (oldImageRequest) { + oldImageRequest->UnlockImage(); + } +} + +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; + + uint32_t clipFlags = + nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ? + 0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT; + + DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox + clip(aBuilder, this, clipFlags); + + nsDisplayList list; + list.AppendNewToTop( + new (aBuilder) nsDisplayXULImage(aBuilder, this)); + + CreateOwnLayerIfNeeded(aBuilder, &list); + + aLists.Content()->AppendToTop(&list); +} + +DrawResult +nsImageBoxFrame::PaintImage(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt, + uint32_t aFlags) +{ + nsRect constraintRect; + GetXULClientRect(constraintRect); + + constraintRect += aPt; + + if (!mImageRequest) { + // This probably means we're drawn by a native theme. + return DrawResult::SUCCESS; + } + + // don't draw if the image is not dirty + // XXX(seth): Can this actually happen anymore? + nsRect dirty; + if (!dirty.IntersectRect(aDirtyRect, constraintRect)) { + return DrawResult::TEMPORARY_ERROR; + } + + // Don't draw if the image's size isn't available. + uint32_t imgStatus; + if (!NS_SUCCEEDED(mImageRequest->GetImageStatus(&imgStatus)) || + !(imgStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) { + return DrawResult::NOT_READY; + } + + nsCOMPtr<imgIContainer> imgCon; + mImageRequest->GetImage(getter_AddRefs(imgCon)); + + if (!imgCon) { + return DrawResult::NOT_READY; + } + + bool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0); + + Maybe<nsPoint> anchorPoint; + nsRect dest; + if (!mUseSrcAttr) { + // Our image (if we have one) is coming from the CSS property + // 'list-style-image' (combined with '-moz-image-region'). For now, ignore + // 'object-fit' & 'object-position' in this case, and just fill our rect. + // XXXdholbert Should we even honor these properties in this case? They only + // apply to replaced elements, and I'm not sure we count as a replaced + // element when our image data is determined by CSS. + dest = constraintRect; + } else { + // Determine dest rect based on intrinsic size & ratio, along with + // 'object-fit' & 'object-position' properties: + IntrinsicSize intrinsicSize; + nsSize intrinsicRatio; + if (mIntrinsicSize.width > 0 && mIntrinsicSize.height > 0) { + // Image has a valid size; use it as intrinsic size & ratio. + intrinsicSize.width.SetCoordValue(mIntrinsicSize.width); + intrinsicSize.height.SetCoordValue(mIntrinsicSize.height); + intrinsicRatio = mIntrinsicSize; + } else { + // Image doesn't have a (valid) intrinsic size. + // Try to look up intrinsic ratio and use that at least. + imgCon->GetIntrinsicRatio(&intrinsicRatio); + } + anchorPoint.emplace(); + dest = nsLayoutUtils::ComputeObjectDestRect(constraintRect, + intrinsicSize, + intrinsicRatio, + StylePosition(), + anchorPoint.ptr()); + } + + + return nsLayoutUtils::DrawSingleImage( + *aRenderingContext.ThebesContext(), + PresContext(), imgCon, + nsLayoutUtils::GetSamplingFilterForFrame(this), + dest, dirty, nullptr, aFlags, + anchorPoint.ptrOr(nullptr), + 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; + + DrawResult result = static_cast<nsImageBoxFrame*>(mFrame)-> + PaintImage(*aCtx, mVisibleRect, ToReferenceFrame(), flags); + + nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); +} + +nsDisplayItemGeometry* +nsDisplayXULImage::AllocateGeometry(nsDisplayListBuilder* aBuilder) +{ + return new nsDisplayItemGenericImageGeometry(this, aBuilder); +} + +void +nsDisplayXULImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) +{ + auto boxFrame = static_cast<nsImageBoxFrame*>(mFrame); + auto geometry = + static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + boxFrame->mImageRequest && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + + nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); +} + +bool +nsDisplayXULImage::CanOptimizeToImageLayer(LayerManager* aManager, + nsDisplayListBuilder* aBuilder) +{ + nsImageBoxFrame* imageFrame = static_cast<nsImageBoxFrame*>(mFrame); + if (!imageFrame->CanOptimizeToImageLayer()) { + return false; + } + + return nsDisplayImageContainer::CanOptimizeToImageLayer(aManager, aBuilder); +} + +already_AddRefed<imgIContainer> +nsDisplayXULImage::GetImage() +{ + nsImageBoxFrame* imageFrame = static_cast<nsImageBoxFrame*>(mFrame); + if (!imageFrame->mImageRequest) { + return nullptr; + } + + nsCOMPtr<imgIContainer> imgCon; + imageFrame->mImageRequest->GetImage(getter_AddRefs(imgCon)); + + return imgCon.forget(); +} + +nsRect +nsDisplayXULImage::GetDestRect() +{ + nsImageBoxFrame* imageFrame = static_cast<nsImageBoxFrame*>(mFrame); + + nsRect clientRect; + imageFrame->GetXULClientRect(clientRect); + + return clientRect + ToReferenceFrame(); +} + +bool +nsImageBoxFrame::CanOptimizeToImageLayer() +{ + bool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0); + if (hasSubRect) { + return false; + } + return true; +} + +// +// 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::GetXULPrefSize(nsBoxLayoutState& aState) +{ + nsSize size(0,0); + DISPLAY_PREF_SIZE(this, size); + if (DoesNeedRecalc(mImageSize)) + GetImageSize(); + + if (!mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0)) + size = mSubRect.Size(); + else + size = mImageSize; + + nsSize intrinsicSize = size; + + nsMargin borderPadding(0,0,0,0); + GetXULBorderAndPadding(borderPadding); + size.width += borderPadding.LeftRight(); + size.height += borderPadding.TopBottom(); + + bool widthSet, heightSet; + nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet); + NS_ASSERTION(size.width != NS_INTRINSICSIZE && size.height != NS_INTRINSICSIZE, + "non-intrinsic size expected"); + + nsSize minSize = GetXULMinSize(aState); + nsSize maxSize = GetXULMaxSize(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::GetXULMinSize(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::AddXULMinSize(aState, this, size, widthSet, heightSet); + return size; +} + +nscoord +nsImageBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aState) +{ + return GetXULPrefSize(aState).height; +} + +nsIAtom* +nsImageBoxFrame::GetType() const +{ + return nsGkAtoms::imageBoxFrame; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +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 OnSizeAvailable(aRequest, image); + } + + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + return OnDecodeComplete(aRequest); + } + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + uint32_t imgStatus; + aRequest->GetImageStatus(&imgStatus); + nsresult status = + imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; + return OnLoadComplete(aRequest, status); + } + + if (aType == imgINotificationObserver::IS_ANIMATED) { + return OnImageIsAnimated(aRequest); + } + + if (aType == imgINotificationObserver::FRAME_UPDATE) { + return OnFrameUpdate(aRequest); + } + + return NS_OK; +} + +nsresult +nsImageBoxFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage) +{ + NS_ENSURE_ARG_POINTER(aImage); + + // 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(); + + nscoord w, h; + aImage->GetWidth(&w); + aImage->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::OnDecodeComplete(imgIRequest* aRequest) +{ + nsBoxLayoutState state(PresContext()); + this->XULRedraw(state); + return NS_OK; +} + +nsresult +nsImageBoxFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) +{ + if (NS_SUCCEEDED(aStatus)) { + // Fire an onload DOM event. + FireImageDOMEvent(mContent, eLoad); + } else { + // Fire an onerror DOM event. + mIntrinsicSize.SizeTo(0, 0); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + FireImageDOMEvent(mContent, eLoadError); + } + + 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::OnFrameUpdate(imgIRequest* aRequest) +{ + if ((0 == mRect.width) || (0 == mRect.height)) { + return NS_OK; + } + + InvalidateLayer(nsDisplayItem::TYPE_XUL_IMAGE); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(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); +} + +NS_IMETHODIMP +nsImageBoxListener::BlockOnload(imgIRequest *aRequest) +{ + if (mFrame && mFrame->GetContent() && mFrame->GetContent()->GetUncomposedDoc()) { + mFrame->GetContent()->GetUncomposedDoc()->BlockOnload(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsImageBoxListener::UnblockOnload(imgIRequest *aRequest) +{ + if (mFrame && mFrame->GetContent() && mFrame->GetContent()->GetUncomposedDoc()) { + mFrame->GetContent()->GetUncomposedDoc()->UnblockOnload(false); + } + + return NS_OK; +} diff --git a/layout/xul/nsImageBoxFrame.h b/layout/xul/nsImageBoxFrame.h new file mode 100644 index 000000000..7faccccae --- /dev/null +++ b/layout/xul/nsImageBoxFrame.h @@ -0,0 +1,163 @@ +/* -*- 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 final : public imgINotificationObserver, + public imgIOnloadBlocker +{ +public: + nsImageBoxListener(); + + NS_DECL_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + NS_DECL_IMGIONLOADBLOCKER + + void SetFrame(nsImageBoxFrame *frame) { mFrame = frame; } + +private: + virtual ~nsImageBoxListener(); + + nsImageBoxFrame *mFrame; +}; + +class nsImageBoxFrame final : public nsLeafBoxFrame +{ +public: + typedef mozilla::image::DrawResult DrawResult; + typedef mozilla::layers::ImageContainer ImageContainer; + typedef mozilla::layers::LayerManager LayerManager; + + friend class nsDisplayXULImage; + NS_DECL_FRAMEARENA_HELPERS + + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override; + virtual void MarkIntrinsicISizesDirty() override; + + nsresult Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData); + + friend nsIFrame* NS_NewImageBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* asPrevInFlow) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual nsIAtom* GetType() const override; +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const 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) override; + + virtual ~nsImageBoxFrame(); + + DrawResult PaintImage(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, uint32_t aFlags); + + bool CanOptimizeToImageLayer(); + +protected: + explicit nsImageBoxFrame(nsStyleContext* aContext); + + virtual void GetImageSize(); + +private: + nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage); + nsresult OnDecodeComplete(imgIRequest* aRequest); + nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus); + nsresult OnImageIsAnimated(imgIRequest* aRequest); + nsresult OnFrameUpdate(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; + + RefPtr<imgRequestProxy> mImageRequest; + nsCOMPtr<imgINotificationObserver> mListener; + + int32_t mLoadFlags; + + // Boolean variable to determine if the current image request has been + // registered with the refresh driver. + bool mRequestRegistered; + + bool mUseSrcAttr; ///< Whether or not the image src comes from an attribute. + bool mSuppressStyleCheck; +}; // 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 bool CanOptimizeToImageLayer(LayerManager* aManager, + nsDisplayListBuilder* aBuilder) override; + virtual already_AddRefed<imgIContainer> GetImage() override; + virtual nsRect GetDestRect() override; + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override + { + *aSnap = true; + return nsRect(ToReferenceFrame(), Frame()->GetSize()); + } + virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override; + virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) override; + // Doesn't handle HitTest because nsLeafBoxFrame already creates an + // event receiver for us + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + NS_DISPLAY_DECL_NAME("XULImage", TYPE_XUL_IMAGE) +}; + +#endif /* nsImageBoxFrame_h___ */ diff --git a/layout/xul/nsLeafBoxFrame.cpp b/layout/xul/nsLeafBoxFrame.cpp new file mode 100644 index 000000000..6d1783c11 --- /dev/null +++ b/layout/xul/nsLeafBoxFrame.cpp @@ -0,0 +1,390 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsBoxLayoutState.h" +#include "nsWidgetsCID.h" +#include "nsViewManager.h" +#include "nsContainerFrame.h" +#include "nsDisplayList.h" +#include <algorithm> + +using namespace mozilla; + +// +// NS_NewLeafBoxFrame +// +// Creates a new Toolbar frame and returns it +// +nsIFrame* +NS_NewLeafBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsLeafBoxFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsLeafBoxFrame) + +nsLeafBoxFrame::nsLeafBoxFrame(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, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsLeafFrame::Init(aContent, aParent, aPrevInFlow); + + if (GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER) { + AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); + } + + UpdateMouseThrough(); +} + +nsresult +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::GetMinISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + nsBoxLayoutState state(PresContext(), aRenderingContext); + + WritingMode wm = GetWritingMode(); + LogicalSize minSize(wm, GetXULMinSize(state)); + + // GetXULMinSize returns border-box size, and we want to return content + // inline-size. Since Reflow uses the reflow state's border and padding, we + // actually just want to subtract what GetXULMinSize added, which is the + // result of GetXULBorderAndPadding. + nsMargin bp; + GetXULBorderAndPadding(bp); + + result = minSize.ISize(wm) - LogicalMargin(wm, bp).IStartEnd(wm); + + return result; +} + +/* virtual */ nscoord +nsLeafBoxFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + nsBoxLayoutState state(PresContext(), aRenderingContext); + + WritingMode wm = GetWritingMode(); + LogicalSize prefSize(wm, GetXULPrefSize(state)); + + // GetXULPrefSize returns border-box size, and we want to return content + // inline-size. Since Reflow uses the reflow state's border and padding, we + // actually just want to subtract what GetXULPrefSize added, which is the + // result of GetXULBorderAndPadding. + nsMargin bp; + GetXULBorderAndPadding(bp); + + result = prefSize.ISize(wm) - LogicalMargin(wm, bp).IStartEnd(wm); + + return result; +} + +nscoord +nsLeafBoxFrame::GetIntrinsicISize() +{ + // No intrinsic width + return 0; +} + +LogicalSize +nsLeafBoxFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + // Important: NOT calling our direct superclass here! + return nsFrame::ComputeAutoSize(aRenderingContext, aWM, + aCBSize, aAvailableISize, + aMargin, aBorder, aPadding, aFlags); +} + +void +nsLeafBoxFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + 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. + + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsLeafBoxFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + + NS_ASSERTION(aReflowInput.ComputedWidth() >=0 && + aReflowInput.ComputedHeight() >= 0, "Computed Size < 0"); + +#ifdef DO_NOISY_REFLOW + printf("\n-------------Starting LeafBoxFrame Reflow ----------------------------\n"); + printf("%p ** nsLBF::Reflow %d R: ", this, myCounter++); + switch (aReflowInput.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", aReflowInput.reason);break; + } + + printSize("AW", aReflowInput.AvailableWidth()); + printSize("AH", aReflowInput.AvailableHeight()); + printSize("CW", aReflowInput.ComputedWidth()); + printSize("CH", aReflowInput.ComputedHeight()); + + printf(" *\n"); + +#endif + + aStatus = NS_FRAME_COMPLETE; + + // create the layout state + nsBoxLayoutState state(aPresContext, aReflowInput.mRenderingContext); + + nsSize computedSize(aReflowInput.ComputedWidth(),aReflowInput.ComputedHeight()); + + nsMargin m; + m = aReflowInput.ComputedPhysicalBorderPadding(); + + //GetXULBorderAndPadding(m); + + // this happens sometimes. So lets handle it gracefully. + if (aReflowInput.ComputedHeight() == 0) { + nsSize minSize = GetXULMinSize(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 = GetXULPrefSize(state); + nsSize minSize = GetXULMinSize(state); + nsSize maxSize = GetXULMaxSize(state); + prefSize = BoundsCheck(minSize, prefSize, maxSize); + } + + // get our desiredSize + if (aReflowInput.ComputedWidth() == NS_INTRINSICSIZE) { + computedSize.width = prefSize.width; + } else { + computedSize.width += m.left + m.right; + } + + if (aReflowInput.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 > aReflowInput.ComputedMaxWidth()) + computedSize.width = aReflowInput.ComputedMaxWidth(); + + if (computedSize.width < aReflowInput.ComputedMinWidth()) + computedSize.width = aReflowInput.ComputedMinWidth(); + + // 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, + aReflowInput.ComputedMinHeight(), + aReflowInput.ComputedMaxHeight()); + computedSize.height += m.TopBottom(); + + nsRect r(mRect.x, mRect.y, computedSize.width, computedSize.height); + + SetXULBounds(state, r); + + // layout our children + XULLayout(state); + + // ok our child could have gotten bigger. So lets get its bounds + aDesiredSize.Width() = mRect.width; + aDesiredSize.Height() = mRect.height; + aDesiredSize.SetBlockStartAscent(GetXULBoxAscent(state)); + + // the overflow rect is set in SetXULBounds() 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 +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsLeafBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("LeafBox"), aResult); +} +#endif + +nsIAtom* +nsLeafBoxFrame::GetType() const +{ + return nsGkAtoms::leafBoxFrame; +} + +nsresult +nsLeafBoxFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) +{ + MarkIntrinsicISizesDirty(); + return nsLeafFrame::CharacterDataChanged(aInfo); +} + +/* virtual */ nsSize +nsLeafBoxFrame::GetXULPrefSize(nsBoxLayoutState& aState) +{ + return nsBox::GetXULPrefSize(aState); +} + +/* virtual */ nsSize +nsLeafBoxFrame::GetXULMinSize(nsBoxLayoutState& aState) +{ + return nsBox::GetXULMinSize(aState); +} + +/* virtual */ nsSize +nsLeafBoxFrame::GetXULMaxSize(nsBoxLayoutState& aState) +{ + return nsBox::GetXULMaxSize(aState); +} + +/* virtual */ nscoord +nsLeafBoxFrame::GetXULFlex() +{ + return nsBox::GetXULFlex(); +} + +/* virtual */ nscoord +nsLeafBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aState) +{ + return nsBox::GetXULBoxAscent(aState); +} + +/* virtual */ void +nsLeafBoxFrame::MarkIntrinsicISizesDirty() +{ + // Don't call base class method, since everything it does is within an + // IsXULBoxWrapped check. +} + +NS_IMETHODIMP +nsLeafBoxFrame::DoXULLayout(nsBoxLayoutState& aState) +{ + return nsBox::DoXULLayout(aState); +} diff --git a/layout/xul/nsLeafBoxFrame.h b/layout/xul/nsLeafBoxFrame.h new file mode 100644 index 000000000..8aea598c8 --- /dev/null +++ b/layout/xul/nsLeafBoxFrame.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 nsLeafBoxFrame_h___ +#define nsLeafBoxFrame_h___ + +#include "mozilla/Attributes.h" +#include "nsLeafFrame.h" +#include "nsBox.h" + +class nsLeafBoxFrame : public nsLeafFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewLeafBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aState) override; + virtual nsSize GetXULMinSize(nsBoxLayoutState& aState) override; + virtual nsSize GetXULMaxSize(nsBoxLayoutState& aState) override; + virtual nscoord GetXULFlex() override; + virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aState) override; + + virtual nsIAtom* GetType() const override; + virtual bool IsFrameOfType(uint32_t aFlags) const 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_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + // nsIHTMLReflow overrides + + virtual void MarkIntrinsicISizesDirty() override; + virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override; + virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override; + + // Our auto size is that provided by nsFrame, not nsLeafFrame + virtual mozilla::LogicalSize + ComputeAutoSize(nsRenderingContext* aRenderingContext, + mozilla::WritingMode aWM, + const mozilla::LogicalSize& aCBSize, + nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorder, + const mozilla::LogicalSize& aPadding, + ComputeSizeFlags aFlags) override; + + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + virtual nsresult CharacterDataChanged(CharacterDataChangeInfo* aInfo) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* asPrevInFlow) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual bool ComputesOwnOverflowArea() override { return false; } + +protected: + + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aState) override; + +#ifdef DEBUG_LAYOUT + virtual void GetBoxName(nsAutoString& aName) override; +#endif + + virtual nscoord GetIntrinsicISize() override; + + explicit nsLeafBoxFrame(nsStyleContext* aContext); + +private: + + void UpdateMouseThrough(); + + +}; // class nsLeafBoxFrame + +#endif /* nsLeafBoxFrame_h___ */ diff --git a/layout/xul/nsListBoxBodyFrame.cpp b/layout/xul/nsListBoxBodyFrame.cpp new file mode 100644 index 000000000..8c4a5e2fd --- /dev/null +++ b/layout/xul/nsListBoxBodyFrame.cpp @@ -0,0 +1,1535 @@ +/* -*- 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 "mozilla/MathAlgorithms.h" +#include "nsCOMPtr.h" +#include "nsGridRowGroupLayout.h" +#include "nsIServiceManager.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsNameSpaceManager.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 "mozilla/StyleSetHandle.h" +#include "mozilla/StyleSetHandleInlines.h" +#include "nsPIBoxObject.h" +#include "nsLayoutUtils.h" +#include "nsPIListBoxObject.h" +#include "nsContentUtils.h" +#include "ChildIterator.h" +#include "nsRenderingContext.h" +#include "prtime.h" +#include <algorithm> + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +/////////////// 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 final : public nsITimerCallback +{ +private: + virtual ~nsListScrollSmoother(); + +public: + NS_DECL_ISUPPORTS + + explicit nsListScrollSmoother(nsListBoxBodyFrame* aOuter); + + // 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_ISUPPORTS(nsListScrollSmoother, nsITimerCallback) + +/////////////// nsListBoxBodyFrame ////////////////// + +nsListBoxBodyFrame::nsListBoxBodyFrame(nsStyleContext* aContext, + nsBoxLayout* aLayoutManager) + : nsBoxFrame(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, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + // Don't call nsLayoutUtils::GetScrollableFrameFor since we are not its + // scrollframe child yet. + nsIScrollableFrame* scrollFrame = do_QueryFrame(aParent); + if (scrollFrame) { + nsIFrame* verticalScrollbar = scrollFrame->GetScrollbarBox(true); + nsScrollbarFrame* scrollbarFrame = do_QueryFrame(verticalScrollbar); + if (scrollbarFrame) { + scrollbarFrame->SetScrollbarMediatorContent(GetContent()); + } + } + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + 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); +} + +nsresult +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::MarkIntrinsicISizesDirty() +{ + mStringWidth = -1; + nsBoxFrame::MarkIntrinsicISizesDirty(); +} + +/////////// nsBox /////////////// + +NS_IMETHODIMP +nsListBoxBodyFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState) +{ + if (mScrolling) + aBoxLayoutState.SetPaintingDisabled(true); + + nsresult rv = nsBoxFrame::DoXULLayout(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->GetXULPrefSize(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::GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) +{ + nsSize result(0, 0); + if (nsContentUtils::HasNonEmptyAttr(GetContent(), kNameSpaceID_None, + nsGkAtoms::sizemode)) { + result = GetXULPrefSize(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::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) +{ + nsSize pref = nsBoxFrame::GetXULPrefSize(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 /////////////// + +void +nsListBoxBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap) +{ + // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored + MOZ_ASSERT(aScrollbar != nullptr); + aScrollbar->SetIncrementToPage(aDirection); + nsWeakFrame weakFrame(this); + int32_t newPos = aScrollbar->MoveToNewPosition(); + if (!weakFrame.IsAlive()) { + return; + } + UpdateIndex(newPos); +} + +void +nsListBoxBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap) +{ + // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored + MOZ_ASSERT(aScrollbar != nullptr); + aScrollbar->SetIncrementToWhole(aDirection); + nsWeakFrame weakFrame(this); + int32_t newPos = aScrollbar->MoveToNewPosition(); + if (!weakFrame.IsAlive()) { + return; + } + UpdateIndex(newPos); +} + +void +nsListBoxBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap) +{ + // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored + MOZ_ASSERT(aScrollbar != nullptr); + aScrollbar->SetIncrementToLine(aDirection); + nsWeakFrame weakFrame(this); + int32_t newPos = aScrollbar->MoveToNewPosition(); + if (!weakFrame.IsAlive()) { + return; + } + UpdateIndex(newPos); +} + +void +nsListBoxBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar) +{ + nsWeakFrame weakFrame(this); + int32_t newPos = aScrollbar->MoveToNewPosition(); + if (!weakFrame.IsAlive()) { + return; + } + UpdateIndex(newPos); +} + +int32_t +nsListBoxBodyFrame::ToRowIndex(nscoord aPos) const +{ + return NS_roundf(float(std::max(aPos, 0)) / mRowHeight); +} + +void +nsListBoxBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar, + nscoord aOldPos, + nscoord aNewPos) +{ + if (mScrolling || mRowHeight == 0) + return; + + int32_t newIndex = ToRowIndex(aNewPos); + if (newIndex == mCurrentIndex) { + return; + } + int32_t rowDelta = newIndex - mCurrentIndex; + + 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() || Abs(rowDelta)*mTimePerRow > USER_TIME_THRESHOLD) { + + smoother->Stop(); + + smoother->mDelta = rowDelta; + + smoother->Start(); + + return; + } + + smoother->Stop(); + + mCurrentIndex = newIndex; + smoother->mDelta = 0; + + if (mCurrentIndex < 0) { + mCurrentIndex = 0; + return; + } + InternalPositionChanged(rowDelta < 0, Abs(rowDelta)); +} + +void +nsListBoxBodyFrame::VisibilityChanged(bool aVisible) +{ + if (mRowHeight == 0) + return; + + int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight); + if (lastPageTopRow < 0) + lastPageTopRow = 0; + int32_t delta = mCurrentIndex - lastPageTopRow; + if (delta > 0) { + mCurrentIndex = lastPageTopRow; + InternalPositionChanged(true, delta); + } +} + +nsIFrame* +nsListBoxBodyFrame::GetScrollbarBox(bool aVertical) +{ + nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this); + return scrollFrame ? scrollFrame->GetScrollbarBox(true) : nullptr; +} + +void +nsListBoxBodyFrame::UpdateIndex(int32_t aNewPos) +{ + int32_t newIndex = ToRowIndex(nsPresContext::CSSPixelsToAppUnits(aNewPos)); + if (newIndex == mCurrentIndex) { + return; + } + bool up = newIndex < mCurrentIndex; + int32_t indexDelta = Abs(newIndex - mCurrentIndex); + mCurrentIndex = newIndex; + InternalPositionChanged(up, indexDelta); +} + +///////////// 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; +} + +///////// ListBoxObject /////////////// + +int32_t +nsListBoxBodyFrame::GetNumberOfVisibleRows() +{ + return mRowHeight ? GetAvailableHeight() / mRowHeight : 0; +} + +int32_t +nsListBoxBodyFrame::GetIndexOfFirstVisibleRow() +{ + return mCurrentIndex; +} + +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 = GetIndexOfFirstVisibleRow(), + visibleRows = GetNumberOfVisibleRows(); + + 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)); + + FlattenedChildIterator iter(mContent); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + // we hit a list row, count it + if (child->IsXULElement(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; + FlattenedChildIterator iter(mContent); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + // we hit a list row, check if it is the one we are looking for + if (child->IsXULElement(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::ComputeIntrinsicISize(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) { + RefPtr<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(); + + FlattenedChildIterator iter(mContent); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + if (child->IsXULElement(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); + } + } + + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForStyleContext(styleContext); + + nscoord textWidth = + nsLayoutUtils::AppUnitWidthOfStringBidi(value, this, *fm, + *rendContext); + textWidth += width; + + if (textWidth > largestWidth) + largestWidth = textWidth; + } + } + } + } + + mStringWidth = largestWidth; + return mStringWidth; +} + +void +nsListBoxBodyFrame::ComputeTotalRowCount() +{ + mRowCount = 0; + FlattenedChildIterator iter(mContent); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + if (child->IsXULElement(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->GetComposedDoc()->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) +{ + RefPtr<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< RefPtr<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; + + RefPtr<nsPresContext> presContext(PresContext()); + nsBoxLayoutState state(presContext); + + // begin timing how long it takes to scroll a row + PRTime start = PR_Now(); + + nsWeakFrame weakThis(this); + mContent->GetComposedDoc()->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; + GetXULClientRect(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->IsXULElement(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->IsXULBoxFrame() ? mTopFrame : nullptr; + } + + // top frame was cleared out + mTopFrame = GetFirstFrame(); + mBottomFrame = mTopFrame; + + if (mTopFrame && mRowsToPrepend <= 0) { + return mTopFrame->IsXULBoxFrame() ? 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->IsXULBoxFrame() ? 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(this, nullptr, startContent, &topFrame, isAppend); + mTopFrame = topFrame; + if (mTopFrame) { + if (aCreated) + *aCreated = true; + + mBottomFrame = mTopFrame; + + return mTopFrame->IsXULBoxFrame() ? 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(this, prevFrame, nextContent, + &result, isAppend); + + 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->IsXULBoxFrame() || result->GetParent() == this, + "returning frame that is not in childlist"); + + return result->IsXULBoxFrame() ? 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(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 + nsIContent* lastChild = nullptr; + FlattenedChildIterator iter(mContent); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + lastChild = child; + } + + if (lastChild) { + 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; + FlattenedChildIterator iter(mContent); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + if (child->IsXULElement(nsGkAtoms::listitem)) { + ++itemsFound; + if (itemsFound-1 == aIndex) { + *aContent = child; + NS_IF_ADDREF(*aContent); + return; + } + } + } +} + +void +nsListBoxBodyFrame::GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, int32_t& aSiblingIndex) +{ + *aContent = nullptr; + aSiblingIndex = -1; + nsIContent *prevKid = nullptr; + FlattenedChildIterator iter(mContent); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + if (child->IsXULElement(nsGkAtoms::listitem)) { + ++aSiblingIndex; + if (prevKid == aListItem) { + *aContent = child; + NS_IF_ADDREF(*aContent); + return; + } + } + prevKid = child; + } + + 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); + } +#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(); + return new (aPresShell) nsListBoxBodyFrame(aContext, layout); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsListBoxBodyFrame) diff --git a/layout/xul/nsListBoxBodyFrame.h b/layout/xul/nsListBoxBodyFrame.h new file mode 100644 index 000000000..57bbf0257 --- /dev/null +++ b/layout/xul/nsListBoxBodyFrame.h @@ -0,0 +1,217 @@ +/* -*- 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 "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 final : public nsBoxFrame, + public nsIScrollbarMediator, + public nsIReflowCallback +{ + nsListBoxBodyFrame(nsStyleContext* aContext, + nsBoxLayout* aLayoutManager); + virtual ~nsListBoxBodyFrame(); + +public: + NS_DECL_QUERYFRAME_TARGET(nsListBoxBodyFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // non-virtual ListBoxObject + int32_t GetNumberOfVisibleRows(); + int32_t GetIndexOfFirstVisibleRow(); + 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, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType) override; + + // nsIScrollbarMediator + virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode snapMode + = nsIScrollbarMediator::DISABLE_SNAP) override; + virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode snapMode + = nsIScrollbarMediator::DISABLE_SNAP) override; + virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode snapMode + = nsIScrollbarMediator::DISABLE_SNAP) override; + virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) override; + virtual void ThumbMoved(nsScrollbarFrame* aScrollbar, + int32_t aOldPos, + int32_t aNewPos) override; + virtual void ScrollbarReleased(nsScrollbarFrame* aScrollbar) override {} + virtual void VisibilityChanged(bool aVisible) override; + virtual nsIFrame* GetScrollbarBox(bool aVertical) override; + virtual void ScrollbarActivityStarted() const override {} + virtual void ScrollbarActivityStopped() const override {} + virtual bool IsScrollbarOnRight() const override { + return (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR); + } + virtual bool ShouldSuppressScrollbarRepaints() const override { + return false; + } + + + // nsIReflowCallback + virtual bool ReflowFinished() override; + virtual void ReflowCallbackCanceled() override; + + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override; + virtual void MarkIntrinsicISizesDirty() override; + + virtual nsSize GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override; + + // size calculation + int32_t GetRowCount(); + int32_t GetRowHeightAppUnits() { return mRowHeight; } + int32_t GetFixedRowSize(); + void SetRowHeight(nscoord aRowHeight); + nscoord GetYPosition(); + nscoord GetAvailableHeight(); + nscoord ComputeIntrinsicISize(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); + // Update the scroll index given a position, in CSS pixels + void UpdateIndex(int32_t aNewPos); + + // 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(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() override; + + virtual bool ComputesOwnOverflowArea() override { return true; } + +protected: + class nsPositionChangedEvent; + friend class nsPositionChangedEvent; + + class nsPositionChangedEvent : public mozilla::Runnable + { + public: + nsPositionChangedEvent(nsListBoxBodyFrame* aFrame, + bool aUp, int32_t aDelta) : + mFrame(aFrame), mUp(aUp), mDelta(aDelta) + {} + + NS_IMETHOD Run() 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(); + int32_t ToRowIndex(nscoord aPos) const; + void RemoveChildFrame(nsBoxLayoutState &aState, nsIFrame *aChild); + + nsTArray< RefPtr<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/nsListBoxLayout.cpp b/layout/xul/nsListBoxLayout.cpp new file mode 100644 index 000000000..a2f915191 --- /dev/null +++ b/layout/xul/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 "mozilla/dom/NameSpaceConstants.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" + +nsListBoxLayout::nsListBoxLayout() : nsGridRowGroupLayout() +{ +} + +////////// nsBoxLayout ////////////// + +nsSize +nsListBoxLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize pref = nsGridRowGroupLayout::GetXULPrefSize(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->ComputeIntrinsicISize(aBoxLayoutState); + if (width > pref.width) + pref.width = width; + } + } + return pref; +} + +nsSize +nsListBoxLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize minSize = nsGridRowGroupLayout::GetXULMinSize(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->ComputeIntrinsicISize(aBoxLayoutState); + if (width > minSize.width) + minSize.width = width; + } + } + return minSize; +} + +nsSize +nsListBoxLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) +{ + nsSize maxSize = nsGridRowGroupLayout::GetXULMaxSize(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::XULLayout(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->GetXULClientRect(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 = nsBox::GetChildXULBox(body); + + // 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->GetXULMargin(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->GetXULPrefSize(aState); + body->SetRowHeight(size.height); + + childRect.height = rowHeight; + + childRect.Deflate(margin); + box->SetXULBounds(aState, childRect); + box->XULLayout(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->SetXULBounds(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 = nsBox::GetNextXULBox(box); + } + + // 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->XULRedraw(aState); + } + + return NS_OK; +} + +// Creation Routines /////////////////////////////////////////////////////////////////////// + +already_AddRefed<nsBoxLayout> NS_NewListBoxLayout() +{ + RefPtr<nsBoxLayout> layout = new nsListBoxLayout(); + return layout.forget(); +} diff --git a/layout/xul/nsListBoxLayout.h b/layout/xul/nsListBoxLayout.h new file mode 100644 index 000000000..d1f978843 --- /dev/null +++ b/layout/xul/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 XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) override; + virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + +protected: + NS_IMETHOD LayoutInternal(nsIFrame* aBox, nsBoxLayoutState& aState); +}; + +#endif + diff --git a/layout/xul/nsListItemFrame.cpp b/layout/xul/nsListItemFrame.cpp new file mode 100644 index 000000000..1776f1b6c --- /dev/null +++ b/layout/xul/nsListItemFrame.cpp @@ -0,0 +1,69 @@ +/* -*- 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 <algorithm> + +#include "nsCOMPtr.h" +#include "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsDisplayList.h" +#include "nsBoxLayout.h" +#include "nsIContent.h" + +nsListItemFrame::nsListItemFrame(nsStyleContext* aContext, + bool aIsRoot, + nsBoxLayout* aLayoutManager) + : nsGridRowLeafFrame(aContext, aIsRoot, aLayoutManager) +{ +} + +nsListItemFrame::~nsListItemFrame() +{ +} + +nsSize +nsListItemFrame::GetXULPrefSize(nsBoxLayoutState& aState) +{ + nsSize size = nsBoxFrame::GetXULPrefSize(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(aContext, false, layout); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsListItemFrame) diff --git a/layout/xul/nsListItemFrame.h b/layout/xul/nsListItemFrame.h new file mode 100644 index 000000000..40e731efa --- /dev/null +++ b/layout/xul/nsListItemFrame.h @@ -0,0 +1,34 @@ +/* -*- 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) override; + + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aState) override; + +protected: + explicit nsListItemFrame(nsStyleContext *aContext, + bool aIsRoot = false, + nsBoxLayout* aLayoutManager = nullptr); + virtual ~nsListItemFrame(); + +}; // class nsListItemFrame diff --git a/layout/xul/nsMenuBarFrame.cpp b/layout/xul/nsMenuBarFrame.cpp new file mode 100644 index 000000000..80aa98fbd --- /dev/null +++ b/layout/xul/nsMenuBarFrame.cpp @@ -0,0 +1,434 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsIDocument.h" +#include "nsGkAtoms.h" +#include "nsMenuFrame.h" +#include "nsMenuPopupFrame.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" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/Event.h" + +using namespace mozilla; + +// +// NS_NewMenuBarFrame +// +// Wrapper for creating a new menu Bar container +// +nsIFrame* +NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsMenuBarFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame) + +NS_QUERYFRAME_HEAD(nsMenuBarFrame) + NS_QUERYFRAME_ENTRY(nsMenuBarFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +// +// nsMenuBarFrame cntr +// +nsMenuBarFrame::nsMenuBarFrame(nsStyleContext* aContext): + nsBoxFrame(aContext), + mStayActive(false), + mIsActive(false), + mCurrentMenu(nullptr), + mTarget(nullptr) +{ +} // cntr + +void +nsMenuBarFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + // Create the menu bar listener. + mMenuBarListener = new nsMenuBarListener(this); + + // 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->GetComposedDoc(); + + // 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->AddSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false); + mTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false); + mTarget->AddSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false); + mTarget->AddSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), 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); + + mTarget->AddEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false); +} + +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); + firstFrame->SelectMenu(true); + + // Track this item for keyboard navigation. + mCurrentMenu = firstFrame; + } + } + + return nullptr; +} + +nsMenuFrame* +nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) +{ + uint32_t charCode; + aKeyEvent->GetCharCode(&charCode); + + AutoTArray<uint32_t, 10> accessKeys; + WidgetKeyboardEvent* nativeKeyEvent = + aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent(); + if (nativeKeyEvent) { + nativeKeyEvent->GetAccessKeyCandidates(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. + auto insertion = PresContext()->PresShell()->FrameConstructor()-> + GetInsertionPoint(GetContent(), nullptr); + nsContainerFrame* immediateParent = insertion.mParentFrame; + if (!immediateParent) + immediateParent = this; + + // Find a most preferred accesskey which should be returned. + nsIFrame* foundMenu = nullptr; + size_t foundIndex = accessKeys.NoIndex; + nsIFrame* currFrame = immediateParent->PrincipalChildList().FirstChild(); + + while (currFrame) { + nsIContent* current = currFrame->GetContent(); + + // See if it's a menu item. + if (nsXULPopupManager::IsValidMenuItem(current, false)) { + // Get the shortcut attribute. + nsAutoString shortcutKey; + current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey); + if (!shortcutKey.IsEmpty()) { + ToLowerCase(shortcutKey); + const char16_t* start = shortcutKey.BeginReading(); + const char16_t* end = shortcutKey.EndReading(); + uint32_t ch = UTF16CharEnumerator::NextChar(&start, end); + size_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, false); + } + + 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 Runnable +{ +public: + nsMenuBarSwitchMenu(nsIContent* aMenuBar, + nsIContent *aOldMenu, + nsIContent *aNewMenu, + bool aSelectFirstItem) + : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu), + mSelectFirstItem(aSelectFirstItem) + { + } + + NS_IMETHOD Run() override + { + 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, 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, + bool aFromKey) +{ + 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; + nsIContent* 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(WidgetGUIEvent* 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->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false); + mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false); + mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false); + mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), 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); + + mTarget->RemoveEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false); + + mMenuBarListener->OnDestroyMenuBarFrame(); + mMenuBarListener = nullptr; + + nsBoxFrame::DestroyFrom(aDestructRoot); +} diff --git a/layout/xul/nsMenuBarFrame.h b/layout/xul/nsMenuBarFrame.h new file mode 100644 index 000000000..676463c50 --- /dev/null +++ b/layout/xul/nsMenuBarFrame.h @@ -0,0 +1,125 @@ +/* -*- 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 final : public nsBoxFrame, public nsMenuParent +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsMenuBarFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + explicit nsMenuBarFrame(nsStyleContext* aContext); + + // nsMenuParent interface + virtual nsMenuFrame* GetCurrentMenuItem() override; + NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) override; + virtual void CurrentMenuIsBeingDestroyed() override; + NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, + bool aSelectFirstItem, + bool aFromKey) override; + + NS_IMETHOD SetActive(bool aActiveFlag) override; + + virtual bool IsMenuBar() override { return true; } + virtual bool IsContextMenu() override { return false; } + virtual bool IsActive() override { return mIsActive; } + virtual bool IsMenu() override { return false; } + virtual bool IsOpen() override { return true; } // menubars are considered always open + + bool IsMenuOpen() { return mCurrentMenu && mCurrentMenu->IsOpen(); } + + void InstallKeyboardNavigator(); + void RemoveKeyboardNavigator(); + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual void LockMenuUntilClosed(bool aLock) override {} + virtual bool IsMenuLocked() 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() 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(mozilla::WidgetGUIEvent* aEvent); + + // Used to handle ALT+key combos + nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent); + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + // Override bogus IsFrameOfType in nsBoxFrame. + if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced)) + return false; + return nsBoxFrame::IsFrameOfType(aFlags); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("MenuBar"), aResult); + } +#endif + +protected: + RefPtr<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/nsMenuBarListener.cpp b/layout/xul/nsMenuBarListener.cpp new file mode 100644 index 000000000..0fce497b8 --- /dev/null +++ b/layout/xul/nsMenuBarListener.cpp @@ -0,0 +1,455 @@ +/* -*- 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" + +// 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/BasicEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEvents.h" + +using namespace mozilla; + +/* + * nsMenuBarListener implementation + */ + +NS_IMPL_ISUPPORTS(nsMenuBarListener, nsIDOMEventListener) + +//////////////////////////////////////////////////////////////////////// + +int32_t nsMenuBarListener::mAccessKey = -1; +Modifiers nsMenuBarListener::mAccessKeyMask = 0; +bool nsMenuBarListener::mAccessKeyFocuses = false; + +nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBar) + :mAccessKeyDown(false), mAccessKeyDownCanceled(false) +{ + mMenuBarFrame = aMenuBar; +} + +//////////////////////////////////////////////////////////////////////// +nsMenuBarListener::~nsMenuBarListener() +{ +} + +void +nsMenuBarListener::OnDestroyMenuBarFrame() +{ + mMenuBarFrame = nullptr; +} + +void +nsMenuBarListener::InitializeStatics() +{ + Preferences::AddBoolVarCache(&mAccessKeyFocuses, + "ui.key.menuAccessKeyFocuses"); +} + +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; +} + +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, false); + } +} + +//////////////////////////////////////////////////////////////////////// +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. + bool toggleMenuActiveState = true; + if (!mMenuBarFrame->IsActive()) { + // First, close all existing popups because other popups shouldn't + // handle key events when menubar is active and IME should be + // disabled. + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + pm->Rollup(0, false, nullptr, nullptr); + } + // If menubar active state is changed or the menubar is destroyed + // during closing the popups, we should do nothing anymore. + toggleMenuActiveState = !Destroyed() && !mMenuBarFrame->IsActive(); + } + if (toggleMenuActiveState) { + if (!mMenuBarFrame->IsActive()) { + mMenuBarFrame->SetActiveByKeyboard(); + } + ToggleMenuActiveState(); + } + } + mAccessKeyDown = false; + mAccessKeyDownCanceled = false; + + bool active = !Destroyed() && 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; + } + + InitAccessKey(); + + if (mAccessKey) + { + // If accesskey handling was forwarded to a child process, wait for + // the mozaccesskeynotfound event before handling accesskeys. + WidgetKeyboardEvent* nativeKeyEvent = + aKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); + if (!nativeKeyEvent || + (nativeKeyEvent && nativeKeyEvent->mAccessKeyForwardedToChild)) { + return NS_OK; + } + + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); + uint32_t keyCode, charCode; + keyEvent->GetKeyCode(&keyCode); + keyEvent->GetCharCode(&charCode); + + bool hasAccessKeyCandidates = charCode != 0; + if (!hasAccessKeyCandidates) { + if (nativeKeyEvent) { + AutoTArray<uint32_t, 10> keys; + nativeKeyEvent->GetAccessKeyCandidates(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(); + } + } +#ifndef XP_MACOSX + // Also need to handle F10 specially on Non-Mac platform. + else if (nativeKeyEvent->mMessage == eKeyPress && keyCode == NS_VK_F10) { + if ((GetModifiersForAccessKey(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()) { +#ifdef MOZ_WIDGET_GTK + // In GTK, this also opens the first menu. + mMenuBarFrame->GetCurrentMenuItem()->OpenMenu(true); +#endif + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + } + } + } +#endif // !XP_MACOSX + } + + return NS_OK; +} + +bool +nsMenuBarListener::IsAccessKeyPressed(nsIDOMKeyEvent* aKeyEvent) +{ + InitAccessKey(); + // No other modifiers are allowed to be down except for Shift. + uint32_t modifiers = GetModifiersForAccessKey(aKeyEvent); + + return (mAccessKeyMask != MODIFIER_SHIFT && + (modifiers & mAccessKeyMask) && + (modifiers & ~(mAccessKeyMask | MODIFIER_SHIFT)) == 0); +} + +Modifiers +nsMenuBarListener::GetModifiersForAccessKey(nsIDOMKeyEvent* aKeyEvent) +{ + WidgetInputEvent* inputEvent = + aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent(); + MOZ_ASSERT(inputEvent); + + static const Modifiers kPossibleModifiersForAccessKey = + (MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META | + MODIFIER_OS); + return (inputEvent->mModifiers & kPossibleModifiersForAccessKey); +} + +//////////////////////////////////////////////////////////////////////// +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) && + (GetModifiersForAccessKey(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::Fullscreen(nsIDOMEvent* aEvent) +{ + if (mMenuBarFrame->IsActive()) { + ToggleMenuActiveState(); + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +nsresult +nsMenuBarListener::HandleEvent(nsIDOMEvent* aEvent) +{ + // If the menu bar is collapsed, don't do anything. + if (!mMenuBarFrame->StyleVisibility()->IsVisible()) { + return NS_OK; + } + + 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("mozaccesskeynotfound")) { + return KeyPress(aEvent); + } + if (eventType.EqualsLiteral("blur")) { + return Blur(aEvent); + } + if (eventType.EqualsLiteral("mousedown")) { + return MouseDown(aEvent); + } + if (eventType.EqualsLiteral("MozDOMFullscreen:Entered")) { + return Fullscreen(aEvent); + } + + NS_ABORT(); + + return NS_OK; +} diff --git a/layout/xul/nsMenuBarListener.h b/layout/xul/nsMenuBarListener.h new file mode 100644 index 000000000..3656c89e2 --- /dev/null +++ b/layout/xul/nsMenuBarListener.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/. */ +#ifndef nsMenuBarListener_h__ +#define nsMenuBarListener_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.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 final : public nsIDOMEventListener +{ +public: + /** default constructor + */ + explicit nsMenuBarListener(nsMenuBarFrame* aMenuBar); + + static void InitializeStatics(); + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override; + + nsresult KeyUp(nsIDOMEvent* aMouseEvent); + nsresult KeyDown(nsIDOMEvent* aMouseEvent); + nsresult KeyPress(nsIDOMEvent* aMouseEvent); + nsresult Blur(nsIDOMEvent* aEvent); + nsresult MouseDown(nsIDOMEvent* aMouseEvent); + nsresult Fullscreen(nsIDOMEvent* aEvent); + + static nsresult GetMenuAccessKey(int32_t* aAccessKey); + + NS_DECL_ISUPPORTS + + static bool IsAccessKeyPressed(nsIDOMKeyEvent* event); + + void OnDestroyMenuBarFrame(); + +protected: + /** default destructor + */ + virtual ~nsMenuBarListener(); + + static void InitAccessKey(); + + static mozilla::Modifiers GetModifiersForAccessKey(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(); + + bool Destroyed() const { return !mMenuBarFrame; } + + 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 mozilla::Modifiers mAccessKeyMask;// Modifier mask for the access key +}; + + +#endif diff --git a/layout/xul/nsMenuFrame.cpp b/layout/xul/nsMenuFrame.cpp new file mode 100644 index 000000000..ea968fab5 --- /dev/null +++ b/layout/xul/nsMenuFrame.cpp @@ -0,0 +1,1514 @@ +/* -*- 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 "nsNameSpaceManager.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 "nsXPIDLString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsIStringBundle.h" +#include "nsContentUtils.h" +#include "nsDisplayList.h" +#include "nsIReflowCallback.h" +#include "nsISound.h" +#include "nsIDOMXULMenuListElement.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/Likely.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include <algorithm> + +using namespace mozilla; + +#define NS_MENU_POPUP_LIST_INDEX 0 + +#if defined(XP_WIN) +#define NSCONTEXTMENUISMOUSEUP 1 +#endif + +NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PopupListProperty) + +// This global flag indicates that a menu just opened or closed and is used +// to ignore the mousemove and mouseup events that would fire on the menu after +// the mousedown occurred. +static int32_t gMenuJustOpenedOrClosed = false; + +const int32_t kBlinkDelay = 67; // milliseconds + +// this class is used for dispatching menu activation events asynchronously. +class nsMenuActivateEvent : public Runnable +{ +public: + nsMenuActivateEvent(nsIContent *aMenu, + nsPresContext* aPresContext, + bool aIsActivate) + : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate) + { + } + + NS_IMETHOD Run() override + { + 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"); + } + + RefPtr<Event> event = NS_NewDOMEvent(mMenu, mPresContext, nullptr); + event->InitEvent(domEventToFire, true, true); + + event->SetTrusted(true); + + EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event, + mPresContext, nullptr); + + return NS_OK; + } + +private: + nsCOMPtr<nsIContent> mMenu; + RefPtr<nsPresContext> mPresContext; + bool mIsActivate; +}; + +class nsMenuAttributeChangedEvent : public Runnable +{ +public: + nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr) + : mFrame(aFrame), mAttr(aAttr) + { + } + + NS_IMETHOD Run() override + { + nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame()); + NS_ENSURE_STATE(frame); + if (mAttr == nsGkAtoms::checked) { + frame->UpdateMenuSpecialState(); + } 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(); + } + 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(aContext); + it->SetIsMenu(true); + return it; +} + +nsIFrame* +NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + nsMenuFrame* it = new (aPresShell) nsMenuFrame(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(nsStyleContext* aContext): + nsBoxFrame(aContext), + mIsMenu(false), + mChecked(false), + mIgnoreAccelTextChange(false), + mType(eMenuType_Normal), + mBlinkState(0) +{ +} + +nsMenuParent* +nsMenuFrame::GetMenuParent() const +{ + nsContainerFrame* parent = GetParent(); + for (; parent; parent = parent->GetParent()) { + nsMenuPopupFrame* popup = do_QueryFrame(parent); + if (popup) { + return popup; + } + nsMenuBarFrame* menubar = do_QueryFrame(parent); + if (menubar) { + return menubar; + } + } + return nullptr; +} + +class nsASyncMenuInitialization final : public nsIReflowCallback +{ +public: + explicit nsASyncMenuInitialization(nsIFrame* aFrame) + : mWeakFrame(aFrame) + { + } + + virtual bool ReflowFinished() override + { + bool shouldFlush = false; + nsMenuFrame* menu = do_QueryFrame(mWeakFrame.GetFrame()); + if (menu) { + menu->UpdateMenuType(); + shouldFlush = true; + } + delete this; + return shouldFlush; + } + + virtual void ReflowCallbackCanceled() override + { + delete this; + } + + nsWeakFrame mWeakFrame; +}; + +void +nsMenuFrame::Init(nsIContent* aContent, + nsContainerFrame* 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); + + 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 = 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 = 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; + } + } +} + +void +nsMenuFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + if (aListID == kPrincipalList || aListID == kPopupList) { + NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?"); + SetPopupFrame(aChildList); + } + 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? + nsMenuParent* menuParent = GetMenuParent(); + if (menuParent && menuParent->GetCurrentMenuItem() == this) { + // yes; tell it that we're going away + menuParent->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); +} + +nsresult +nsMenuFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + nsMenuParent* menuParent = GetMenuParent(); + if (menuParent && menuParent->IsMenuLocked()) { + return NS_OK; + } + + nsWeakFrame weakFrame(this); + if (*aEventStatus == nsEventStatus_eIgnore) + *aEventStatus = nsEventStatus_eConsumeDoDefault; + + // If a menu just opened, ignore the mouseup event that might occur after a + // the mousedown event that opened it. However, if a different mousedown + // event occurs, just clear this flag. + if (gMenuJustOpenedOrClosed) { + if (aEvent->mMessage == eMouseDown) { + gMenuJustOpenedOrClosed = false; + } else if (aEvent->mMessage == eMouseUp) { + return NS_OK; + } + } + + bool onmenu = IsOnMenu(); + + if (aEvent->mMessage == eKeyPress && !IsDisabled()) { + WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); + uint32_t keyCode = keyEvent->mKeyCode; +#ifdef XP_MACOSX + // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed) + if (!IsOpen() && ((keyEvent->mCharCode == ' ' && !keyEvent->IsMeta()) || + (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) { + + // When pressing space, don't open the menu if performing an incremental search. + if (keyEvent->mCharCode != ' ' || + !nsMenuPopupFrame::IsWithinIncrementalTime(keyEvent->mTime)) { + *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->mMessage == eMouseDown && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::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 (!menuParent || menuParent->IsMenuBar()) { + ToggleMenuState(); + } + else { + if (!IsOpen()) { + menuParent->ChangeMenuItem(this, false, false); + OpenMenu(false); + } + } + } + else if ( +#ifndef NSCONTEXTMENUISMOUSEUP + (aEvent->mMessage == eMouseUp && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) && +#else + aEvent->mMessage == eContextMenu && +#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 (menuParent->IsContextMenu()) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + Execute(aEvent); + } + } + else if (aEvent->mMessage == eMouseUp && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && + !IsMenu() && !IsDisabled()) { + // Execute the execute event handler. + *aEventStatus = nsEventStatus_eConsumeNoDefault; + Execute(aEvent); + } + else if (aEvent->mMessage == eMouseOut) { + // Kill our timer if one is active. + if (mOpenTimer) { + mOpenTimer->Cancel(); + mOpenTimer = nullptr; + } + + // Deactivate the menu. + if (menuParent) { + bool onmenubar = menuParent->IsMenuBar(); + if (!(onmenubar && menuParent->IsActive())) { + if (IsMenu() && !onmenubar && IsOpen()) { + // Submenus don't get closed up immediately. + } + else if (this == menuParent->GetCurrentMenuItem() +#ifdef XP_WIN + && GetParentMenuListType() == eNotMenuList +#endif + ) { + menuParent->ChangeMenuItem(nullptr, false, false); + } + } + } + } + else if (aEvent->mMessage == eMouseMove && + (onmenu || (menuParent && menuParent->IsMenuBar()))) { + if (gMenuJustOpenedOrClosed) { + gMenuJustOpenedOrClosed = false; + return NS_OK; + } + + if (IsDisabled() && GetParentMenuListType() != eNotMenuList) { + return NS_OK; + } + + // Let the menu parent know we're the new item. + menuParent->ChangeMenuItem(this, false, false); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + NS_ENSURE_TRUE(menuParent, NS_OK); + + // we need to check if we really became the current menu + // item or not + nsMenuFrame *realCurrentItem = menuParent->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 && !menuParent->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; + + nsMenuParent* menuParent = GetMenuParent(); + if (menuParent) { + menuParent->SetActive(true); + // Make sure the current menu which is being toggled on + // the menubar is highlighted + menuParent->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 + nsMenuParent* menuParent = GetMenuParent(); + if (menuParent && menuParent->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 = menuParent->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) { + nsMenuParent* menuParent = GetMenuParent(); + pm->CancelMenuTimer(menuParent); + } + + nsCOMPtr<nsIRunnable> event = + new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag); + NS_DispatchToCurrentThread(event); + } + + return NS_OK; +} + +nsresult +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; +} + +nsIContent* +nsMenuFrame::GetAnchor() +{ + mozilla::dom::Element* anchor = nullptr; + + nsAutoString id; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id); + if (!id.IsEmpty()) { + nsIDocument* doc = mContent->OwnerDoc(); + + anchor = + doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id); + if (!anchor) { + anchor = doc->GetElementById(id); + } + } + + // Always return the menu's content if the anchor wasn't set or wasn't found. + return anchor && anchor->GetPrimaryFrame() ? anchor : mContent; +} + +void +nsMenuFrame::OpenMenu(bool aSelectFirstItem) +{ + if (!mContent) + return; + + gMenuJustOpenedOrClosed = true; + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + pm->KillMenuTimer(); + // This opens the menu asynchronously + pm->ShowMenu(mContent, aSelectFirstItem, true); + } +} + +void +nsMenuFrame::CloseMenu(bool aDeselectMenu) +{ + gMenuJustOpenedOrClosed = true; + + // Close the menu asynchronously + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && HasPopup()) + pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false); +} + +bool +nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways) +{ + nsAutoString sizedToPopup; + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup); + return sizedToPopup.EqualsLiteral("always") || + (!aRequireAlways && sizedToPopup.EqualsLiteral("pref")); +} + +nsSize +nsMenuFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) +{ + nsSize size = nsBoxFrame::GetXULMinSize(aBoxLayoutState); + DISPLAY_MIN_SIZE(this, size); + + if (IsSizedToPopup(mContent, true)) + SizeToPopup(aBoxLayoutState, size); + + return size; +} + +NS_IMETHODIMP +nsMenuFrame::DoXULLayout(nsBoxLayoutState& aState) +{ + // lay us out + nsresult rv = nsBoxFrame::DoXULLayout(aState); + + nsMenuPopupFrame* popupFrame = GetPopup(); + if (popupFrame) { + bool sizeToPopup = IsSizedToPopup(mContent, false); + popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup); + } + + return rv; +} + +#ifdef DEBUG_LAYOUT +nsresult +nsMenuFrame::SetXULDebug(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::SetXULDebug(aState, aDebug); + nsMenuPopupFrame* popupFrame = GetPopup(); + if (popupFrame) + SetXULDebug(aState, popupFrame, aDebug); + } + + return NS_OK; +} + +nsresult +nsMenuFrame::SetXULDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug) +{ + if (!aList) + return NS_OK; + + while (aList) { + if (aList->IsXULBoxFrame()) + aList->SetXULDebug(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(WidgetGUIEvent* aEvent) +{ + if (IsDisabled()) { +#ifdef XP_WIN + // behavior on Windows - close the popup chain + nsMenuParent* menuParent = GetMenuParent(); + if (menuParent) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny); + if (popup) + pm->HidePopup(popup->GetContent(), true, true, true, false); + } + } +#endif // #ifdef XP_WIN + // this menu item was disabled - exit + return nullptr; + } + + if (!IsOpen()) { + // The enter key press applies to us. + nsMenuParent* menuParent = GetMenuParent(); + if (!IsMenu() && menuParent) + 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() +{ + nsMenuParent* menuParent = GetMenuParent(); + if (menuParent && menuParent->IsMenu()) { + nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(menuParent); + 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; + + nsMenuParent* menuParent = GetMenuParent(); + if (!IsOpen() && menuParent) { + // 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) || menuParent->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: { + nsMenuParent* menuParent = GetMenuParent(); + if (menuParent) { + menuParent->LockMenuUntilClosed(false); + } + PassMenuCommandEventToPopupManager(); + StopBlinking(); + break; + } + } + } + + return NS_OK; +} + +bool +nsMenuFrame::IsDisabled() +{ + return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters); +} + +void +nsMenuFrame::UpdateMenuType() +{ + 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(); +} + +/* update checked-ness for type="checkbox" and type="radio" */ +void +nsMenuFrame::UpdateMenuSpecialState() +{ + 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* firstMenuItem = nsXULPopupManager::GetNextMenuItem(GetParent(), nullptr, true); + nsIFrame* sib = firstMenuItem; + while (sib) { + nsMenuFrame* menu = do_QueryFrame(sib); + if (sib != this) { + 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 = nsXULPopupManager::GetNextMenuItem(GetParent(), menu, true); + if (sib == firstMenuItem) { + break; + } + } +} + +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->GetUncomposedDoc(); + if (!document) + return; + + //XXXsmaug If mContent is in shadow dom, should we use + // ShadowRoot::GetElementById()? + 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; + } + } + + 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 (WidgetInputEvent::AccelModifier()) { + case MODIFIER_META: + accelText += metaText; + break; + case MODIFIER_OS: + accelText += osText; + break; + case MODIFIER_ALT: + accelText += altText; + break; + case MODIFIER_CONTROL: + accelText += controlText; + break; + default: + MOZ_CRASH( + "Handle the new result of WidgetInputEvent::AccelModifier()"); + break; + } + } + + accelText += modifierSeparator; + + token = nsCRT::strtok(newStr, ", \t", &newStr); + } + + free(str); + + accelText += accelString; + + mIgnoreAccelTextChange = true; + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify); + ENSURE_TRUE(weakFrame.IsAlive()); + + mIgnoreAccelTextChange = false; +} + +void +nsMenuFrame::Execute(WidgetGUIEvent* 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(WidgetGUIEvent* 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; + + nsMenuParent* menuParent = GetMenuParent(); + if (menuParent) { + // Make this menu ignore events from now on. + menuParent->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(WidgetGUIEvent* 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->IsTrusted() : + nsContentUtils::IsCallerChrome(); + + bool shift = false, control = false, alt = false, meta = false; + WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr; + if (inputEvent) { + shift = inputEvent->IsShift(); + control = inputEvent->IsControl(); + alt = inputEvent->IsAlt(); + meta = inputEvent->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 = EventStateManager::IsHandlingUserInput(); + + mDelayedMenuCommandEvent = + new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta, + userinput, aFlipChecked); +} + +void +nsMenuFrame::PassMenuCommandEventToPopupManager() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + nsMenuParent* menuParent = GetMenuParent(); + if (pm && menuParent && mDelayedMenuCommandEvent) { + pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent); + } + mDelayedMenuCommandEvent = nullptr; +} + +void +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; + } + nsBoxFrame::RemoveFrame(aListID, aOldFrame); +} + +void +nsMenuFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { + SetPopupFrame(aFrameList); + if (HasPopup()) { +#ifdef DEBUG_LAYOUT + nsBoxLayoutState state(PresContext()); + SetXULDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); +#endif + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + } + + if (aFrameList.IsEmpty()) + return; + + if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) { + aPrevFrame = nullptr; + } + + nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); +} + +void +nsMenuFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { + SetPopupFrame(aFrameList); + if (HasPopup()) { + +#ifdef DEBUG_LAYOUT + nsBoxLayoutState state(PresContext()); + SetXULDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); +#endif + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + } + + if (aFrameList.IsEmpty()) + return; + + nsBoxFrame::AppendFrames(aListID, aFrameList); +} + +bool +nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) +{ + if (!IsXULCollapsed()) { + bool widthSet, heightSet; + nsSize tmpSize(-1, 0); + nsIFrame::AddXULPrefSize(this, tmpSize, widthSet, heightSet); + if (!widthSet && GetXULFlex() == 0) { + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) + return false; + tmpSize = popupFrame->GetXULPrefSize(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; + GetXULBorderAndPadding(borderPadding); + + // if there is a scroll frame, add the desired width of the scrollbar as well + nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->PrincipalChildList().FirstChild()); + 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::GetXULPrefSize(nsBoxLayoutState& aState) +{ + nsSize size = nsBoxFrame::GetXULPrefSize(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::GetXULMinSize(aState); + nsSize maxSize = GetXULMaxSize(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, false); + return NS_OK; + } + + nsCOMPtr<nsIContent> child(do_QueryInterface(aChild)); + + nsMenuFrame* menu = do_QueryFrame(child->GetPrimaryFrame()); + if (menu) + popupFrame->ChangeMenuItem(menu, false, false); + return NS_OK; +} + +nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame() +{ + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) + return nullptr; + nsIFrame* childFrame = popupFrame->PrincipalChildList().FirstChild(); + if (childFrame) + return popupFrame->GetScrollFrame(childFrame); + return nullptr; +} + +// nsMenuTimerMediator implementation. +NS_IMPL_ISUPPORTS(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/nsMenuFrame.h b/layout/xul/nsMenuFrame.h new file mode 100644 index 000000000..1941ec69e --- /dev/null +++ b/layout/xul/nsMenuFrame.h @@ -0,0 +1,288 @@ +/* -*- 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; + +#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 final : public nsITimerCallback +{ +public: + explicit nsMenuTimerMediator(nsMenuFrame* aFrame); + + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + void ClearFrame(); + +private: + ~nsMenuTimerMediator(); + + // Pointer to the wrapped frame. + nsMenuFrame* mFrame; +}; + +class nsMenuFrame final : public nsBoxFrame +{ +public: + explicit nsMenuFrame(nsStyleContext* aContext); + + NS_DECL_QUERYFRAME_TARGET(nsMenuFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + +#ifdef DEBUG_LAYOUT + virtual nsresult SetXULDebug(nsBoxLayoutState& aState, bool aDebug) 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 override; + virtual void GetChildLists(nsTArray<ChildList>* aLists) const override; + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + // Overridden to prevent events from going to children of the menu. + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + // this method can destroy the frame + virtual nsresult HandleEvent(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual void SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) override; + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + virtual nsIAtom* GetType() const override { return nsGkAtoms::menuFrame; } + + NS_IMETHOD SelectMenu(bool aActivateFlag); + + virtual nsIScrollableFrame* GetScrollTargetFrame() override; + + // Retrieve the element that the menu should be anchored to. By default this is + // the menu itself. However, the anchor attribute may refer to the value of an + // anonid within the menu's binding, or, if not found, the id of an element in + // the document. + nsIContent* GetAnchor(); + + /** + * 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(mozilla::WidgetGUIEvent* aEvent); + + // Return the nearest menu bar or menupopup ancestor frame. + nsMenuParent* GetMenuParent() const; + + 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() const + { + nsMenuParent* menuParent = GetMenuParent(); + return menuParent && menuParent->IsMenuBar(); + } + bool IsOnActiveMenuBar() const + { + nsMenuParent* menuParent = GetMenuParent(); + return menuParent && menuParent->IsMenuBar() && menuParent->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() const + { + nsMenuParent* menuParent = GetMenuParent(); + return menuParent && menuParent->IsMenu(); + } + void SetIsMenu(bool aIsMenu) { mIsMenu = aIsMenu; } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const 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(); + + // Update the menu's type (normal, checkbox, radio). + // This method can destroy the frame. + void UpdateMenuType(); + // Update the checked state of the menu, and for radios, clear any other + // checked items. This method can destroy the frame. + void UpdateMenuSpecialState(); + + // 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(mozilla::WidgetGUIEvent *aEvent); + + // This method can destroy the frame + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + virtual ~nsMenuFrame() { } + + bool SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize); + + bool ShouldBlink(); + void StartBlinking(mozilla::WidgetGUIEvent* aEvent, bool aFlipChecked); + void StopBlinking(); + void CreateMenuCommandEvent(mozilla::WidgetGUIEvent* aEvent, + bool aFlipChecked); + void PassMenuCommandEventToPopupManager(); + +protected: +#ifdef DEBUG_LAYOUT + nsresult SetXULDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug); +#endif + 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; + + // Reference to the mediator which wraps this frame. + RefPtr<nsMenuTimerMediator> mTimerMediator; + + nsCOMPtr<nsITimer> mOpenTimer; + nsCOMPtr<nsITimer> mBlinkTimer; + + uint8_t mBlinkState; // 0: not blinking, 1: off, 2: on + RefPtr<nsXULMenuCommandEvent> mDelayedMenuCommandEvent; + + nsString mGroupName; + +}; // class nsMenuFrame + +#endif diff --git a/layout/xul/nsMenuParent.h b/layout/xul/nsMenuParent.h new file mode 100644 index 000000000..19c2dcd7c --- /dev/null +++ b/layout/xul/nsMenuParent.h @@ -0,0 +1,69 @@ +/* -*- 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. The aFromKey argument indicates + // that the keyboard was used to navigate to the new menu item. + NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, + bool aSelectFirstItem, + bool aFromKey) = 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/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp new file mode 100644 index 000000000..6e706e49c --- /dev/null +++ b/layout/xul/nsMenuPopupFrame.cpp @@ -0,0 +1,2442 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsViewManager.h" +#include "nsWidgetsCID.h" +#include "nsMenuFrame.h" +#include "nsMenuBarFrame.h" +#include "nsPopupSetFrame.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMKeyEvent.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 "nsIRootBox.h" +#include "nsIDocShell.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsLayoutUtils.h" +#include "nsContentUtils.h" +#include "nsCSSFrameConstructor.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 "nsTransitionManager.h" +#include "nsDisplayList.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/PopupBoxObject.h" +#include <algorithm> + +using namespace mozilla; +using mozilla::dom::PopupBoxObject; + +int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1; + +DOMTimeStamp nsMenuPopupFrame::sLastKeyTime = 0; + +// 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. +uint32_t nsMenuPopupFrame::sTimeoutOfIncrementalSearch = 1000; + +const char* kPrefIncrementalSearchTimeout = + "ui.menu.incremental_search.timeout"; + +// NS_NewMenuPopupFrame +// +// Wrapper for creating a new menu popup container +// +nsIFrame* +NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsMenuPopupFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame) + +NS_QUERYFRAME_HEAD(nsMenuPopupFrame) + NS_QUERYFRAME_ENTRY(nsMenuPopupFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +// +// nsMenuPopupFrame ctor +// +nsMenuPopupFrame::nsMenuPopupFrame(nsStyleContext* aContext) + :nsBoxFrame(aContext), + mCurrentMenu(nullptr), + mPrefSize(-1, -1), + mLastClientOffset(0, 0), + mPopupType(ePopupTypePanel), + mPopupState(ePopupClosed), + mPopupAlignment(POPUPALIGNMENT_NONE), + mPopupAnchor(POPUPALIGNMENT_NONE), + mPosition(POPUPPOSITION_UNKNOWN), + mConsumeRollupEvent(PopupBoxObject::ROLLUP_DEFAULT), + mFlip(FlipType_Default), + mIsOpenChanged(false), + mIsContextMenu(false), + mAdjustOffsetForContextMenu(false), + mGeneratedChildren(false), + mMenuCanOverlapOSBar(false), + mShouldAutoPosition(true), + mInContentShell(true), + mIsMenuLocked(false), + mMouseTransparent(false), + mHFlip(false), + mVFlip(false), + mAnchorType(MenuPopupAnchorType_Node) +{ + // 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); + Preferences::AddUintVarCache(&sTimeoutOfIncrementalSearch, + kPrefIncrementalSearchTimeout, 1000); +} // ctor + +void +nsMenuPopupFrame::Init(nsIContent* aContent, + nsContainerFrame* 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; + } + + nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell(); + if (dsti && dsti->ItemType() == 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 && !PrincipalChildList().FirstChild(), + "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(); + + if (!mInContentShell) { + // A drag popup may be used for non-static translucent drag feedback + if (mPopupType == ePopupTypePanel && + mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::drag, eIgnoreCase)) { + widgetData.mIsDragPopup = true; + } + + // If mousethrough="always" is set directly on the popup, then the widget + // should ignore mouse events, passing them through to the content behind. + mMouseTransparent = GetStateBits() & NS_FRAME_MOUSE_THROUGH_ALWAYS; + widgetData.mMouseTransparent = mMouseTransparent; + } + + 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); + nsIContent* parentContent = GetContent()->GetParent(); + nsIAtom *tag = nullptr; + if (parentContent && parentContent->IsXULElement()) + tag = parentContent->NodeInfo()->NameAtom(); + widgetData.mSupportTranslucency = mode == eTransparencyTransparent; + widgetData.mDropShadow = !(mode == eTransparencyTransparent || 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<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell(); + 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; +} + +NS_IMETHODIMP nsXULPopupShownEvent::Run() +{ + nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame()); + // Set the state to visible if the popup is still open. + if (popup && popup->IsOpen()) { + popup->SetPopupState(ePopupShown); + } + + WidgetMouseEvent event(true, eXULPopupShown, nullptr, + WidgetMouseEvent::eReal); + return EventDispatcher::Dispatch(mPopup, mPresContext, &event); +} + +NS_IMETHODIMP nsXULPopupShownEvent::HandleEvent(nsIDOMEvent* aEvent) +{ + nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame()); + nsCOMPtr<nsIDOMEventTarget> eventTarget; + aEvent->GetTarget(getter_AddRefs(eventTarget)); + // Ignore events not targeted at the popup itself (ie targeted at + // descendants): + if (!SameCOMIdentity(mPopup, eventTarget)) { + return NS_OK; + } + if (popup) { + // ResetPopupShownDispatcher will delete the reference to this, so keep + // another one until Run is finished. + RefPtr<nsXULPopupShownEvent> event = this; + // Only call Run if it the dispatcher was assigned. This avoids calling the + // Run method if the transitionend event fires multiple times. + if (popup->ClearPopupShownDispatcher()) { + return Run(); + } + } + + CancelListener(); + return NS_OK; +} + +void nsXULPopupShownEvent::CancelListener() +{ + mPopup->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false); +} + +NS_IMPL_ISUPPORTS_INHERITED(nsXULPopupShownEvent, Runnable, nsIDOMEventListener); + +void +nsMenuPopupFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + // unless the list is empty, indicate that children have been generated. + if (aListID == kPrincipalList && aChildList.NotEmpty()) { + mGeneratedChildren = true; + } + 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, + nsIFrame* aAnchor, 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 || mPopupState == ePopupPositioning); + if (!shouldPosition && !aSizedToPopup) { + RemoveStateBits(NS_FRAME_FIRST_REFLOW); + return; + } + } + + // if the popup has just been opened, make sure the scrolled window is at 0,0 + // Don't scroll menulists as they will scroll to their selected item on their own. + if (mIsOpenChanged && !IsMenuList()) { + nsIScrollableFrame *scrollframe = do_QueryFrame(nsBox::GetChildXULBox(this)); + 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 = GetXULPrefSize(aState); + nsSize minSize = GetXULMinSize(aState); + nsSize maxSize = GetXULMaxSize(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) { + SetXULBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false); + mPrefSize = prefSize; + } + + bool needCallback = false; + if (shouldPosition) { + SetPopupPosition(aAnchor, false, aSizedToPopup, mPopupState == ePopupPositioning); + needCallback = true; + } + + nsRect bounds(GetRect()); + XULLayout(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. + bool rePosition = shouldPosition && (mPosition == POPUPPOSITION_SELECTION); + 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) { + rePosition = true; + needCallback = true; + } + } + } + + if (rePosition) { + SetPopupPosition(aAnchor, false, aSizedToPopup, 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); + + if (mPopupState == ePopupOpening) { + mPopupState = ePopupVisible; + } + + viewManager->SetViewVisibility(view, nsViewVisibility_kShow); + nsContainerFrame::SyncFrameViewProperties(pc, this, nullptr, view, 0); + } + + // finally, if the popup just opened, send a popupshown event + if (mIsOpenChanged) { + mIsOpenChanged = false; + + // Make sure the current selection in a menulist is visible. + if (IsMenuList() && mCurrentMenu) { + EnsureMenuItemIsVisible(mCurrentMenu); + } + +#ifndef MOZ_WIDGET_GTK + // If the animate attribute is set to open, check for a transition and wait + // for it to finish before firing the popupshown event. + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::animate, + nsGkAtoms::open, eCaseMatters) && + nsLayoutUtils::HasCurrentTransitions(this)) { + mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, pc); + mContent->AddSystemEventListener(NS_LITERAL_STRING("transitionend"), + mPopupShownDispatcher, false, false); + return; + } +#endif + + // If there are no transitions, fire the popupshown event right away. + nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), pc); + NS_DispatchToCurrentThread(event); + } + + if (needCallback && !mReflowCallbackData.mPosted) { + pc->PresShell()->PostReflowCallback(this); + mReflowCallbackData.MarkPosted(aAnchor, aSizedToPopup); + } +} + +bool +nsMenuPopupFrame::ReflowFinished() +{ + SetPopupPosition(mReflowCallbackData.mAnchor, false, mReflowCallbackData.mSizedToPopup, false); + + mReflowCallbackData.Clear(); + + return false; +} + +void +nsMenuPopupFrame::ReflowCallbackCanceled() +{ + mReflowCallbackData.Clear(); +} + +bool +nsMenuPopupFrame::IsMenuList() +{ + nsIFrame* parentMenu = GetParent(); + if (!parentMenu) { + return false; + } + + nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent()); + return menulist != nullptr; +} + +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, + MenuPopupAnchorType aAnchorType, + bool aAttributesOverride) +{ + EnsureWidget(); + + mPopupState = ePopupShowing; + mAnchorContent = aAnchorContent; + mTriggerContent = aTriggerContent; + mXPos = aXPos; + mYPos = aYPos; + mAdjustOffsetForContextMenu = false; + mVFlip = false; + mHFlip = false; + mAlignmentOffset = 0; + + mAnchorType = aAnchorType; + + // 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 || aAnchorType == MenuPopupAnchorType_Rect) { + 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); + } + + if (flip.EqualsLiteral("none")) { + mFlip = FlipType_None; + } else if (flip.EqualsLiteral("both")) { + mFlip = FlipType_Both; + } else if (flip.EqualsLiteral("slide")) { + mFlip = FlipType_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 if (position.EqualsLiteral("selection")) { + mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; + mPopupAlignment = POPUPALIGNMENT_TOPLEFT; + mPosition = POPUPPOSITION_SELECTION; + } + else { + InitPositionFromAnchorAlign(anchor, align); + } + } + + mScreenRect = nsIntRect(-1, -1, 0, 0); + + 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)) + mScreenRect.x = x; + } + if (!top.IsEmpty()) { + int32_t y = top.ToInteger(&err); + if (NS_SUCCEEDED(err)) + mScreenRect.y = y; + } + } +} + +void +nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu) +{ + EnsureWidget(); + + mPopupState = ePopupShowing; + mAnchorContent = nullptr; + mTriggerContent = aTriggerContent; + mScreenRect = nsIntRect(aXPos, aYPos, 0, 0); + mXPos = 0; + mYPos = 0; + mFlip = FlipType_Default; + mPopupAnchor = POPUPALIGNMENT_NONE; + mPopupAlignment = POPUPALIGNMENT_NONE; + mPosition = POPUPPOSITION_UNKNOWN; + mIsContextMenu = aIsContextMenu; + mAdjustOffsetForContextMenu = aIsContextMenu; + mAnchorType = MenuPopupAnchorType_Point; +} + +void +nsMenuPopupFrame::InitializePopupAtRect(nsIContent* aTriggerContent, + const nsAString& aPosition, + const nsIntRect& aRect, + bool aAttributesOverride) +{ + InitializePopup(nullptr, aTriggerContent, aPosition, 0, 0, + MenuPopupAnchorType_Rect, aAttributesOverride); + mScreenRect = aRect; +} + +void +nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent, + nsAString& aAnchor, + nsAString& aAlign, + int32_t aXPos, int32_t aYPos) +{ + EnsureWidget(); + + mPopupState = ePopupShowing; + mAdjustOffsetForContextMenu = false; + mFlip = FlipType_Default; + + // 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; + mAnchorType = MenuPopupAnchorType_Node; + mScreenRect = nsIntRect(-1, -1, 0, 0); + mXPos = 0; + mYPos = 0; + InitPositionFromAnchorAlign(aAnchor, aAlign); + } + else { + mAnchorContent = nullptr; + mAnchorType = MenuPopupAnchorType_Point; + mPopupAnchor = POPUPALIGNMENT_NONE; + mPopupAlignment = POPUPALIGNMENT_NONE; + mPosition = POPUPPOSITION_UNKNOWN; + mScreenRect = nsIntRect(aXPos, aYPos, 0, 0); + mXPos = aXPos; + mYPos = aYPos; + } +} + +void +nsMenuPopupFrame::ShowPopup(bool aIsContextMenu) +{ + mIsContextMenu = aIsContextMenu; + + InvalidateFrameSubtree(); + + if (mPopupState == ePopupShowing || mPopupState == ePopupPositioning) { + mPopupState = ePopupOpening; + mIsOpenChanged = true; + + // Clear mouse capture when a popup is opened. + if (mPopupType == ePopupTypeMenu) { + EventStateManager* activeESM = + static_cast<EventStateManager*>( + EventStateManager::GetActiveEventStateManager()); + if (activeESM) { + EventStateManager::ClearGlobalActiveContent(activeESM); + } + + nsIPresShell::SetCapturingContent(nullptr, 0); + } + + 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"); + + ClearPopupShownDispatcher(); + + // don't hide the popup when it isn't open + if (mPopupState == ePopupClosed || mPopupState == ePopupShowing || + mPopupState == ePopupPositioning) + 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->GetUncomposedDoc(); + if (doc) { + if (nsPIDOMWindowOuter* win = doc->GetWindow()) { + 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?"); + EventStates state = mContent->AsElement()->State(); + + if (state.HasState(NS_EVENT_STATE_HOVER)) { + EventStateManager* esm = PresContext()->EventStateManager(); + esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER); + } + + nsMenuFrame* menuFrame = do_QueryFrame(GetParent()); + if (menuFrame) { + menuFrame->PopupClosed(aDeselectMenu); + } +} + +uint32_t +nsMenuPopupFrame::GetXULLayoutFlags() +{ + return 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 && widget->WindowType() == 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; + } + + nsRect originalAnchorRect(anchorRect); + + // 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; + } + + // If we aligning to the selected item in the popup, adjust the vertical + // position by the height of the menulist label and the selected item's + // position. + if (mPosition == POPUPPOSITION_SELECTION) { + MOZ_ASSERT(popupAnchor == POPUPALIGNMENT_BOTTOMLEFT || + popupAnchor == POPUPALIGNMENT_BOTTOMRIGHT); + MOZ_ASSERT(popupAlign == POPUPALIGNMENT_TOPLEFT || + popupAlign == POPUPALIGNMENT_TOPRIGHT); + + nsIFrame* selectedItemFrame = GetSelectedItemForAlignment(); + if (selectedItemFrame) { + pnt.y -= originalAnchorRect.height + selectedItemFrame->GetRect().y; + } + } + + // 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 = mFlip == FlipType_Both ? 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; +} + +nsIFrame* nsMenuPopupFrame::GetSelectedItemForAlignment() +{ + // This method adjusts a menulist's popup such that the selected item is under the cursor, aligned + // with the menulist label. + nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(mAnchorContent); + if (!select) { + // If there isn't an anchor, then try just getting the parent of the popup. + select = do_QueryInterface(mContent->GetParent()); + if (!select) { + return nullptr; + } + } + + nsCOMPtr<nsIDOMXULSelectControlItemElement> item; + select->GetSelectedItem(getter_AddRefs(item)); + + nsCOMPtr<nsIContent> selectedElement = do_QueryInterface(item); + return selectedElement ? selectedElement->GetPrimaryFrame() : nullptr; +} + +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. + nscoord newPos = + std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint)); + *aOffset = newPos - aScreenPoint; + aScreenPoint = newPos; + 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 aEndAligned, bool* aFlipSide) +{ + // The flip side argument will be set to true if there wasn't room and we + // flipped to the opposite side. + *aFlipSide = false; + + // 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; + *aFlipSide = !aEndAligned; + } + 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 = aEndAligned; + 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) { + *aFlipSide = aEndAligned; + 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 - std::max(aOffsetForContextMenu, 0); + if (newScreenPoint != aScreenPoint) { + *aFlipSide = !aEndAligned; + 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, bool aSizedToPopup, bool aNotify) +{ + 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???"); + + // For anchored popups, the anchor rectangle. For non-anchored popups, the + // size will be 0. + nsRect anchorRect; + + // Width of the parent, used when aSizedToPopup is true. + int32_t parentWidth = 0; + + bool anchored = IsAnchored(); + if (anchored || aSizedToPopup) { + // In order to deal with transforms, we need the root prescontext: + nsPresContext* rootPresContext = presContext->GetRootPresContext(); + + // If we can't reach a root pres context, don't bother continuing: + if (!rootPresContext) { + return NS_OK; + } + + // If anchored to a rectangle, use that rectangle. Otherwise, determine the + // rectangle from the anchor. + if (mAnchorType == MenuPopupAnchorType_Rect) { + anchorRect = ToAppUnits(mScreenRect, presContext->AppUnitsPerCSSPixel()); + } + else { + // 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; + } + } + + // And then we need its root frame for a reference + nsIFrame* referenceFrame = rootPresContext->FrameManager()->GetRootFrame(); + + // the dimensions of the anchor + nsRect parentRect = aAnchorFrame->GetRectRelativeToSelf(); + // Relative to the root + anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(aAnchorFrame, + parentRect, + referenceFrame); + // Relative to the screen + anchorRect.MoveBy(referenceFrame->GetScreenRectInAppUnits().TopLeft()); + + // In its own app units + anchorRect = + anchorRect.ScaleToOtherAppUnitsRoundOut(rootPresContext->AppUnitsPerDevPixel(), + presContext->AppUnitsPerDevPixel()); + } + + // The width is needed when aSizedToPopup is true + parentWidth = anchorRect.width; + } + + // 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 = aSizedToPopup ? parentWidth : mPrefSize.width; + mRect.height = mPrefSize.height; + + // If we're anchoring to a rect, and the rect is smaller than the preferred size + // of the popup, change its width accordingly. + if (mAnchorType == MenuPopupAnchorType_Rect && + parentWidth < mPrefSize.width) { + mRect.width = mPrefSize.width; + } + + // the screen position in app units where the popup should appear + nsPoint screenPoint; + + // 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(); + nsPoint offsetForContextMenu; + + bool isNoAutoHide = IsNoAutoHide(); + nsPopupLevel popupLevel = PopupLevel(isNoAutoHide); + + if (anchored) { + // 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 || mAnchorType == MenuPopupAnchorType_Rect) { + // 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 != ePopupLevelParent || mAnchorType == MenuPopupAnchorType_Rect)) { + // Account for the margin that will end up being added to the screen coordinate + // the next time SetPopupPosition is called. + mAnchorType = MenuPopupAnchorType_Point; + mScreenRect.x = presContext->AppUnitsToIntCSSPixels(screenPoint.x - margin.left); + mScreenRect.y = presContext->AppUnitsToIntCSSPixels(screenPoint.y - margin.top); + } + } + else { + // The popup is positioned at a screen coordinate. + // First convert the screen position in mScreenRect from CSS pixels into + // device pixels, ignoring any zoom as mScreenRect holds unzoomed screen + // coordinates. + int32_t factor = devContext->AppUnitsPerDevPixelAtUnitFullZoom(); + + // Depending on the platform, context menus should be offset by varying amounts + // to ensure 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) { + nsPoint offsetForContextMenuDev; + offsetForContextMenuDev.x = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt( + LookAndFeel::eIntID_ContextMenuOffsetHorizontal)) / factor; + offsetForContextMenuDev.y = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt( + LookAndFeel::eIntID_ContextMenuOffsetVertical)) / factor; + offsetForContextMenu.x = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.x); + offsetForContextMenu.y = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.y); + } + + // next, convert into app units accounting for the zoom + screenPoint.x = presContext->DevPixelsToAppUnits( + nsPresContext::CSSPixelsToAppUnits(mScreenRect.x) / factor); + screenPoint.y = presContext->DevPixelsToAppUnits( + nsPresContext::CSSPixelsToAppUnits(mScreenRect.y) / factor); + anchorRect = nsRect(screenPoint, nsSize(0, 0)); + + // add the margins on the popup + screenPoint.MoveBy(margin.left + offsetForContextMenu.x, + margin.top + offsetForContextMenu.y); + +#ifdef XP_MACOSX + // OSX tooltips follow standard flip rule but other popups flip horizontally not vertically + if (mPopupType == ePopupTypeTooltip) { + vFlip = FlipStyle_Outside; + } else { + hFlip = FlipStyle_Outside; + } +#else + // Other OS screen positioned popups can be flipped vertically but never horizontally + vFlip = FlipStyle_Outside; +#endif // #ifdef XP_MACOSX + } + + // If a panel is being moved or has flip="none", 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 || (mFlip != FlipType_None && + (!aIsMove || mPopupType != ePopupTypePanel))) { + int32_t appPerDev = presContext->AppUnitsPerDevPixel(); + LayoutDeviceIntRect anchorRectDevPix = + LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRect, appPerDev); + LayoutDeviceIntRect rootScreenRectDevPix = + LayoutDeviceIntRect::FromAppUnitsToNearest(rootScreenRect, appPerDev); + LayoutDeviceIntRect screenRectDevPix = + GetConstraintRect(anchorRectDevPix, rootScreenRectDevPix, popupLevel); + nsRect screenRect = + LayoutDeviceIntRect::ToAppUnits(screenRectDevPix, appPerDev); + + // Ensure that anchorRect is on screen. + anchorRect = anchorRect.Intersect(screenRect); + + // 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 = false, slideVertical = false; + if (mFlip == FlipType_Slide) { + int8_t position = GetAlignmentPosition(); + slideHorizontal = position >= POPUPPOSITION_BEFORESTART && + position <= POPUPPOSITION_AFTEREND; + slideVertical = position >= POPUPPOSITION_STARTBEFORE && + position <= POPUPPOSITION_ENDAFTER; + } + + // 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. + bool endAligned = IsDirectionRTL() ? + mPopupAlignment == POPUPALIGNMENT_TOPLEFT || mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT : + mPopupAlignment == POPUPALIGNMENT_TOPRIGHT || mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT; + 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.x, hFlip, + endAligned, &mHFlip); + } + + endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT || + mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT; + 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.y, vFlip, + endAligned, &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"); + } + + // snap the popup's position in screen coordinates to device pixels, + // see bug 622507, bug 961431 + screenPoint.x = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.x); + screenPoint.y = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.y); + + // 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(); + + 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 (aSizedToPopup) { + nsBoxLayoutState state(PresContext()); + // XXXndeakin can parentSize.width still extend outside? + SetXULBounds(state, mRect); + } + + // If the popup is in the positioned state or if it is shown and the position + // or size changed, dispatch a popuppositioned event if the popup wants it. + nsIntRect newRect(screenPoint.x, screenPoint.y, mRect.width, mRect.height); + if (mPopupState == ePopupPositioning || + (mPopupState == ePopupShown && !newRect.IsEqualEdges(mUsedScreenRect))) { + mUsedScreenRect = newRect; + if (aNotify) { + nsXULPopupPositionedEvent::DispatchIfNeeded(mContent, false, false); + } + } + + return NS_OK; +} + +/* virtual */ nsMenuFrame* +nsMenuPopupFrame::GetCurrentMenuItem() +{ + return mCurrentMenu; +} + +LayoutDeviceIntRect +nsMenuPopupFrame::GetConstraintRect(const LayoutDeviceIntRect& aAnchorRect, + const LayoutDeviceIntRect& aRootScreenRect, + nsPopupLevel aPopupLevel) +{ + LayoutDeviceIntRect screenRectPixels; + + // 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. + DesktopToLayoutDeviceScale scale = + PresContext()->DeviceContext()->GetDesktopToDeviceScale(); + DesktopRect rect = + (mInContentShell ? aRootScreenRect : aAnchorRect) / scale; + int32_t width = std::max(1, NSToIntRound(rect.width)); + int32_t height = std::max(1, NSToIntRound(rect.height)); + sm->ScreenForRect(rect.x, rect.y, width, height, getter_AddRefs(screen)); + if (screen) { + // Non-top-level popups (which will always be panels) + // should never overlap the OS bar: + bool dontOverlapOSBar = aPopupLevel != ePopupLevelTop; + // get the total screen area if the popup is allowed to overlap it. + if (!dontOverlapOSBar && mMenuCanOverlapOSBar && !mInContentShell) + screen->GetRect(&screenRectPixels.x, &screenRectPixels.y, + &screenRectPixels.width, &screenRectPixels.height); + else + screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y, + &screenRectPixels.width, &screenRectPixels.height); + } + } + + if (mInContentShell) { + // for content shells, clip to the client area rather than the screen area + screenRectPixels.IntersectRect(screenRectPixels, aRootScreenRect); + } + else if (!mOverrideConstraintRect.IsEmpty()) { + LayoutDeviceIntRect overrideConstrainRect = + LayoutDeviceIntRect::FromAppUnitsToNearest(mOverrideConstraintRect, + PresContext()->AppUnitsPerDevPixel()); + // This is currently only used for <select> elements where we want to constrain + // vertically to the screen but not horizontally, so do the intersection and then + // reset the horizontal values. + screenRectPixels.IntersectRect(screenRectPixels, overrideConstrainRect); + screenRectPixels.x = overrideConstrainRect.x; + screenRectPixels.width = overrideConstrainRect.width; + } + + return screenRectPixels; +} + +void nsMenuPopupFrame::CanAdjustEdges(int8_t aHorizontalSide, + int8_t aVerticalSide, + LayoutDeviceIntPoint& 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; + } + } +} + +ConsumeOutsideClicksResult nsMenuPopupFrame::ConsumeOutsideClicks() +{ + // If the popup has explicitly set a consume mode, honor that. + if (mConsumeRollupEvent != PopupBoxObject::ROLLUP_DEFAULT) { + return (mConsumeRollupEvent == PopupBoxObject::ROLLUP_CONSUME) ? + ConsumeOutsideClicks_True : ConsumeOutsideClicks_ParentOnly; + } + + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks, + nsGkAtoms::_true, eCaseMatters)) { + return ConsumeOutsideClicks_True; + } + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks, + nsGkAtoms::_false, eCaseMatters)) { + return ConsumeOutsideClicks_ParentOnly; + } + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks, + nsGkAtoms::never, eCaseMatters)) { + return ConsumeOutsideClicks_Never; + } + + nsCOMPtr<nsIContent> parentContent = mContent->GetParent(); + if (parentContent) { + dom::NodeInfo *ni = parentContent->NodeInfo(); + if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL)) { + return ConsumeOutsideClicks_True; // Consume outside clicks for combo boxes on all platforms + } +#if defined(XP_WIN) + // 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 ConsumeOutsideClicks_Never; + } +#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 ConsumeOutsideClicks_Never; + } + } + } + + return ConsumeOutsideClicks_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->PrincipalChildList().FirstChild(); + 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); + } +} + +void nsMenuPopupFrame::ChangeByPage(bool aIsUp) +{ + // Only scroll by page within menulists. + if (!IsMenuList()) { + return; + } + + nsMenuFrame* newMenu = nullptr; + nsIFrame* currentMenu = mCurrentMenu; + if (!currentMenu) { + // If there is no current menu item, get the first item. When moving up, + // just use this as the newMenu and leave currentMenu null so that no + // check for a later element is performed. When moving down, set currentMenu + // so that we look for one page down from the first item. + newMenu = nsXULPopupManager::GetNextMenuItem(this, nullptr, true); + if (!aIsUp) { + currentMenu = newMenu; + } + } + + if (currentMenu) { + nscoord scrollHeight = mRect.height; + nsIScrollableFrame *scrollframe = GetScrollFrame(this); + if (scrollframe) { + scrollHeight = scrollframe->GetScrollPortRect().height; + } + + // Get the position of the current item and add or subtract one popup's + // height to or from it. + nscoord targetPosition = aIsUp ? currentMenu->GetRect().YMost() - scrollHeight : + currentMenu->GetRect().y + scrollHeight; + + // Indicates that the last visible child was a valid menuitem. + bool lastWasValid = false; + + // Look for the next child which is just past the target position. This child + // will need to be selected. + while (currentMenu) { + // Only consider menu frames. + nsMenuFrame* menuFrame = do_QueryFrame(currentMenu); + if (menuFrame && + nsXULPopupManager::IsValidMenuItem(menuFrame->GetContent(), true)) { + + // If the right position was found, break out. Otherwise, look for another item. + if ((!aIsUp && currentMenu->GetRect().YMost() > targetPosition) || + (aIsUp && currentMenu->GetRect().y < targetPosition)) { + + // If the last visible child was not a valid menuitem or was disabled, + // use this as the menu to select, skipping over any non-valid items at + // the edge of the page. + if (!lastWasValid) { + newMenu = menuFrame; + } + + break; + } + + // Assign this item to newMenu. This item will be selected in case we + // don't find any more. + lastWasValid = true; + newMenu = menuFrame; + } + else { + lastWasValid = false; + } + + currentMenu = aIsUp ? currentMenu->GetPrevSibling() : + currentMenu->GetNextSibling(); + } + } + + // Select the new menuitem. + if (newMenu) { + ChangeMenuItem(newMenu, false, true); + } +} + +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, + bool aFromKey) +{ + 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); + + // On Windows, a menulist should update its value whenever navigation was + // done by the keyboard. +#ifdef XP_WIN + if (aFromKey && IsOpen() && IsMenuList()) { + // Fire a command event as the new item, but we don't want to close + // the menu, blink it, or update any other state of the menuitem. The + // command event will cause the item to be selected. + nsContentUtils::DispatchXULCommand(aMenuItem->GetContent(), /* aTrusted = */ true, + nullptr, PresContext()->PresShell(), + false, false, false, false); + } +#endif + } + + mCurrentMenu = aMenuItem; + + return NS_OK; +} + +nsMenuFrame* +nsMenuPopupFrame::Enter(WidgetGUIEvent* 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. + auto insertion = PresContext()->PresShell()-> + FrameConstructor()->GetInsertionPoint(GetContent(), nullptr); + nsContainerFrame* immediateParent = insertion.mParentFrame; + 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); + + DOMTimeStamp keyTime; + aKeyEvent->AsEvent()->GetTimeStamp(&keyTime); + + if (charCode == 0) { + if (keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) { + 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 { + char16_t uniChar = ToLowerCase(static_cast<char16_t>(charCode)); + if (isMenu) { + // Menu supports only first-letter navigation + mIncrementalString = uniChar; + } else if (IsWithinIncrementalTime(keyTime)) { + mIncrementalString.Append(uniChar); + } else { + // Interval too long, treat as new typing + mIncrementalString = 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; + } + + sLastKeyTime = keyTime; + + // 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> + nsIFrame* firstMenuItem = nsXULPopupManager::GetNextMenuItem(immediateParent, nullptr, true); + nsIFrame* currFrame = firstMenuItem; + + 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(); + 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; + } + } + + nsMenuFrame* menu = do_QueryFrame(currFrame); + currFrame = nsXULPopupManager::GetNextMenuItem(immediateParent, menu, true); + if (currFrame == firstMenuItem) + break; + } + + 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 = PopupBoxObject::ROLLUP_DEFAULT; +} + +// helpers ///////////////////////////////////////////////////////////// + +nsresult +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(); + +#ifndef MOZ_GTK2 + if (aAttribute == nsGkAtoms::noautohide) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + pm->EnableRollup(mContent, !IsNoAutoHide()); + } +#endif + + 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 + // PopupBoxObject if possible. + nsAutoString left, top; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left); + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top); + nsresult err1, err2; + mozilla::CSSIntPoint pos(left.ToInteger(&err1), top.ToInteger(&err2)); + + if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2)) + MoveTo(pos, false); +} + +void +nsMenuPopupFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (mReflowCallbackData.mPosted) { + PresContext()->PresShell()->CancelReflowCallback(this); + mReflowCallbackData.Clear(); + } + + nsMenuFrame* menu = do_QueryFrame(GetParent()); + if (menu) { + // clear the open attribute on the parent menu + nsContentUtils::AddScriptRunner( + new nsUnsetAttrRunnable(menu->GetContent(), nsGkAtoms::open)); + } + + ClearPopupShownDispatcher(); + + 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(const CSSIntPoint& aPos, bool aUpdateAttrs) +{ + nsIWidget* widget = GetWidget(); + if ((mScreenRect.x == aPos.x && mScreenRect.y == aPos.y) && + (!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) { + margin.left += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt( + LookAndFeel::eIntID_ContextMenuOffsetHorizontal)); + margin.top += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt( + LookAndFeel::eIntID_ContextMenuOffsetVertical)); + } + + nsPresContext* presContext = PresContext(); + mAnchorType = MenuPopupAnchorType_Point; + mScreenRect.x = aPos.x - presContext->AppUnitsToIntCSSPixels(margin.left); + mScreenRect.y = aPos.y - presContext->AppUnitsToIntCSSPixels(margin.top); + + SetPopupPosition(nullptr, true, false, true); + + nsCOMPtr<nsIContent> popup = mContent; + if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) || + popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top))) + { + nsAutoString left, top; + left.AppendInt(aPos.x); + top.AppendInt(aPos.y); + 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(IsVisible(), "popup must be visible to move it"); + + nsPopupState oldstate = mPopupState; + InitializePopup(aAnchorContent, mTriggerContent, aPosition, + aXPos, aYPos, MenuPopupAnchorType_Node, aAttributesOverride); + // InitializePopup changed the state so reset it. + mPopupState = oldstate; + + // Pass false here so that flipping and adjusting to fit on the screen happen. + SetPopupPosition(nullptr, false, false, true); +} + +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 || + mPosition == POPUPPOSITION_SELECTION) + 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/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h new file mode 100644 index 000000000..b32073960 --- /dev/null +++ b/layout/xul/nsMenuPopupFrame.h @@ -0,0 +1,628 @@ +/* -*- 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" + +#include "Units.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 +// ePopupOpening - between the popupshowing event and being visible. Creation +// of the child frames, layout and reflow occurs in this +// state. The popup is stored in the popup manager's list of +// open popups during this state. +// ePopupVisible - layout is done and the popup's view and widget are made +// visible. The popup is visible on screen but may be +// transitioning. The popupshown event has not yet fired. +// ePopupShown - the popup has been shown and is fully ready. This state is +// assigned just before 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 waiting to be laid out and positioned + ePopupPositioning, + // state while a popup is open but the widget is not yet visible + ePopupOpening, + // state while a popup is visible and waiting for the popupshown event + ePopupVisible, + // state while a popup is open and visible on screen + ePopupShown, + // 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 +}; + +enum ConsumeOutsideClicksResult { + ConsumeOutsideClicks_ParentOnly = 0, // Only consume clicks on the parent anchor + ConsumeOutsideClicks_True = 1, // Always consume clicks + ConsumeOutsideClicks_Never = 2 // Never consume clicks +}; + +// 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 for the flip attribute +enum FlipType { + FlipType_Default = 0, + FlipType_None = 1, // don't try to flip or translate to stay onscreen + FlipType_Both = 2, // flip in both directions + FlipType_Slide = 3 // allow the arrow to "slide" instead of resizing +}; + +enum MenuPopupAnchorType { + MenuPopupAnchorType_Node = 0, // anchored to a node + MenuPopupAnchorType_Point = 1, // unanchored and positioned at a screen point + MenuPopupAnchorType_Rect = 2, // anchored at a screen rectangle +}; + +// 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_SELECTION 10 + +#define POPUPPOSITION_HFLIP(v) (v ^ 1) +#define POPUPPOSITION_VFLIP(v) (v ^ 2) + +nsIFrame* NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsView; +class nsMenuPopupFrame; + +// this class is used for dispatching popupshown events asynchronously. +class nsXULPopupShownEvent : public mozilla::Runnable, + public nsIDOMEventListener +{ +public: + nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext) + : mPopup(aPopup), mPresContext(aPresContext) + { + } + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIDOMEVENTLISTENER + + void CancelListener(); + +protected: + virtual ~nsXULPopupShownEvent() { } + +private: + nsCOMPtr<nsIContent> mPopup; + RefPtr<nsPresContext> mPresContext; +}; + +class nsMenuPopupFrame final : public nsBoxFrame, public nsMenuParent, + public nsIReflowCallback +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsMenuPopupFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + explicit nsMenuPopupFrame(nsStyleContext* aContext); + + // nsMenuParent interface + virtual nsMenuFrame* GetCurrentMenuItem() override; + NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) override; + virtual void CurrentMenuIsBeingDestroyed() override; + NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, + bool aSelectFirstItem, + bool aFromKey) 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) override { return NS_OK; } // We don't care. + virtual bool IsActive() override { return false; } + virtual bool IsMenuBar() 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 + * + */ + ConsumeOutsideClicksResult ConsumeOutsideClicks(); + + virtual bool IsContextMenu() override { return mIsContextMenu; } + + virtual bool MenuClosed() override { return true; } + + virtual void LockMenuUntilClosed(bool aLock) override; + virtual bool IsMenuLocked() override { return mIsMenuLocked; } + + nsIWidget* GetWidget(); + + // The dismissal listener gets created and attached to the window. + void AttachedDismissalListener(); + + // Overridden methods + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) 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(); + + virtual void SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) override; + + virtual bool IsLeaf() const override; + + // layout, position and display the popup as needed + void LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, + nsIFrame* aAnchor, 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), anchored at a + // rectangle, or at a specific point if a screen position is 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. If aNotify is true, then + // a popuppositioned event is sent. + nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, + bool aSizedToPopup, bool aNotify); + + 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(mozilla::WidgetGUIEvent* aEvent); + + nsPopupType PopupType() const { return mPopupType; } + bool IsMenu() override { return mPopupType == ePopupTypeMenu; } + bool IsOpen() override { return mPopupState == ePopupOpening || + mPopupState == ePopupVisible || + mPopupState == ePopupShown; } + bool IsVisible() { return mPopupState == ePopupVisible || + mPopupState == ePopupShown; } + + // Return true if the popup is for a menulist. + bool IsMenuList(); + + bool IsMouseTransparent() { return mMouseTransparent; } + + 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, + MenuPopupAnchorType aAnchorType, + bool aAttributesOverride); + + void InitializePopupAtRect(nsIContent* aTriggerContent, + const nsAString& aPosition, + const nsIntRect& aRect, + 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); + // 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(); } + static bool IsWithinIncrementalTime(DOMTimeStamp time) { + return !sTimeoutOfIncrementalSearch || time - sLastKeyTime <= sTimeoutOfIncrementalSearch; + } + + virtual nsIAtom* GetType() const override { return nsGkAtoms::menuPopupFrame; } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("MenuPopup"), aResult); + } +#endif + + void EnsureMenuItemIsVisible(nsMenuFrame* aMenuFrame); + + void ChangeByPage(bool aIsUp); + + // Move the popup to the screen coordinate |aPos| 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(const mozilla::CSSIntPoint& aPos, 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); + + void SetOverrideConstraintRect(mozilla::LayoutDeviceIntRect aRect) { + mOverrideConstraintRect = ToAppUnits(aRect, PresContext()->AppUnitsPerCSSPixel()); + } + + // 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. + // For non-toplevel popups (which will always be panels), we will also + // constrain them to the available screen rect, ie they will not fall + // underneath the taskbar, dock or other fixed OS elements. + // This operates in device pixels. + mozilla::LayoutDeviceIntRect + GetConstraintRect(const mozilla::LayoutDeviceIntRect& aAnchorRect, + const mozilla::LayoutDeviceIntRect& aRootScreenRect, + nsPopupLevel aPopupLevel); + + // 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, + mozilla::LayoutDeviceIntPoint& aChange); + + // Return true if the popup is positioned relative to an anchor. + bool IsAnchored() const { return mAnchorType != MenuPopupAnchorType_Point; } + + // Return the anchor if there is one. + nsIContent* GetAnchor() const { return mAnchorContent; } + + // Return the screen coordinates in CSS pixels of the popup, + // or (-1, -1, 0, 0) if anchored. + nsIntRect GetScreenAnchorRect() const { return mScreenRect; } + + mozilla::LayoutDeviceIntPoint 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; } + + // Clear the mPopupShownDispatcher, remove the listener and return true if + // mPopupShownDispatcher was non-null. + bool ClearPopupShownDispatcher() + { + if (mPopupShownDispatcher) { + mPopupShownDispatcher->CancelListener(); + mPopupShownDispatcher = nullptr; + return true; + } + + return false; + } + + void ShowWithPositionedEvent() { + mPopupState = ePopupPositioning; + mShouldAutoPosition = true; + } + + // nsIReflowCallback + virtual bool ReflowFinished() override; + virtual void ReflowCallbackCanceled() override; + +protected: + + // returns the popup's level. + nsPopupLevel PopupLevel(bool aIsNoAutoHide) const; + + // redefine to tell the box system not to move the views. + virtual uint32_t GetXULLayoutFlags() 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); + + // For popups that are going to align to their selected item, get the frame of + // the selected item. + nsIFrame* GetSelectedItemForAlignment(); + + // 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 aIsOnEnd, 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. + + RefPtr<nsXULPopupShownEvent> mPopupShownDispatcher; + + // The popup's screen rectangle in app units. + nsIntRect mUsedScreenRect; + + // 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; + nsIntRect mScreenRect; + + // 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. + mozilla::LayoutDeviceIntPoint 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 PopupBoxObject::ROLLUP_DEFAULT/ROLLUP_CONSUME/ROLLUP_NO_CONSUME + uint8_t mConsumeRollupEvent; + FlipType mFlip; // Whether to flip + + struct ReflowCallbackData { + ReflowCallbackData() : + mPosted(false), + mAnchor(nullptr), + mSizedToPopup(false) + {} + void MarkPosted(nsIFrame* aAnchor, bool aSizedToPopup) { + mPosted = true; + mAnchor = aAnchor; + mSizedToPopup = aSizedToPopup; + } + void Clear() { + mPosted = false; + mAnchor = nullptr; + mSizedToPopup = false; + } + bool mPosted; + nsIFrame* mAnchor; + bool mSizedToPopup; + }; + ReflowCallbackData mReflowCallbackData; + + 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 mMouseTransparent; // True if this is a popup is transparent to mouse events + + // the flip modes that were used when the popup was opened + bool mHFlip; + bool mVFlip; + + // How the popup is anchored. + MenuPopupAnchorType mAnchorType; + + nsRect mOverrideConstraintRect; + + static int8_t sDefaultLevelIsTop; + + static DOMTimeStamp sLastKeyTime; + + // If 0, never timed out. Otherwise, the value is in milliseconds. + static uint32_t sTimeoutOfIncrementalSearch; +}; // class nsMenuPopupFrame + +#endif diff --git a/layout/xul/nsPIBoxObject.h b/layout/xul/nsPIBoxObject.h new file mode 100644 index 000000000..46ec6ea38 --- /dev/null +++ b/layout/xul/nsPIBoxObject.h @@ -0,0 +1,37 @@ +/* -*- 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___ + +#include "nsIBoxObject.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/nsPIListBoxObject.h b/layout/xul/nsPIListBoxObject.h new file mode 100644 index 000000000..c7b9928f0 --- /dev/null +++ b/layout/xul/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/nsPopupSetFrame.cpp b/layout/xul/nsPopupSetFrame.cpp new file mode 100644 index 000000000..a627ee079 --- /dev/null +++ b/layout/xul/nsPopupSetFrame.cpp @@ -0,0 +1,160 @@ +/* -*- 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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsPopupSetFrame) + +void +nsPopupSetFrame::Init(nsIContent* aContent, + nsContainerFrame* 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; +} + +void +nsPopupSetFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + if (aListID == kPopupList) { + AddPopupFrameList(aFrameList); + return; + } + nsBoxFrame::AppendFrames(aListID, aFrameList); +} + +void +nsPopupSetFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + if (aListID == kPopupList) { + RemovePopupFrame(aOldFrame); + return; + } + nsBoxFrame::RemoveFrame(aListID, aOldFrame); +} + +void +nsPopupSetFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + if (aListID == kPopupList) { + AddPopupFrameList(aFrameList); + return; + } + nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); +} + +void +nsPopupSetFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + if (aListID == kPopupList) { + NS_ASSERTION(mPopupList.IsEmpty(), + "SetInitialChildList on non-empty child list"); + AddPopupFrameList(aChildList); + return; + } + nsBoxFrame::SetInitialChildList(aListID, aChildList); +} + +const nsFrameList& +nsPopupSetFrame::GetChildList(ChildListID aListID) const +{ + if (kPopupList == aListID) { + return mPopupList; + } + return nsBoxFrame::GetChildList(aListID); +} + +void +nsPopupSetFrame::GetChildLists(nsTArray<ChildList>* aLists) const +{ + nsBoxFrame::GetChildLists(aLists); + mPopupList.AppendIfNonempty(aLists, kPopupList); +} + +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::DoXULLayout(nsBoxLayoutState& aState) +{ + // lay us out + nsresult rv = nsBoxFrame::DoXULLayout(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, 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); +} diff --git a/layout/xul/nsPopupSetFrame.h b/layout/xul/nsPopupSetFrame.h new file mode 100644 index 000000000..1981b996d --- /dev/null +++ b/layout/xul/nsPopupSetFrame.h @@ -0,0 +1,64 @@ +/* -*- 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 + + explicit nsPopupSetFrame(nsStyleContext* aContext): + nsBoxFrame(aContext) {} + + ~nsPopupSetFrame() {} + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) override; + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + + virtual const nsFrameList& GetChildList(ChildListID aList) const override; + virtual void GetChildLists(nsTArray<ChildList>* aLists) const override; + + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override; + + // Used to destroy our popup frames. + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const 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/nsProgressMeterFrame.cpp b/layout/xul/nsProgressMeterFrame.cpp new file mode 100644 index 000000000..a798d5717 --- /dev/null +++ b/layout/xul/nsProgressMeterFrame.cpp @@ -0,0 +1,184 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsCOMPtr.h" +#include "nsBoxLayoutState.h" +#include "nsIReflowCallback.h" +#include "nsContentUtils.h" +#include "mozilla/Attributes.h" + +class nsReflowFrameRunnable : public mozilla::Runnable +{ +public: + nsReflowFrameRunnable(nsIFrame* aFrame, + nsIPresShell::IntrinsicDirty aIntrinsicDirty, + nsFrameState aBitToAdd); + + NS_DECL_NSIRUNNABLE + + nsWeakFrame mWeakFrame; + nsIPresShell::IntrinsicDirty mIntrinsicDirty; + nsFrameState mBitToAdd; +}; + +nsReflowFrameRunnable::nsReflowFrameRunnable(nsIFrame* aFrame, + nsIPresShell::IntrinsicDirty aIntrinsicDirty, + nsFrameState aBitToAdd) + : mWeakFrame(aFrame), + mIntrinsicDirty(aIntrinsicDirty), + mBitToAdd(aBitToAdd) +{ +} + +NS_IMETHODIMP +nsReflowFrameRunnable::Run() +{ + if (mWeakFrame.IsAlive()) { + mWeakFrame->PresContext()->PresShell()-> + FrameNeedsReflow(mWeakFrame, mIntrinsicDirty, mBitToAdd); + } + return NS_OK; +} + +// +// NS_NewToolbarFrame +// +// Creates a new Toolbar frame and returns it +// +nsIFrame* +NS_NewProgressMeterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsProgressMeterFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsProgressMeterFrame) + +// +// nsProgressMeterFrame dstr +// +// Cleanup, if necessary +// +nsProgressMeterFrame :: ~nsProgressMeterFrame ( ) +{ +} + +class nsAsyncProgressMeterInit final : public nsIReflowCallback +{ +public: + explicit nsAsyncProgressMeterInit(nsIFrame* aFrame) : mWeakFrame(aFrame) {} + + virtual bool ReflowFinished() override + { + 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() override + { + delete this; + } + + nsWeakFrame mWeakFrame; +}; + +NS_IMETHODIMP +nsProgressMeterFrame::DoXULLayout(nsBoxLayoutState& aState) +{ + if (mNeedsReflowCallback) { + nsIReflowCallback* cb = new nsAsyncProgressMeterInit(this); + if (cb) { + PresContext()->PresShell()->PostReflowCallback(cb); + } + mNeedsReflowCallback = false; + } + return nsBoxFrame::DoXULLayout(aState); +} + +nsresult +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 = PrincipalChildList().FirstChild(); + 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_FRAME_DUMP +nsresult +nsProgressMeterFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("ProgressMeter"), aResult); +} +#endif diff --git a/layout/xul/nsProgressMeterFrame.h b/layout/xul/nsProgressMeterFrame.h new file mode 100644 index 000000000..0a4af7ff4 --- /dev/null +++ b/layout/xul/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 DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + +protected: + explicit nsProgressMeterFrame(nsStyleContext* aContext) : + nsBoxFrame(aContext), mNeedsReflowCallback(true) {} + virtual ~nsProgressMeterFrame(); + + bool mNeedsReflowCallback; +}; // class nsProgressMeterFrame diff --git a/layout/xul/nsRepeatService.cpp b/layout/xul/nsRepeatService.cpp new file mode 100644 index 000000000..919763d4d --- /dev/null +++ b/layout/xul/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_ISUPPORTS(nsRepeatService, nsITimerCallback) diff --git a/layout/xul/nsRepeatService.h b/layout/xul/nsRepeatService.h new file mode 100644 index 000000000..81321a472 --- /dev/null +++ b/layout/xul/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 final : 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 + +protected: + nsRepeatService(); + virtual ~nsRepeatService(); + +private: + Callback mCallback; + void* mCallbackData; + nsCOMPtr<nsITimer> mRepeatTimer; + static nsRepeatService* gInstance; + +}; // class nsRepeatService + +#endif diff --git a/layout/xul/nsResizerFrame.cpp b/layout/xul/nsResizerFrame.cpp new file mode 100644 index 000000000..b32b84fe5 --- /dev/null +++ b/layout/xul/nsResizerFrame.cpp @@ -0,0 +1,538 @@ +/* -*- 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 "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIServiceManager.h" +#include "nsResizerFrame.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMNodeList.h" +#include "nsGkAtoms.h" +#include "nsNameSpaceManager.h" +#include "nsIDOMCSSStyleDeclaration.h" + +#include "nsPresContext.h" +#include "nsFrameManager.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIBaseWindow.h" +#include "nsPIDOMWindow.h" +#include "mozilla/MouseEvents.h" +#include "nsContentUtils.h" +#include "nsMenuPopupFrame.h" +#include "nsIScreenManager.h" +#include "mozilla/dom/Element.h" +#include "nsError.h" +#include "nsICSSDeclaration.h" +#include "nsStyledElement.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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsResizerFrame) + +nsResizerFrame::nsResizerFrame(nsStyleContext* aContext) +:nsTitleBarFrame(aContext) +{ +} + +nsresult +nsResizerFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + nsWeakFrame weakFrame(this); + bool doDefault = true; + + switch (aEvent->mMessage) { + case eTouchStart: + case eMouseDown: { + if (aEvent->mClass == eTouchEventClass || + (aEvent->mClass == eMouseEventClass && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::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(); + if (frameToResize->StylePosition()->mBoxSizing == StyleBoxSizing::Content) { + rect.Deflate(frameToResize->GetUsedBorderAndPadding()); + } + + mMouseDownRect = + LayoutDeviceIntRect::FromAppUnitsToNearest(rect, 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->mWidget->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 + LayoutDeviceIntPoint refPoint; + if (!GetEventPoint(aEvent, refPoint)) + return NS_OK; + mMouseDownPoint = refPoint + aEvent->mWidget->WidgetToScreenOffset(); + + // we're tracking + mTrackingMouseMove = true; + + nsIPresShell::SetCapturingContent(GetContent(), CAPTURE_IGNOREALLOWED); + } + } + break; + + case eTouchEnd: + case eMouseUp: { + if (aEvent->mClass == eTouchEventClass || + (aEvent->mClass == eMouseEventClass && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton)) { + // we're done tracking. + mTrackingMouseMove = false; + + nsIPresShell::SetCapturingContent(nullptr, 0); + + doDefault = false; + } + } + break; + + case eTouchMove: + case eMouseMove: { + 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 + LayoutDeviceIntPoint refPoint; + if (!GetEventPoint(aEvent, refPoint)) + return NS_OK; + LayoutDeviceIntPoint screenPoint = + refPoint + aEvent->mWidget->WidgetToScreenOffset(); + LayoutDeviceIntPoint 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 + } + + LayoutDeviceIntRect 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) { + LayoutDeviceIntRect 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(); + + nsPopupLevel popupLevel = menuPopupFrame->PopupLevel(); + int32_t appPerDev = aPresContext->AppUnitsPerDevPixel(); + LayoutDeviceIntRect screenRect = menuPopupFrame->GetConstraintRect + (LayoutDeviceIntRect::FromAppUnitsToNearest(frameRect, appPerDev), + // round using ...ToInside 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. + LayoutDeviceIntRect::FromAppUnitsToInside(rootScreenRect, appPerDev), + popupLevel); + rect.IntersectRect(rect, screenRect); + } + + 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 = ToAppUnits(rect.ToUnknownRect(), 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()); + + LayoutDeviceIntRect oldRect; + nsWeakFrame weakFrame(menuPopupFrame); + if (menuPopupFrame) { + nsCOMPtr<nsIWidget> widget = menuPopupFrame->GetWidget(); + if (widget) + oldRect = widget->GetScreenBounds(); + + // convert the new rectangle into outer window coordinates + LayoutDeviceIntPoint 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)) { + + CSSPoint cssPos = rect.TopLeft() / aPresContext->CSSToDevPixelScale(); + menuPopupFrame->MoveTo(RoundedToInt(cssPos), true); + } + } + else { + window->SetPositionAndSize(rect.x, rect.y, rect.width, rect.height, + nsIBaseWindow::eRepaint); // do the repaint. + } + + doDefault = false; + } + } + break; + + case eMouseClick: { + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent->IsLeftClickEvent()) { + MouseClicked(mouseEvent); + } + break; + } + case eMouseDoubleClick: + if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::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; + + default: + 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 + nsCOMPtr<nsIDocShellTreeItem> dsti = aPresShell->GetPresContext()->GetDocShell(); + if (!dsti || dsti->ItemType() != nsIDocShellTreeItem::typeChrome) { + // 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? + if (nsPIDOMWindowOuter* domWindow = aPresShell->GetDocument()->GetWindow()) { + 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->IsXULElement()) { + 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<nsStyledElement> inlineStyleContent = + do_QueryInterface(aContent); + if (inlineStyleContent) { + nsICSSDeclaration* decl = inlineStyleContent->Style(); + + 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::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(), + nsINode::DeleteProperty<nsResizerFrame::SizeInfo>); + 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 + } + + if (index >= 8) { + // Directions 8 and higher are RTL-aware directions and should reverse the + // horizontal component if RTL. + WritingMode wm = GetWritingMode(); + if (!(wm.IsVertical() ? wm.IsVerticalLR() : wm.IsBidiLTR())) { + Direction direction = directions[index]; + direction.mHorizontal *= -1; + return direction; + } + } + + return directions[index]; +} + +void +nsResizerFrame::MouseClicked(WidgetMouseEvent* aEvent) +{ + // Execute the oncommand event handler. + nsContentUtils::DispatchXULCommand(mContent, aEvent && aEvent->IsTrusted()); +} diff --git a/layout/xul/nsResizerFrame.h b/layout/xul/nsResizerFrame.h new file mode 100644 index 000000000..92656bc76 --- /dev/null +++ b/layout/xul/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 "mozilla/EventForwards.h" +#include "nsTitleBarFrame.h" + +class nsIBaseWindow; + +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); + + explicit nsResizerFrame(nsStyleContext* aContext); + + virtual nsresult HandleEvent(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual void MouseClicked(mozilla::WidgetMouseEvent* aEvent) 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: + LayoutDeviceIntRect mMouseDownRect; + LayoutDeviceIntPoint mMouseDownPoint; +}; // class nsResizerFrame + +#endif /* nsResizerFrame_h___ */ diff --git a/layout/xul/nsRootBoxFrame.cpp b/layout/xul/nsRootBoxFrame.cpp new file mode 100644 index 000000000..fe41dce52 --- /dev/null +++ b/layout/xul/nsRootBoxFrame.cpp @@ -0,0 +1,291 @@ +/* -*- 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 "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" +#include "mozilla/BasicEvents.h" + +using namespace mozilla; + +// 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->PrincipalChildList().FirstChild(); + } + + nsIRootBox* rootBox = do_QueryFrame(rootFrame); + return rootBox; +} + +class nsRootBoxFrame : public nsBoxFrame, public nsIRootBox { +public: + + friend nsIFrame* NS_NewBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + explicit nsRootBoxFrame(nsStyleContext* aContext); + + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + virtual nsPopupSetFrame* GetPopupSetFrame() override; + virtual void SetPopupSetFrame(nsPopupSetFrame* aPopupSet) override; + virtual nsIContent* GetDefaultTooltip() override; + virtual void SetDefaultTooltip(nsIContent* aTooltip) override; + virtual nsresult AddTooltipSupport(nsIContent* aNode) override; + virtual nsresult RemoveTooltipSupport(nsIContent* aNode) override; + + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + virtual nsresult HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::rootFrame + */ + virtual nsIAtom* GetType() const override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + // Override bogus IsFrameOfType in nsBoxFrame. + if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced)) + return false; + return nsBoxFrame::IsFrameOfType(aFlags); + } + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + nsPopupSetFrame* mPopupSetFrame; + +protected: + nsIContent* mDefaultTooltip; +}; + +//---------------------------------------------------------------------- + +nsContainerFrame* +NS_NewRootBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsRootBoxFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsRootBoxFrame) + +nsRootBoxFrame::nsRootBoxFrame(nsStyleContext* aContext): + nsBoxFrame(aContext, true) +{ + mPopupSetFrame = nullptr; + + nsCOMPtr<nsBoxLayout> layout; + NS_NewStackLayout(layout); + SetXULLayoutManager(layout); +} + +void +nsRootBoxFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list ID"); + MOZ_ASSERT(mFrames.IsEmpty(), "already have a child frame"); + nsBoxFrame::AppendFrames(aListID, aFrameList); +} + +void +nsRootBoxFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + // Because we only support a single child frame inserting is the same + // as appending. + MOZ_ASSERT(!aPrevFrame, "unexpected previous sibling frame"); + AppendFrames(aListID, aFrameList); +} + +void +nsRootBoxFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list ID"); + if (aOldFrame == mFrames.FirstChild()) { + nsBoxFrame::RemoveFrame(aListID, aOldFrame); + } else { + MOZ_CRASH("unknown aOldFrame"); + } +} + +#ifdef DEBUG_REFLOW +int32_t gReflows = 0; +#endif + +void +nsRootBoxFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + DO_GLOBAL_REFLOW_COUNT("nsRootBoxFrame"); + +#ifdef DEBUG_REFLOW + gReflows++; + printf("----Reflow %d----\n", gReflows); +#endif + return nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus); +} + +void +nsRootBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (mContent && mContent->GetProperty(nsGkAtoms::DisplayPortMargins)) { + // The XUL document's root element may have displayport margins set in + // ChromeProcessController::InitializeRoot, and we should to supply the + // base rect. + nsRect displayPortBase = aDirtyRect.Intersect(nsRect(nsPoint(0, 0), GetSize())); + nsLayoutUtils::SetDisplayPortBase(mContent, displayPortBase); + } + + // 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); +} + +nsresult +nsRootBoxFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + if (aEvent->mMessage == eMouseUp) { + 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_FRAME_DUMP +nsresult +nsRootBoxFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("RootBox"), aResult); +} +#endif diff --git a/layout/xul/nsScrollBoxFrame.cpp b/layout/xul/nsScrollBoxFrame.cpp new file mode 100644 index 000000000..4a6c9c2a8 --- /dev/null +++ b/layout/xul/nsScrollBoxFrame.cpp @@ -0,0 +1,178 @@ +/* -*- 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 "nsButtonBoxFrame.h" +#include "nsITimer.h" +#include "nsRepeatService.h" +#include "mozilla/MouseEvents.h" +#include "nsIContent.h" + +using namespace mozilla; + +class nsAutoRepeatBoxFrame : public nsButtonBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewAutoRepeatBoxFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual nsresult HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + NS_IMETHOD HandlePress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + +protected: + explicit nsAutoRepeatBoxFrame(nsStyleContext* aContext): + nsButtonBoxFrame(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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsAutoRepeatBoxFrame) + +nsresult +nsAutoRepeatBoxFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + switch(aEvent->mMessage) { + // 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 eMouseEnterIntoWidget: + case eMouseOver: + if (IsActivatedOnHover()) { + StartRepeat(); + mTrustedEvent = aEvent->IsTrusted(); + } + break; + + case eMouseExitFromWidget: + case eMouseOut: + // always stop on mouse exit + StopRepeat(); + // Not really necessary but do this to be safe + mTrustedEvent = false; + break; + + case eMouseClick: { + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent->IsLeftClickEvent()) { + // skip button frame handling to prevent click handling + return nsBoxFrame::HandleEvent(aPresContext, mouseEvent, aEventStatus); + } + break; + } + + default: + break; + } + + return nsButtonBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +NS_IMETHODIMP +nsAutoRepeatBoxFrame::HandlePress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + if (!IsActivatedOnHover()) { + StartRepeat(); + mTrustedEvent = aEvent->IsTrusted(); + DoMouseClick(aEvent, mTrustedEvent); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAutoRepeatBoxFrame::HandleRelease(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + if (!IsActivatedOnHover()) { + StopRepeat(); + } + return NS_OK; +} + +nsresult +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/nsScrollbarButtonFrame.cpp b/layout/xul/nsScrollbarButtonFrame.cpp new file mode 100644 index 000000000..206d9717f --- /dev/null +++ b/layout/xul/nsScrollbarButtonFrame.cpp @@ -0,0 +1,298 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsSliderFrame.h" +#include "nsScrollbarFrame.h" +#include "nsIScrollbarMediator.h" +#include "nsRepeatService.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Telemetry.h" +#include "mozilla/layers/ScrollInputMethods.h" + +using namespace mozilla; +using mozilla::layers::ScrollInputMethod; + +// +// NS_NewToolbarFrame +// +// Creates a new Toolbar frame and returns it +// +nsIFrame* +NS_NewScrollbarButtonFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsScrollbarButtonFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarButtonFrame) + +nsresult +nsScrollbarButtonFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* 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->mMessage) { + case eMouseDown: + 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 eMouseUp: + HandleRelease(aPresContext, aEvent, aEventStatus); + break; + case eMouseOut: + mCursorOnThis = false; + break; + case eMouseMove: { + nsPoint cursor = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this); + nsRect frameRect(nsPoint(0, 0), GetSize()); + mCursorOnThis = frameRect.Contains(cursor); + break; + } + default: + break; + } + + return nsButtonBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +bool +nsScrollbarButtonFrame::HandleButtonPress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + // Get the desired action for the scrollbar button. + LookAndFeel::IntID tmpAction; + uint16_t button = aEvent->AsMouseEvent()->button; + if (button == WidgetMouseEvent::eLeftButton) { + tmpAction = LookAndFeel::eIntID_ScrollButtonLeftMouseButtonAction; + } else if (button == WidgetMouseEvent::eMiddleButton) { + tmpAction = LookAndFeel::eIntID_ScrollButtonMiddleMouseButtonAction; + } else if (button == WidgetMouseEvent::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; + + 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; + + bool repeat = pressedButtonAction != 2; + // 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()) { + return false; + } + + nsScrollbarFrame* sb = do_QueryFrame(scrollbar); + if (sb) { + nsIScrollbarMediator* m = sb->GetScrollbarMediator(); + switch (pressedButtonAction) { + case 0: + sb->SetIncrementToLine(direction); + if (m) { + m->ScrollByLine(sb, direction, nsIScrollbarMediator::ENABLE_SNAP); + } + break; + case 1: + sb->SetIncrementToPage(direction); + if (m) { + m->ScrollByPage(sb, direction, nsIScrollbarMediator::ENABLE_SNAP); + } + break; + case 2: + sb->SetIncrementToWhole(direction); + if (m) { + m->ScrollByWhole(sb, direction, nsIScrollbarMediator::ENABLE_SNAP); + } + 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; + } + if (!weakFrame.IsAlive()) { + return false; + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::MainThreadScrollbarButtonClick); + + if (!m) { + sb->MoveToNewPosition(); + if (!weakFrame.IsAlive()) { + return false; + } + } + } + if (repeat) { + StartRepeat(); + } + return true; +} + +NS_IMETHODIMP +nsScrollbarButtonFrame::HandleRelease(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + nsIPresShell::SetCapturingContent(nullptr, 0); + // we're not active anymore + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true); + StopRepeat(); + nsIFrame* scrollbar; + GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar); + nsScrollbarFrame* sb = do_QueryFrame(scrollbar); + if (sb) { + nsIScrollbarMediator* m = sb->GetScrollbarMediator(); + if (m) { + m->ScrollbarReleased(sb); + } + } + return NS_OK; +} + +void nsScrollbarButtonFrame::Notify() +{ + if (mCursorOnThis || + LookAndFeel::GetInt( + LookAndFeel::eIntID_ScrollbarButtonAutoRepeatBehavior, 0)) { + // get the scrollbar control + nsIFrame* scrollbar; + GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar); + nsScrollbarFrame* sb = do_QueryFrame(scrollbar); + if (sb) { + nsIScrollbarMediator* m = sb->GetScrollbarMediator(); + if (m) { + m->RepeatButtonScroll(sb); + } else { + sb->MoveToNewPosition(); + } + } + } +} + +void +nsScrollbarButtonFrame::MouseClicked(WidgetGUIEvent* aEvent) +{ + nsButtonBoxFrame::MouseClicked(aEvent); + //MouseClicked(); +} + +nsresult +nsScrollbarButtonFrame::GetChildWithTag(nsIAtom* atom, nsIFrame* start, + nsIFrame*& result) +{ + // recursively search our children + for (nsIFrame* childFrame : start->PrincipalChildList()) + { + // get the content node + nsIContent* child = childFrame->GetContent(); + + if (child) { + // see if it is the child + if (child->IsXULElement(atom)) + { + result = childFrame; + + return NS_OK; + } + } + + // recursive search the child + GetChildWithTag(atom, childFrame, result); + if (result != nullptr) + return NS_OK; + } + + 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->IsXULElement(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/nsScrollbarButtonFrame.h b/layout/xul/nsScrollbarButtonFrame.h new file mode 100644 index 000000000..9be73b13c --- /dev/null +++ b/layout/xul/nsScrollbarButtonFrame.h @@ -0,0 +1,81 @@ +/* -*- 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 nsScrollbarButtonFrame : public nsButtonBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + explicit nsScrollbarButtonFrame(nsStyleContext* aContext): + nsButtonBoxFrame(aContext), mCursorOnThis(false) {} + + // Overrides + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + friend nsIFrame* NS_NewScrollbarButtonFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + virtual nsresult HandleEvent(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + static nsresult GetChildWithTag(nsIAtom* atom, nsIFrame* start, nsIFrame*& result); + static nsresult GetParentWithTag(nsIAtom* atom, nsIFrame* start, nsIFrame*& result); + + bool HandleButtonPress(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus); + + NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) override + { + return NS_OK; + } + + NS_IMETHOD HandleDrag(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override + { + return NS_OK; + } + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + +protected: + virtual void MouseClicked(mozilla::WidgetGUIEvent* aEvent) override; + + 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(); + } + + bool mCursorOnThis; +}; + +#endif diff --git a/layout/xul/nsScrollbarFrame.cpp b/layout/xul/nsScrollbarFrame.cpp new file mode 100644 index 000000000..d9c28a2bf --- /dev/null +++ b/layout/xul/nsScrollbarFrame.cpp @@ -0,0 +1,311 @@ +/* -*- 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 "nsSliderFrame.h" +#include "nsScrollbarButtonFrame.h" +#include "nsGkAtoms.h" +#include "nsIScrollableFrame.h" +#include "nsIScrollbarMediator.h" +#include "mozilla/LookAndFeel.h" +#include "nsThemeConstants.h" +#include "nsIContent.h" +#include "nsIDOMMutationEvent.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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarFrame) + +NS_QUERYFRAME_HEAD(nsScrollbarFrame) + NS_QUERYFRAME_ENTRY(nsScrollbarFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +void +nsScrollbarFrame::Init(nsIContent* aContent, + nsContainerFrame* 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; +} + +void +nsScrollbarFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus); + + // nsGfxScrollFrame may have told us to shrink to nothing. If so, make sure our + // desired size agrees. + if (aReflowInput.AvailableWidth() == 0) { + aDesiredSize.Width() = 0; + } + if (aReflowInput.AvailableHeight() == 0) { + aDesiredSize.Height() = 0; + } +} + +nsIAtom* +nsScrollbarFrame::GetType() const +{ + return nsGkAtoms::scrollbarFrame; +} + +nsresult +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> content(mContent); + scrollable->CurPosAttributeChanged(content); + return rv; +} + +NS_IMETHODIMP +nsScrollbarFrame::HandlePress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsScrollbarFrame::HandleMultiplePress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsScrollbarFrame::HandleDrag(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsScrollbarFrame::HandleRelease(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +void +nsScrollbarFrame::SetScrollbarMediatorContent(nsIContent* aMediator) +{ + mScrollbarMediator = aMediator; +} + +nsIScrollbarMediator* +nsScrollbarFrame::GetScrollbarMediator() +{ + if (!mScrollbarMediator) { + return nullptr; + } + nsIFrame* f = mScrollbarMediator->GetPrimaryFrame(); + nsIScrollableFrame* scrollFrame = do_QueryFrame(f); + nsIScrollbarMediator* sbm; + + if (scrollFrame) { + nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); + sbm = do_QueryFrame(scrolledFrame); + if (sbm) { + return sbm; + } + } + sbm = do_QueryFrame(f); + if (f && !sbm) { + f = f->PresContext()->PresShell()->GetRootScrollFrame(); + if (f && f->GetContent() == mScrollbarMediator) { + return do_QueryFrame(f); + } + } + return sbm; +} + +nsresult +nsScrollbarFrame::GetXULMargin(nsMargin& aMargin) +{ + nsresult rv = NS_ERROR_FAILURE; + aMargin.SizeTo(0,0,0,0); + + if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { + nsPresContext* presContext = PresContext(); + nsITheme* theme = presContext->GetTheme(); + if (theme) { + LayoutDeviceIntSize size; + bool isOverridable; + theme->GetMinimumWidgetSize(presContext, this, NS_THEME_SCROLLBAR, &size, + &isOverridable); + if (IsXULHorizontal()) { + aMargin.top = -presContext->DevPixelsToAppUnits(size.height); + } + else { + aMargin.left = -presContext->DevPixelsToAppUnits(size.width); + } + rv = NS_OK; + } + } + + if (NS_FAILED(rv)) { + rv = nsBox::GetXULMargin(aMargin); + } + + if (NS_SUCCEEDED(rv) && !IsXULHorizontal()) { + nsIScrollbarMediator* scrollFrame = GetScrollbarMediator(); + if (scrollFrame && !scrollFrame->IsScrollbarOnRight()) { + Swap(aMargin.left, aMargin.right); + } + } + + return rv; +} + +void +nsScrollbarFrame::SetIncrementToLine(int32_t aDirection) +{ + // get the scrollbar's content node + nsIContent* content = GetContent(); + mSmoothScroll = true; + mIncrement = aDirection * nsSliderFrame::GetIncrement(content); +} + +void +nsScrollbarFrame::SetIncrementToPage(int32_t aDirection) +{ + // get the scrollbar's content node + nsIContent* content = GetContent(); + mSmoothScroll = true; + mIncrement = aDirection * nsSliderFrame::GetPageIncrement(content); +} + +void +nsScrollbarFrame::SetIncrementToWhole(int32_t aDirection) +{ + // get the scrollbar's content node + nsIContent* content = GetContent(); + if (aDirection == -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. + mSmoothScroll = false; +} + +int32_t +nsScrollbarFrame::MoveToNewPosition() +{ + // get the scrollbar's content node + nsCOMPtr<nsIContent> content = GetContent(); + + // get the current pos + int32_t curpos = nsSliderFrame::GetCurrentPosition(content); + + // get the max pos + int32_t maxpos = nsSliderFrame::GetMaxPosition(content); + + // save the old curpos + int32_t oldCurpos = curpos; + + // 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; + } + + // set the current position of the slider. + nsAutoString curposStr; + curposStr.AppendInt(curpos); + + nsWeakFrame weakFrame(this); + if (mSmoothScroll) { + content->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false); + } + content->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curposStr, false); + // notify the nsScrollbarFrame of the change + AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos, nsIDOMMutationEvent::MODIFICATION); + if (!weakFrame.IsAlive()) { + return curpos; + } + // notify all nsSliderFrames of the change + nsIFrame::ChildListIterator childLists(this); + for (; !childLists.IsDone(); childLists.Next()) { + nsFrameList::Enumerator childFrames(childLists.CurrentList()); + for (; !childFrames.AtEnd(); childFrames.Next()) { + nsIFrame* f = childFrames.get(); + nsSliderFrame* sliderFrame = do_QueryFrame(f); + if (sliderFrame) { + sliderFrame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos, nsIDOMMutationEvent::MODIFICATION); + if (!weakFrame.IsAlive()) { + return curpos; + } + } + } + } + // See if we have appearance information for a theme. + const nsStyleDisplay* disp = StyleDisplay(); + nsPresContext* presContext = PresContext(); + if (disp->mAppearance) { + nsITheme *theme = presContext->GetTheme(); + if (theme && theme->ThemeSupportsWidget(presContext, this, disp->mAppearance)) { + bool repaint; + nsAttrValue oldValue; + oldValue.SetTo(oldCurpos); + theme->WidgetStateChanged(this, disp->mAppearance, nsGkAtoms::curpos, + &repaint, &oldValue); + } + } + content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false); + return curpos; +} diff --git a/layout/xul/nsScrollbarFrame.h b/layout/xul/nsScrollbarFrame.h new file mode 100644 index 000000000..4048bc05b --- /dev/null +++ b/layout/xul/nsScrollbarFrame.h @@ -0,0 +1,110 @@ +/* -*- 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: + explicit nsScrollbarFrame(nsStyleContext* aContext): + nsBoxFrame(aContext), mScrollbarMediator(nullptr) {} + + NS_DECL_QUERYFRAME_TARGET(nsScrollbarFrame) + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(NS_LITERAL_STRING("ScrollbarFrame"), aResult); + } +#endif + + // nsIFrame overrides + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + NS_IMETHOD HandlePress(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) override; + + NS_IMETHOD HandleDrag(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + virtual nsIAtom* GetType() const 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() override { return true; } + + virtual nsresult GetXULMargin(nsMargin& aMargin) override; + + /** + * The following three methods set the value of mIncrement when a + * scrollbar button is pressed. + */ + void SetIncrementToLine(int32_t aDirection); + void SetIncrementToPage(int32_t aDirection); + void SetIncrementToWhole(int32_t aDirection); + /** + * MoveToNewPosition() adds mIncrement to the current position and + * updates the curpos attribute. + * @returns The new position after clamping, in CSS Pixels + * @note This method might destroy the frame, pres shell, and other objects. + */ + int32_t MoveToNewPosition(); + int32_t GetIncrement() { return mIncrement; } + +protected: + int32_t mIncrement; // Amount to scroll, in CSSPixels + bool mSmoothScroll; + +private: + nsCOMPtr<nsIContent> mScrollbarMediator; +}; // class nsScrollbarFrame + +#endif diff --git a/layout/xul/nsSliderFrame.cpp b/layout/xul/nsSliderFrame.cpp new file mode 100644 index 000000000..8e083f20c --- /dev/null +++ b/layout/xul/nsSliderFrame.cpp @@ -0,0 +1,1446 @@ +/* -*- 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 "gfxPrefs.h" +#include "nsStyleContext.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsCOMPtr.h" +#include "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsHTMLParts.h" +#include "nsIPresShell.h" +#include "nsCSSRendering.h" +#include "nsIDOMEvent.h" +#include "nsIDOMMouseEvent.h" +#include "nsScrollbarButtonFrame.h" +#include "nsISliderListener.h" +#include "nsIScrollableFrame.h" +#include "nsIScrollbarMediator.h" +#include "nsISupportsImpl.h" +#include "nsScrollbarFrame.h" +#include "nsRepeatService.h" +#include "nsBoxLayoutState.h" +#include "nsSprocketLayout.h" +#include "nsIServiceManager.h" +#include "nsContentUtils.h" +#include "nsLayoutUtils.h" +#include "nsDisplayList.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Telemetry.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/AsyncDragMetrics.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/ScrollInputMethods.h" +#include <algorithm> + +using namespace mozilla; +using mozilla::layers::APZCCallbackHelper; +using mozilla::layers::AsyncDragMetrics; +using mozilla::layers::InputAPZContext; +using mozilla::layers::ScrollInputMethod; + +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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame) + +NS_QUERYFRAME_HEAD(nsSliderFrame) + NS_QUERYFRAME_ENTRY(nsSliderFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +nsSliderFrame::nsSliderFrame(nsStyleContext* aContext): + nsBoxFrame(aContext), + mCurPos(0), + mChange(0), + mDragFinished(true), + mUserChanged(false), + mScrollingWithAPZ(false), + mSuppressionActive(false) +{ +} + +// stop timer +nsSliderFrame::~nsSliderFrame() +{ + if (mSuppressionActive) { + APZCCallbackHelper::SuppressDisplayport(false, PresContext() ? + PresContext()->PresShell() : + nullptr); + } +} + +void +nsSliderFrame::Init(nsIContent* aContent, + nsContainerFrame* 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); +} + +void +nsSliderFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + nsBoxFrame::RemoveFrame(aListID, aOldFrame); + if (mFrames.IsEmpty()) + RemoveListener(); +} + +void +nsSliderFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + bool wasEmpty = mFrames.IsEmpty(); + nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); + if (wasEmpty) + AddListener(); +} + +void +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(); + nsBoxFrame::AppendFrames(aListID, aFrameList); + if (wasEmpty) + AddListener(); +} + +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 Runnable +{ +public: + nsValueChangedRunnable(nsISliderListener* aListener, + nsIAtom* aWhich, + int32_t aValue, + bool aUserChanged) + : mListener(aListener), mWhich(aWhich), + mValue(aValue), mUserChanged(aUserChanged) + {} + + NS_IMETHOD Run() override + { + return mListener->ValueChanged(nsDependentAtomString(mWhich), + mValue, mUserChanged); + } + + nsCOMPtr<nsISliderListener> mListener; + nsCOMPtr<nsIAtom> mWhich; + int32_t mValue; + bool mUserChanged; +}; + +class nsDragStateChangedRunnable : public Runnable +{ +public: + nsDragStateChangedRunnable(nsISliderListener* aListener, + bool aDragBeginning) + : mListener(aListener), + mDragBeginning(aDragBeginning) + {} + + NS_IMETHOD Run() override + { + return mListener->DragStateChanged(mDragBeginning); + } + + nsCOMPtr<nsISliderListener> mListener; + bool mDragBeginning; +}; + +nsresult +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) + { + int32_t direction = 0; + if (current < min || max < min) { + current = min; + direction = -1; + } else if (current > max) { + current = max; + direction = 1; + } + + // set the new position and notify observers + nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox); + if (scrollbarFrame) { + nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator(); + scrollbarFrame->SetIncrementToWhole(direction); + if (mediator) { + mediator->ScrollByWhole(scrollbarFrame, direction, + nsIScrollbarMediator::ENABLE_SNAP); + } + } + // 'this' might be destroyed here + + 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 = nsBox::GetChildXULBox(this); + + if (thumb) { + nsRect thumbRect(thumb->GetRect()); + nsMargin m; + thumb->GetXULMargin(m); + thumbRect.Inflate(m); + + nsRect crect; + GetXULClientRect(crect); + + if (crect.width < thumbRect.width || crect.height < thumbRect.height) + return; + + // If this scrollbar is the scrollbar of an actively scrolled scroll frame, + // layerize the scrollbar thumb, wrap it in its own ContainerLayer and + // attach scrolling information to it. + // We do this here and not in the thumb's nsBoxFrame::BuildDisplayList so + // that the event region that gets created for the thumb is included in + // the nsDisplayOwnLayer contents. + + uint32_t flags = 0; + mozilla::layers::FrameMetrics::ViewID scrollTargetId = + mozilla::layers::FrameMetrics::NULL_SCROLL_ID; + aBuilder->GetScrollbarInfo(&scrollTargetId, &flags); + bool thumbGetsLayer = (scrollTargetId != layers::FrameMetrics::NULL_SCROLL_ID); + nsLayoutUtils::SetScrollbarThumbLayerization(thumb, thumbGetsLayer); + + if (thumbGetsLayer) { + nsDisplayListCollection tempLists; + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, tempLists); + + // This is a bit of a hack. Collect up all descendant display items + // and merge them into a single Content() list. + 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, flags, scrollTargetId, + GetThumbRatio())); + + return; + } + } + + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); +} + +NS_IMETHODIMP +nsSliderFrame::DoXULLayout(nsBoxLayoutState& aState) +{ + // get the thumb should be our only child + nsIFrame* thumbBox = nsBox::GetChildXULBox(this); + + 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) + SetXULDebug(aState, true); + else + SetXULDebug(aState, false); + } +#endif + + // get the content area inside our borders + nsRect clientRect; + GetXULClientRect(clientRect); + + // get the scrollbar + nsIFrame* scrollbarBox = GetScrollbar(); + nsCOMPtr<nsIContent> scrollbar; + scrollbar = GetContentOfBox(scrollbarBox); + + // get the thumb's pref size + nsSize thumbSize = thumbBox->GetXULPrefSize(aState); + + if (IsXULHorizontal()) + 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 = IsXULHorizontal() ? clientRect.width : clientRect.height; + nscoord& thumbLength = IsXULHorizontal() ? thumbSize.width : thumbSize.height; + + if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetXULFlex() > 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 = (IsXULHorizontal() ? 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)) + XULRedraw(aState); + + return NS_OK; +} + + +nsresult +nsSliderFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* 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; + } + + if (!mDragFinished && !isDraggingThumb()) { + StopDrag(); + return NS_OK; + } + + nsIFrame* scrollbarBox = GetScrollbar(); + nsCOMPtr<nsIContent> scrollbar; + scrollbar = GetContentOfBox(scrollbarBox); + bool isHorizontal = IsXULHorizontal(); + + if (isDraggingThumb()) + { + switch (aEvent->mMessage) { + case eTouchMove: + case eMouseMove: { + if (mScrollingWithAPZ) { + break; + } + nsPoint eventPoint; + if (!GetEventPoint(aEvent, eventPoint)) { + break; + } + if (mChange) { + // On Linux the destination point is determined by the initial click + // on the scrollbar track and doesn't change until the mouse button + // is released. +#ifndef MOZ_WIDGET_GTK + // On the other platforms we need to update the destination point now. + mDestinationPoint = eventPoint; + StopRepeat(); + StartRepeat(); +#endif + break; + } + + nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y; + + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + return NS_OK; + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::MainThreadScrollbarDrag); + + // 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->mClass == eTouchEventClass) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } + if (isMouseOutsideThumb) + { + SetCurrentThumbPosition(scrollbar, mThumbStart, false, false); + return NS_OK; + } + + // set it + SetCurrentThumbPosition(scrollbar, pos, false, true); // with snapping + } + break; + + case eTouchEnd: + case eMouseUp: + if (ShouldScrollForEvent(aEvent)) { + StopDrag(); + //we MUST call nsFrame HandleEvent for mouse ups to maintain the selection state and capture state. + return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + } + break; + + default: + break; + } + + //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; + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::MainThreadScrollbarTrackClick); + + // 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); + +#ifdef MOZ_WIDGET_GTK + nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent(); + thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true); +#endif + + if (aEvent->mClass == eTouchEventClass) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } + + if (isHorizontal) + mThumbStart = thumbFrame->GetPosition().x; + else + mThumbStart = thumbFrame->GetPosition().y; + + mDragStart = pos - mThumbStart; + } +#ifdef MOZ_WIDGET_GTK + else if (ShouldScrollForEvent(aEvent) && + aEvent->mClass == eMouseEventClass && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) { + // HandlePress and HandleRelease are usually called via + // nsFrame::HandleEvent, but only for the left mouse button. + if (aEvent->mMessage == eMouseDown) { + HandlePress(aPresContext, aEvent, aEventStatus); + } else if (aEvent->mMessage == eMouseUp) { + HandleRelease(aPresContext, aEvent, aEventStatus); + } + + return NS_OK; + } +#endif + + // XXX hack until handle release is actually called in nsframe. + // if (aEvent->mMessage == eMouseOut || + // aEvent->mMessage == NS_MOUSE_RIGHT_BUTTON_UP || + // aEvent->mMessage == NS_MOUSE_LEFT_BUTTON_UP) { + // HandleRelease(aPresContext, aEvent, aEventStatus); + // } + + if (aEvent->mMessage == eMouseOut && 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->IsXULBoxFrame() ? 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); + + 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; + GetXULClientRect(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 (IsXULHorizontal()) + newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio); + else + newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio); + + // avoid putting the scroll thumb at subpixel positions which cause needless invalidations + nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel(); + nsPoint snappedThumbLocation = ToAppUnits( + newThumbRect.TopLeft().ToNearestPixels(appUnitsPerPixel), + appUnitsPerPixel); + if (IsXULHorizontal()) { + newThumbRect.x = snappedThumbLocation.x; + } else { + newThumbRect.y = snappedThumbLocation.y; + } + + // set the rect + thumbFrame->SetRect(newThumbRect); + + // Request a repaint of the scrollbar + nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox); + nsIScrollbarMediator* mediator = scrollbarFrame + ? scrollbarFrame->GetScrollbarMediator() : nullptr; + if (!mediator || !mediator->ShouldSuppressScrollbarRepaints()) { + 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; + GetXULClientRect(crect); + nscoord offset = IsXULHorizontal() ? 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(); + nsWeakFrame weakFrame(this); + + mUserChanged = true; + + nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox); + if (scrollbarFrame) { + // See if we have a mediator. + nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator(); + if (mediator) { + nscoord oldPos = nsPresContext::CSSPixelsToAppUnits(GetCurrentPosition(scrollbar)); + nscoord newPos = nsPresContext::CSSPixelsToAppUnits(aNewPos); + mediator->ThumbMoved(scrollbarFrame, oldPos, newPos); + if (!weakFrame.IsAlive()) { + return; + } + UpdateAttribute(scrollbar, aNewPos, /* aNotify */false, aIsSmooth); + CurrentPositionChanged(); + mUserChanged = false; + return; + } + } + + UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth); + if (!weakFrame.IsAlive()) { + return; + } + mUserChanged = false; + +#ifdef DEBUG_SLIDER + printf("Current Pos=%d\n",aNewPos); +#endif + +} + +nsIAtom* +nsSliderFrame::GetType() const +{ + return nsGkAtoms::sliderFrame; +} + +void +nsSliderFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + nsBoxFrame::SetInitialChildList(aListID, aChildList); + if (aListID == kPrincipalList) { + AddListener(); + } +} + +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; +} + +bool +nsSliderFrame::StartAPZDrag() +{ + if (!gfxPlatform::GetPlatform()->SupportsApzDragInput()) { + return false; + } + + nsContainerFrame* cf = GetScrollbar()->GetParent(); + if (!cf) { + return false; + } + + nsIContent* scrollableContent = cf->GetContent(); + if (!scrollableContent) { + return false; + } + + mozilla::layers::FrameMetrics::ViewID scrollTargetId; + bool hasID = nsLayoutUtils::FindIDFor(scrollableContent, &scrollTargetId); + bool hasAPZView = hasID && (scrollTargetId != layers::FrameMetrics::NULL_SCROLL_ID); + + if (!hasAPZView) { + return false; + } + + nsIFrame* scrollbarBox = GetScrollbar(); + nsCOMPtr<nsIContent> scrollbar = GetContentOfBox(scrollbarBox); + + // This rect is the range in which the scroll thumb can slide in. + nsRect sliderTrack = GetRect() - scrollbarBox->GetPosition(); + CSSIntRect sliderTrackCSS = CSSIntRect::FromAppUnitsRounded(sliderTrack); + + uint64_t inputblockId = InputAPZContext::GetInputBlockId(); + uint32_t presShellId = PresContext()->PresShell()->GetPresShellId(); + AsyncDragMetrics dragMetrics(scrollTargetId, presShellId, inputblockId, + NSAppUnitsToIntPixels(mDragStart, + float(AppUnitsPerCSSPixel())), + sliderTrackCSS, + IsXULHorizontal() ? AsyncDragMetrics::HORIZONTAL : + AsyncDragMetrics::VERTICAL); + + if (!nsLayoutUtils::HasDisplayPort(scrollableContent)) { + return false; + } + + // When we start an APZ drag, we wont get mouse events for the drag. + // APZ will consume them all and only notify us of the new scroll position. + this->GetNearestWidget()->StartAsyncScrollbarDrag(dragMetrics); + return true; +} + +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; + + WidgetGUIEvent* event = aEvent->WidgetEventPtr()->AsGUIEvent(); + + if (!ShouldScrollForEvent(event)) { + return NS_OK; + } + + nsPoint pt; + if (!GetEventPoint(event, pt)) { + return NS_OK; + } + bool isHorizontal = IsXULHorizontal(); + 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; + } + +#ifdef MOZ_WIDGET_GTK + nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent(); + thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true); +#endif + + if (isHorizontal) + mThumbStart = thumbFrame->GetPosition().x; + else + mThumbStart = thumbFrame->GetPosition().y; + + mDragStart = pos - mThumbStart; + + mScrollingWithAPZ = StartAPZDrag(); + +#ifdef DEBUG_SLIDER + printf("Pressed mDragStart=%d\n",mDragStart); +#endif + + if (!mScrollingWithAPZ && !mSuppressionActive) { + MOZ_ASSERT(PresContext()->PresShell()); + APZCCallbackHelper::SuppressDisplayport(true, PresContext()->PresShell()); + mSuppressionActive = true; + } + + return NS_OK; +} + +nsresult +nsSliderFrame::StopDrag() +{ + AddListener(); + DragThumb(false); + + mScrollingWithAPZ = false; + + if (mSuppressionActive) { + MOZ_ASSERT(PresContext()->PresShell()); + APZCCallbackHelper::SuppressDisplayport(false, PresContext()->PresShell()); + mSuppressionActive = false; + } + +#ifdef MOZ_WIDGET_GTK + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (thumbFrame) { + nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent(); + thumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true); + } +#endif + + if (mChange) { + StopRepeat(); + mChange = 0; + } + return NS_OK; +} + +void +nsSliderFrame::DragThumb(bool aGrabMouseEvents) +{ + mDragFinished = !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(WidgetGUIEvent* aEvent) +{ + switch (aEvent->mMessage) { + case eTouchStart: + case eTouchEnd: + return true; + case eMouseDown: + case eMouseUp: { + uint16_t button = aEvent->AsMouseEvent()->button; +#ifdef MOZ_WIDGET_GTK + return (button == WidgetMouseEvent::eLeftButton) || + (button == WidgetMouseEvent::eRightButton && GetScrollToClick()) || + (button == WidgetMouseEvent::eMiddleButton && gMiddlePref && !GetScrollToClick()); +#else + return (button == WidgetMouseEvent::eLeftButton) || + (button == WidgetMouseEvent::eMiddleButton && gMiddlePref); +#endif + } + default: + return false; + } +} + +bool +nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent) +{ + if (!ShouldScrollForEvent(aEvent)) { + return false; + } + + if (aEvent->mMessage == eTouchStart) { + return GetScrollToClick(); + } + + if (aEvent->mMessage != eMouseDown) { + return false; + } + +#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) + // On Mac and Linux, clicking the scrollbar thumb should never scroll to click. + if (IsEventOverThumb(aEvent)) { + return false; + } +#endif + + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent->button == WidgetMouseEvent::eLeftButton) { +#ifdef XP_MACOSX + bool invertPref = mouseEvent->IsAlt(); +#else + bool invertPref = mouseEvent->IsShift(); +#endif + return GetScrollToClick() != invertPref; + } + +#ifdef MOZ_WIDGET_GTK + if (mouseEvent->button == WidgetMouseEvent::eRightButton) { + return !GetScrollToClick(); + } +#endif + + return true; +} + +bool +nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent) +{ + nsIFrame* thumbFrame = mFrames.FirstChild(); + if (!thumbFrame) { + return false; + } + + nsPoint eventPoint; + if (!GetEventPoint(aEvent, eventPoint)) { + return false; + } + + nsRect thumbRect = thumbFrame->GetRect(); +#if defined(MOZ_WIDGET_GTK) + /* Scrollbar track can have padding, so it's better to check that eventPoint + * is inside of actual thumb, not just its one axis. The part of the scrollbar + * track adjacent to thumb can actually receive events in GTK3 */ + return eventPoint.x >= thumbRect.x && eventPoint.x < thumbRect.XMost() && + eventPoint.y >= thumbRect.y && eventPoint.y < thumbRect.YMost(); +#else + bool isHorizontal = IsXULHorizontal(); + 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; +#endif +} + +NS_IMETHODIMP +nsSliderFrame::HandlePress(nsPresContext* aPresContext, + WidgetGUIEvent* 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 (IsXULHorizontal() ? eventPoint.x < thumbRect.x + : eventPoint.y < thumbRect.y) + change = -1; + + mChange = change; + DragThumb(true); + // On Linux we want to keep scrolling in the direction indicated by |change| + // until the mouse is released. On the other platforms we want to stop + // scrolling as soon as the scrollbar thumb has reached the current mouse + // position. +#ifdef MOZ_WIDGET_GTK + nsRect clientRect; + GetXULClientRect(clientRect); + + // Set the destination point to the very end of the scrollbar so that + // scrolling doesn't stop halfway through. + if (change > 0) { + mDestinationPoint = nsPoint(clientRect.width, clientRect.height); + } + else { + mDestinationPoint = nsPoint(0, 0); + } +#else + mDestinationPoint = eventPoint; +#endif + StartRepeat(); + PageScroll(change); + + return NS_OK; +} + +NS_IMETHODIMP +nsSliderFrame::HandleRelease(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + StopRepeat(); + + nsIFrame* scrollbar = GetScrollbar(); + nsScrollbarFrame* sb = do_QueryFrame(scrollbar); + if (sb) { + nsIScrollbarMediator* m = sb->GetScrollbarMediator(); + if (m) { + m->ScrollbarReleased(sb); + } + } + 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::GetXULPrefSize(nsBoxLayoutState& aState) +{ + EnsureOrient(); + return nsBoxFrame::GetXULPrefSize(aState); +} + +nsSize +nsSliderFrame::GetXULMinSize(nsBoxLayoutState& aState) +{ + EnsureOrient(); + + // our min size is just our borders and padding + return nsBox::GetXULMinSize(aState); +} + +nsSize +nsSliderFrame::GetXULMaxSize(nsBoxLayoutState& aState) +{ + EnsureOrient(); + return nsBoxFrame::GetXULMaxSize(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 = IsXULHorizontal(); + + // 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 { + PageScroll(mChange); + } +} + +void +nsSliderFrame::PageScroll(nscoord aChange) +{ + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, + nsGkAtoms::reverse, eCaseMatters)) { + aChange = -aChange; + } + nsIFrame* scrollbar = GetScrollbar(); + nsScrollbarFrame* sb = do_QueryFrame(scrollbar); + if (sb) { + nsIScrollbarMediator* m = sb->GetScrollbarMediator(); + sb->SetIncrementToPage(aChange); + if (m) { + m->ScrollByPage(sb, aChange, nsIScrollbarMediator::ENABLE_SNAP); + return; + } + } + PageUpDown(aChange); +} + +float +nsSliderFrame::GetThumbRatio() const +{ + // mRatio is in thumb app units per scrolled css pixels. Convert it to a + // ratio of the thumb's CSS pixels per scrolled CSS pixels. (Note the thumb + // is in the scrollframe's parent's space whereas the scrolled CSS pixels + // are in the scrollframe's space). + return mRatio / mozilla::AppUnitsPerCSSPixel(); +} + +NS_IMPL_ISUPPORTS(nsSliderMediator, + nsIDOMEventListener) diff --git a/layout/xul/nsSliderFrame.h b/layout/xul/nsSliderFrame.h new file mode 100644 index 000000000..832065a21 --- /dev/null +++ b/layout/xul/nsSliderFrame.h @@ -0,0 +1,206 @@ +/* -*- 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 nsITimer; +class nsSliderFrame; + +nsIFrame* NS_NewSliderFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + +class nsSliderMediator final : public nsIDOMEventListener +{ +public: + + NS_DECL_ISUPPORTS + + nsSliderFrame* mSlider; + + explicit nsSliderMediator(nsSliderFrame* aSlider) { mSlider = aSlider; } + + virtual void SetSlider(nsSliderFrame* aSlider) { mSlider = aSlider; } + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override; + +protected: + virtual ~nsSliderMediator() {} +}; + +class nsSliderFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + NS_DECL_QUERYFRAME_TARGET(nsSliderFrame) + NS_DECL_QUERYFRAME + + friend class nsSliderMediator; + + explicit nsSliderFrame(nsStyleContext* aContext); + virtual ~nsSliderFrame(); + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(NS_LITERAL_STRING("SliderFrame"), aResult); + } +#endif + + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState) override; + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override; + + // nsIFrame overrides + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* asPrevInFlow) override; + + + virtual nsresult HandleEvent(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual nsIAtom* GetType() const override; + + // nsContainerFrame overrides + virtual void SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) override; + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + nsresult StartDrag(nsIDOMEvent* aEvent); + nsresult StopDrag(); + + bool StartAPZDrag(); + + 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, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) override + { + return NS_OK; + } + + NS_IMETHOD HandleDrag(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override + { + return NS_OK; + } + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + // Return the ratio the scrollbar thumb should move in proportion to the + // scrolled frame. + float GetThumbRatio() const; + +private: + + bool GetScrollToClick(); + nsIFrame* GetScrollbar(); + bool ShouldScrollForEvent(mozilla::WidgetGUIEvent* aEvent); + bool ShouldScrollToClickForEvent(mozilla::WidgetGUIEvent* aEvent); + bool IsEventOverThumb(mozilla::WidgetGUIEvent* 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(); + } + void PageScroll(nscoord aChange); + + nsPoint mDestinationPoint; + RefPtr<nsSliderMediator> mMediator; + + float mRatio; + + nscoord mDragStart; + nscoord mThumbStart; + + int32_t mCurPos; + + nscoord mChange; + + bool mDragFinished; + + // 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; + + // true if we've handed off the scrolling to APZ. This means that we should + // ignore scrolling events as the position will be updated by APZ. If we were + // to process these events then the scroll position update would conflict + // causing the scroll position to jump. + bool mScrollingWithAPZ; + + // true if displayport suppression is active, for more performant + // scrollbar-dragging behaviour. + bool mSuppressionActive; + + static bool gMiddlePref; + static int32_t gSnapMultiplier; +}; // class nsSliderFrame + +#endif diff --git a/layout/xul/nsSplitterFrame.cpp b/layout/xul/nsSplitterFrame.cpp new file mode 100644 index 000000000..7879a176d --- /dev/null +++ b/layout/xul/nsSplitterFrame.cpp @@ -0,0 +1,1046 @@ +/* -*- 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 "nsNameSpaceManager.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 "nsContentCID.h" +#include "mozilla/StyleSetHandle.h" +#include "mozilla/StyleSetHandleInlines.h" +#include "nsLayoutUtils.h" +#include "nsDisplayList.h" +#include "nsContentUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/UniquePtr.h" + +using namespace mozilla; + +class nsSplitterInfo { +public: + nscoord min; + nscoord max; + nscoord current; + nscoord changed; + nsCOMPtr<nsIContent> childElem; + int32_t flex; + int32_t index; +}; + +class nsSplitterFrameInner final : public nsIDOMEventListener +{ +protected: + virtual ~nsSplitterFrameInner(); + +public: + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter) + { + mOuter = aSplitter; + mPressed = false; + } + + void Disconnect() { mOuter = nullptr; } + + nsresult MouseDown(nsIDOMEvent* aMouseEvent); + nsresult MouseUp(nsIDOMEvent* aMouseEvent); + nsresult MouseMove(nsIDOMEvent* aMouseEvent); + + void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent); + void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* 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(nscoord& aDiff, + nsSplitterInfo* aChildrenBeforeInfos, + nsSplitterInfo* aChildrenAfterInfos, + int32_t aChildrenBeforeCount, + int32_t aChildrenAfterCount, + bool aBounded); + + void UpdateState(); + + void AddListener(); + 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(UniquePtr<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; + UniquePtr<nsSplitterInfo[]> mChildInfosBefore; + UniquePtr<nsSplitterInfo[]> mChildInfosAfter; + int32_t mChildInfosBeforeCount; + int32_t mChildInfosAfterCount; + State mState; + nscoord mSplitterPos; + bool mDragging; + +}; + +NS_IMPL_ISUPPORTS(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() +{ +} + +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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame) + +nsSplitterFrame::nsSplitterFrame(nsStyleContext* aContext) +: nsBoxFrame(aContext), + mInner(0) +{ +} + +void +nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (mInner) { + mInner->RemoveListener(); + mInner->Disconnect(); + mInner->Release(); + mInner = nullptr; + } + nsBoxFrame::DestroyFrom(aDestructRoot); +} + + +nsresult +nsSplitterFrame::GetCursor(const nsPoint& aPoint, + nsIFrame::Cursor& aCursor) +{ + return nsBoxFrame::GetCursor(aPoint, aCursor); + + /* + if (IsXULHorizontal()) + aCursor = NS_STYLE_CURSOR_N_RESIZE; + else + aCursor = NS_STYLE_CURSOR_W_RESIZE; + + return NS_OK; + */ +} + +nsresult +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(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, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + MOZ_ASSERT(!mInner); + mInner = new nsSplitterFrameInner(this); + + mInner->AddRef(); + 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->IsXULBoxFrame()) { + if (!aParent->IsXULHorizontal()) { + if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None, + nsGkAtoms::orient)) { + aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, + NS_LITERAL_STRING("vertical"), false); + nsStyleContext* parentStyleContext = StyleContext()->GetParent(); + RefPtr<nsStyleContext> newContext = PresContext()->StyleSet()-> + ResolveStyleFor(aContent->AsElement(), parentStyleContext); + SetStyleContextWithoutNotification(newContext); + } + } + } + + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + mInner->mState = nsSplitterFrameInner::Open; + mInner->AddListener(); + mInner->mParentBox = nullptr; +} + +NS_IMETHODIMP +nsSplitterFrame::DoXULLayout(nsBoxLayoutState& aState) +{ + if (GetStateBits() & NS_FRAME_FIRST_REFLOW) + { + mInner->mParentBox = nsBox::GetParentXULBox(this); + mInner->UpdateState(); + } + + return nsBoxFrame::DoXULLayout(aState); +} + + +void +nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal) +{ + nsIFrame* box = nsBox::GetParentXULBox(this); + if (box) { + aIsHorizontal = !box->IsXULHorizontal(); + } + else + nsBoxFrame::GetInitialOrientation(aIsHorizontal); +} + +NS_IMETHODIMP +nsSplitterFrame::HandlePress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleRelease(nsPresContext* aPresContext, + WidgetGUIEvent* 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; + } +} + +nsresult +nsSplitterFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + nsWeakFrame weakFrame(this); + RefPtr<nsSplitterFrameInner> inner(mInner); + switch (aEvent->mMessage) { + case eMouseMove: + inner->MouseDrag(aPresContext, aEvent); + break; + + case eMouseUp: + if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) { + inner->MouseUp(aPresContext, aEvent); + } + break; + + default: + break; + } + + NS_ENSURE_STATE(weakFrame.IsAlive()); + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +void +nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent) +{ + if (mDragging && mOuter) { + AdjustChildren(aPresContext); + AddListener(); + 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"); + } + + mChildInfosBefore = nullptr; + mChildInfosAfter = nullptr; + mChildInfosBeforeCount = 0; + mChildInfosAfterCount = 0; +} + +void +nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent) +{ + if (mDragging && mOuter) { + + //printf("Dragging\n"); + + bool isHorizontal = !mOuter->IsXULHorizontal(); + // 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(pos, + mChildInfosBefore.get(), mChildInfosAfter.get(), + 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() +{ + 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; + + int16_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 = nsBox::GetParentXULBox(mOuter); + 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; + + nsRenderingContext rc( + outerPresContext->PresShell()->CreateReferenceRenderingContext()); + nsBoxLayoutState state(outerPresContext, &rc); + mCurrentPos = 0; + mPressed = true; + + mDidDrag = false; + + EnsureOrient(); + bool isHorizontal = !mOuter->IsXULHorizontal(); + + ResizeType resizeBefore = GetResizeBefore(); + ResizeType resizeAfter = GetResizeAfter(); + + mChildInfosBefore = MakeUnique<nsSplitterInfo[]>(childCount); + mChildInfosAfter = MakeUnique<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 = nsBox::GetChildXULBox(mParentBox); + + 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->GetXULPrefSize(state); + nsSize minSize = childBox->GetXULMinSize(state); + nsSize maxSize = nsBox::BoundsCheckMinMax(minSize, childBox->GetXULMaxSize(state)); + prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize); + + mOuter->AddMargin(childBox, minSize); + mOuter->AddMargin(childBox, prefSize); + mOuter->AddMargin(childBox, maxSize); + + nscoord flex = childBox->GetXULFlex(); + + nsMargin margin(0,0,0,0); + childBox->GetXULMargin(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 = nsBox::GetNextXULBox(childBox); + count++; + } + + if (!mParentBox->IsXULNormalDirection()) { + // 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. + Swap(mChildInfosBeforeCount, mChildInfosAfterCount); + Swap(mChildInfosBefore, mChildInfosAfter); + } + + // 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->AsEvent(), + 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(UniquePtr<nsSplitterInfo[]>& aChildInfos, int32_t aCount) +{ + UniquePtr<nsSplitterInfo[]> infos(new nsSplitterInfo[aCount]); + + for (int i=0; i < aCount; i++) + infos[i] = aChildInfos[aCount - 1 - i]; + + aChildInfos = Move(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()->IsXULBoxFrame()) { + // 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->IsXULHorizontal(); + + AdjustChildren(aPresContext, mChildInfosBefore.get(), + mChildInfosBeforeCount, isHorizontal); + AdjustChildren(aPresContext, mChildInfosAfter.get(), + mChildInfosAfterCount, isHorizontal); +} + +static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox, nsIContent* aContent) +{ + nsIFrame* childBox = nsBox::GetChildXULBox(aParentBox); + + while (nullptr != childBox) { + if (childBox->GetContent() == aContent) { + return childBox; + } + childBox = nsBox::GetNextXULBox(childBox); + } + 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 = nsBox::GetChildXULBox(mOuter); + while(child) + { + SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr); + child = nsBox::GetNextXULBox(child); + } + + // 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->GetXULMargin(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(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); + } + } +} diff --git a/layout/xul/nsSplitterFrame.h b/layout/xul/nsSplitterFrame.h new file mode 100644 index 000000000..df8872255 --- /dev/null +++ b/layout/xul/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 + + explicit nsSplitterFrame(nsStyleContext* aContext); + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override { + return MakeFrameName(NS_LITERAL_STRING("SplitterFrame"), aResult); + } +#endif + + // nsIFrame overrides + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual nsresult GetCursor(const nsPoint& aPoint, + nsIFrame::Cursor& aCursor) override; + + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override; + + NS_IMETHOD HandlePress(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) override; + + NS_IMETHOD HandleDrag(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + NS_IMETHOD HandleRelease(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual nsresult HandleEvent(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual void GetInitialOrientation(bool& aIsHorizontal) override; + +private: + + friend class nsSplitterFrameInner; + nsSplitterFrameInner* mInner; + +}; // class nsSplitterFrame + +#endif diff --git a/layout/xul/nsSprocketLayout.cpp b/layout/xul/nsSprocketLayout.cpp new file mode 100644 index 000000000..8ce73566b --- /dev/null +++ b/layout/xul/nsSprocketLayout.cpp @@ -0,0 +1,1652 @@ +/* -*- 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(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::IsXULHorizontal(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->GetXULHAlign(); + nsIFrame::Valignment valign = aBox->GetXULVAlign(); + + // 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::XULLayout(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->IsXULCollapsed()) { + nsIFrame* child = nsBox::GetChildXULBox(aBox); + while(child) + { + nsBoxFrame::LayoutChildAt(aState, child, nsRect(0,0,0,0)); + child = nsBox::GetNextXULBox(child); + } + 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->GetXULClientRect(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 = nsFrameState(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 (!IsXULHorizontal(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 (IsXULHorizontal(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; + + // 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 = nsBox::GetChildXULBox(aBox); + + 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->GetXULPrefSize(aState); + nsSize minSize = child->GetXULMinSize(aState); + nsSize maxSize = child->GetXULMaxSize(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); + } + + // 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. + nscoord nextX = x; + nscoord nextY = y; + + 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); + if (GetFrameDirection(aBox) == NS_STYLE_DIRECTION_LTR) { + childRect.x = originalClientRect.x; + } else { + // keep the right edge of the box the same + childRect.x = clientRect.x + originalClientRect.width - childRect.width; + } + } + + // 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->GetXULMargin(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 XULLayout 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->SetXULBounds(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->GetXULMinSize(aState); + nsSize maxSize = child->GetXULMaxSize(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->SetXULBounds(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->XULLayout(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->XULDumpBox(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 (!(frameState & NS_STATE_IS_HORIZONTAL)) { + if (GetFrameDirection(aBox) != NS_STYLE_DIRECTION_LTR) { + // keep the right edge the same + newChildRect.x = childRect.XMost() - newChildRect.width; + } + } + + // 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->SetXULBounds(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 = nsBox::GetNextXULBox(child); + 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->GetXULBorderAndPadding(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->SetXULBounds(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 = nsBox::GetChildXULBox(aBox); + + // reposition all our children + while (child) + { + nsRect childRect(child->GetRect()); + childRect.x += (x - origX); + childRect.y += (y - origY); + child->SetXULBounds(aState, childRect); + child = nsBox::GetNextXULBox(child); + } + } + + // 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 = nsFrameState(0); + GetFrameState(aBox, frameState); + + //if (frameState & NS_STATE_CURRENTLY_IN_DEBUG) + // printf("In debug\n"); + + aMinSize = 0; + aMaxSize = NS_INTRINSICSIZE; + + bool isHorizontal; + + if (IsXULHorizontal(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 = nsBox::GetChildXULBox(aBox); + + 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->GetXULFlex(); + + currentBox->flex = flex; + currentBox->collapsed = child->IsXULCollapsed(); + } else { + flex = start->flex; + start = start->next; + } + + if (flex > 0) + aFlexes++; + + child = GetNextXULBox(child); + } +#endif + + // get pref, min, max + child = nsBox::GetChildXULBox(aBox); + 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->IsXULCollapsed(); + + 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->GetXULPrefSize(aState); + minSize = child->GetXULMinSize(aState); + maxSize = nsBox::BoundsCheckMinMax(minSize, child->GetXULMaxSize(aState)); + ascent = child->GetXULBoxAscent(aState); + nsMargin margin; + child->GetXULMargin(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->GetXULFlex(); + + // 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 = nsBox::GetNextXULBox(child); + + 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 = nsFrameState(0); + GetFrameState(aBox, frameState); + + if (IsXULHorizontal(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 = nsFrameState(0); + GetFrameState(aBox, frameState); + bool isHorizontal = (frameState & NS_STATE_IS_HORIZONTAL) != 0; + nsRect clientRect; + aBox->GetXULClientRect(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 = 0; + bool isLTR; + + if (isHorizontal) { + valign = aBox->GetXULVAlign(); + if (valign == nsBoxFrame::vAlign_BaseLine) { + maxAscent = aBox->GetXULBoxAscent(aState); + } + } else { + isLTR = GetFrameDirection(aBox) == NS_STYLE_DIRECTION_LTR; + halign = aBox->GetXULHAlign(); + } + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + while (child) { + + nsMargin margin; + child->GetXULMargin(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 = 0; + 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->GetXULBoxAscent(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 = 0; + 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->SetXULBounds(aState, childRect); + } + + child = nsBox::GetNextXULBox(child); + } +} + +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 = IsXULHorizontal(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->GetXULMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, aChild->GetXULMaxSize(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->GetXULMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, aChild->GetXULMaxSize(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->GetXULMargin(margin); + nsRect rect(aChildActualRect); + if (rect.width >= margin.left + margin.right && rect.height >= margin.top + margin.bottom) + rect.Deflate(margin); + + aChild->SetXULBounds(aState, rect); + aChild->XULLayout(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::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize vpref (0, 0); + bool isHorizontal = IsXULHorizontal(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 = nsBox::GetChildXULBox(aBox); + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsXULCollapsed()) + { + nsSize pref = child->GetXULPrefSize(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 = nsBox::GetNextXULBox(child); + } + + 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::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize (0, 0); + bool isHorizontal = IsXULHorizontal(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 = nsBox::GetChildXULBox(aBox); + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsXULCollapsed()) + { + nsSize min = child->GetXULMinSize(aState); + nsSize pref(0,0); + + // if the child is not flexible then + // its min size is its pref size. + if (child->GetXULFlex() == 0) { + pref = child->GetXULPrefSize(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 = nsBox::GetNextXULBox(child); + } + + + 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::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + + bool isHorizontal = IsXULHorizontal(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 = nsBox::GetChildXULBox(aBox); + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsXULCollapsed()) + { + // if completely redefined don't even ask our child for its size. + nsSize min = child->GetXULMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, child->GetXULMaxSize(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 = nsBox::GetNextXULBox(child); + } + + 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 = IsXULHorizontal(aBox); + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + + while (child) + { + // ignore collapsed children + //if (!child->IsXULCollapsed()) + //{ + // if completely redefined don't even ask our child for its size. + nscoord ascent = child->GetXULBoxAscent(aState); + + nsMargin margin; + child->GetXULMargin(margin); + ascent += margin.top; + + if (isHorizontal) + { + if (ascent > vAscent) + vAscent = ascent; + } else { + if (vAscent == 0) + vAscent = ascent; + } + //} + + child = nsBox::GetNextXULBox(child); + } + + nsMargin borderPadding; + aBox->GetXULBorderAndPadding(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/nsSprocketLayout.h b/layout/xul/nsSprocketLayout.h new file mode 100644 index 000000000..0f15dce44 --- /dev/null +++ b/layout/xul/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(nsCOMPtr<nsBoxLayout>& aNewLayout); + +class nsSprocketLayout : public nsBoxLayout { + +public: + + friend nsresult NS_NewSprocketLayout(nsCOMPtr<nsBoxLayout>& aNewLayout); + static void Shutdown(); + + NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) override; + + virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nscoord GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + + nsSprocketLayout(); + + static bool IsXULHorizontal(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/nsStackFrame.cpp b/layout/xul/nsStackFrame.cpp new file mode 100644 index 000000000..437d558f9 --- /dev/null +++ b/layout/xul/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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsStackFrame) + +nsStackFrame::nsStackFrame(nsStyleContext* aContext): + nsBoxFrame(aContext) +{ + nsCOMPtr<nsBoxLayout> layout; + NS_NewStackLayout(layout); + SetXULLayoutManager(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/nsStackFrame.h b/layout/xul/nsStackFrame.h new file mode 100644 index 000000000..b90a16b21 --- /dev/null +++ b/layout/xul/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_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("Stack"), aResult); + } +#endif + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + +protected: + explicit nsStackFrame(nsStyleContext* aContext); +}; // class nsStackFrame + + + +#endif + diff --git a/layout/xul/nsStackLayout.cpp b/layout/xul/nsStackLayout.cpp new file mode 100644 index 000000000..6072e0612 --- /dev/null +++ b/layout/xul/nsStackLayout.cpp @@ -0,0 +1,385 @@ +/* -*- 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 "nsNameSpaceManager.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(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::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize prefSize (0, 0); + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + while (child) { + if (child->StyleXUL()->mStretchStack) { + nsSize pref = child->GetXULPrefSize(aState); + + AddMargin(child, pref); + nsMargin offset; + GetOffset(child, offset); + pref.width += offset.LeftRight(); + pref.height += offset.TopBottom(); + AddLargestSize(prefSize, pref); + } + + child = nsBox::GetNextXULBox(child); + } + + AddBorderAndPadding(aBox, prefSize); + + return prefSize; +} + +nsSize +nsStackLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize (0, 0); + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + while (child) { + if (child->StyleXUL()->mStretchStack) { + nsSize min = child->GetXULMinSize(aState); + + AddMargin(child, min); + nsMargin offset; + GetOffset(child, offset); + min.width += offset.LeftRight(); + min.height += offset.TopBottom(); + AddLargestSize(minSize, min); + } + + child = nsBox::GetNextXULBox(child); + } + + AddBorderAndPadding(aBox, minSize); + + return minSize; +} + +nsSize +nsStackLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE); + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + while (child) { + if (child->StyleXUL()->mStretchStack) { + nsSize min = child->GetXULMinSize(aState); + nsSize max = child->GetXULMaxSize(aState); + + max = nsBox::BoundsCheckMinMax(min, max); + + AddMargin(child, max); + nsMargin offset; + GetOffset(child, offset); + max.width += offset.LeftRight(); + max.height += offset.TopBottom(); + AddSmallestSize(maxSize, max); + } + + child = nsBox::GetNextXULBox(child); + } + + AddBorderAndPadding(aBox, maxSize); + + return maxSize; +} + + +nscoord +nsStackLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nscoord vAscent = 0; + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + while (child) { + nscoord ascent = child->GetXULBoxAscent(aState); + nsMargin margin; + child->GetXULMargin(margin); + ascent += margin.top; + if (ascent > vAscent) + vAscent = ascent; + + child = nsBox::GetNextXULBox(child); + } + + return vAscent; +} + +uint8_t +nsStackLayout::GetOffset(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->IsXULBoxFrame() && + (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->IsXULBoxFrame()) { + // 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::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsRect clientRect; + aBox->GetXULClientRect(clientRect); + + bool grow; + + do { + nsIFrame* child = nsBox::GetChildXULBox(aBox); + grow = false; + + while (child) + { + nsMargin margin; + child->GetXULMargin(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->GetXULMargin(margin); + + // obtain our offset from the top left border of the stack's content box. + nsMargin offset; + uint8_t offsetSpecified = GetOffset(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) { + nsSize min = child->GetXULMinSize(aState); + nsSize max = child->GetXULMaxSize(aState); + if (offsetSpecified & SPECIFIED_LEFT) { + childRect.x = clientRect.x + offset.left + margin.left; + if (offsetSpecified & SPECIFIED_RIGHT) { + nscoord width = clientRect.width - offset.LeftRight() - margin.LeftRight(); + childRect.width = clamped(width, min.width, max.width); + } + else { + nscoord width = child->GetXULPrefSize(aState).width; + childRect.width = clamped(width, min.width, max.width); + } + } + else if (offsetSpecified & SPECIFIED_RIGHT) { + nscoord width = child->GetXULPrefSize(aState).width; + childRect.width = clamped(width, min.width, max.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) { + nscoord height = clientRect.height - offset.TopBottom() - margin.TopBottom(); + childRect.height = clamped(height, min.height, max.height); + } + else { + nscoord height = child->GetXULPrefSize(aState).height; + childRect.height = clamped(height, min.height, max.height); + } + } + else if (offsetSpecified & SPECIFIED_BOTTOM) { + nscoord height = child->GetXULPrefSize(aState).height; + childRect.height = clamped(height, min.height, max.height); + childRect.y = clientRect.YMost() - offset.bottom - margin.bottom - childRect.height; + } + } + + // Now place the child. + child->SetXULBounds(aState, childRect); + + // Flow the child. + child->XULLayout(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 = nsBox::GetNextXULBox(child); + } + } while (grow); + + // if some HTML inside us got bigger we need to force ourselves to + // get bigger + nsRect bounds(aBox->GetRect()); + nsMargin bp; + aBox->GetXULBorderAndPadding(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->SetXULBounds(aState, bounds); + } + + return NS_OK; +} + diff --git a/layout/xul/nsStackLayout.h b/layout/xul/nsStackLayout.h new file mode 100644 index 000000000..1eb1a6318 --- /dev/null +++ b/layout/xul/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(nsCOMPtr<nsBoxLayout>& aNewLayout); + +class nsStackLayout : public nsBoxLayout +{ +public: + + friend nsresult NS_NewStackLayout(nsCOMPtr<nsBoxLayout>& aNewLayout); + static void Shutdown(); + + nsStackLayout(); + + NS_IMETHOD XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) override; + + virtual nsSize GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) override; + virtual nscoord GetAscent(nsIFrame* aBox, nsBoxLayoutState& aBoxLayoutState) 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(nsIFrame* aChild, nsMargin& aMargin); + +private: + static nsBoxLayout* gInstance; + +}; // class nsStackLayout + + + +#endif + diff --git a/layout/xul/nsTextBoxFrame.cpp b/layout/xul/nsTextBoxFrame.cpp new file mode 100644 index 000000000..c82d3d6b9 --- /dev/null +++ b/layout/xul/nsTextBoxFrame.cpp @@ -0,0 +1,1241 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cindent: */ +/* 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 "gfx2DGlue.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "nsFontMetrics.h" +#include "nsReadableUtils.h" +#include "nsCOMPtr.h" +#include "nsGkAtoms.h" +#include "nsPresContext.h" +#include "nsRenderingContext.h" +#include "nsStyleContext.h" +#include "nsIContent.h" +#include "nsNameSpaceManager.h" +#include "nsBoxLayoutState.h" +#include "nsMenuBarListener.h" +#include "nsXPIDLString.h" +#include "nsIServiceManager.h" +#include "nsIDOMElement.h" +#include "nsIDOMXULLabelElement.h" +#include "mozilla/EventStateManager.h" +#include "nsITheme.h" +#include "nsUnicharUtils.h" +#include "nsContentUtils.h" +#include "nsDisplayList.h" +#include "nsCSSRendering.h" +#include "nsIReflowCallback.h" +#include "nsBoxFrame.h" +#include "mozilla/Preferences.h" +#include "nsLayoutUtils.h" +#include "mozilla/Attributes.h" +#include "nsUnicodeProperties.h" + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +#include "nsBidiUtils.h" +#include "nsBidiPresUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame) + +NS_QUERYFRAME_HEAD(nsTextBoxFrame) + NS_QUERYFRAME_ENTRY(nsTextBoxFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame) + +nsresult +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()); + XULRedraw(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(nsStyleContext* aContext): + nsLeafBoxFrame(aContext), mAccessKeyInfo(nullptr), mCropType(CropRight), + mNeedsReflowCallback(false) +{ + MarkIntrinsicISizesDirty(); +} + +nsTextBoxFrame::~nsTextBoxFrame() +{ + delete mAccessKeyInfo; +} + + +void +nsTextBoxFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsLeafBoxFrame::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); + nsLeafBoxFrame::DestroyFrom(aDestructRoot); +} + +bool +nsTextBoxFrame::AlwaysAppendAccessKey() +{ + if (!gAccessKeyPrefInitialized) + { + gAccessKeyPrefInitialized = true; + + const char* prefName = "intl.menuitems.alwaysappendaccesskeys"; + nsAdoptingString val = Preferences::GetLocalizedString(prefName); + gAlwaysAppendAccessKey = val.EqualsLiteral("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 final : public nsIReflowCallback +{ +public: + explicit nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame) + { + } + + virtual bool ReflowFinished() override + { + bool shouldFlush = false; + nsTextBoxFrame* frame = + static_cast<nsTextBoxFrame*>(mWeakFrame.GetFrame()); + if (frame) { + shouldFlush = frame->UpdateAccesskey(mWeakFrame); + } + delete this; + return shouldFlush; + } + + virtual void ReflowCallbackCanceled() override + { + 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. + 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. + RecomputeTitle(); + 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, &nsGkAtoms::none, 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; + case 5: + cropType = CropNone; + break; + default: + cropType = CropAuto; + break; + } + + if (cropType != mCropType) { + aResize = true; + mCropType = cropType; + } + } + + if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) { + RecomputeTitle(); + 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) override; + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) override; + NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX) + + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override; + + virtual void DisableComponentAlpha() override { + 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) +{ + DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(), + 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(); + int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + // paint the title + nscolor overColor = 0; + nscolor underColor = 0; + nscolor strikeColor = 0; + uint8_t overStyle = 0; + uint8_t underStyle = 0; + uint8_t strikeStyle = 0; + + // 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; + + WritingMode wm = GetWritingMode(); + bool vertical = wm.IsVertical(); + + 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 { + color = context->StyleColor()-> + CalcComplexColor(styleText->mTextDecorationColor); + } + uint8_t style = styleText->mTextDecorationStyle; + + 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))); + + RefPtr<nsFontMetrics> fontMet = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + fontMet->SetVertical(wm.IsVertical()); + fontMet->SetTextOrientation(StyleVisibility()->mTextOrientation); + + nscoord offset; + nscoord size; + nscoord ascent = fontMet->MaxAscent(); + + nsPoint baselinePt; + if (wm.IsVertical()) { + baselinePt.x = + presContext->RoundAppUnitsToNearestDevPixels(aTextRect.x + + (wm.IsVerticalRL() ? aTextRect.width - ascent : ascent)); + baselinePt.y = aTextRect.y; + } else { + baselinePt.x = aTextRect.x; + baselinePt.y = + presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent); + } + + nsCSSRendering::PaintDecorationLineParams params; + params.dirtyRect = ToRect(presContext->AppUnitsToGfxUnits(aDirtyRect)); + params.pt = Point(presContext->AppUnitsToGfxUnits(aTextRect.x), + presContext->AppUnitsToGfxUnits(aTextRect.y)); + params.icoordInFrame = + Float(PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x)); + params.lineSize = Size(presContext->AppUnitsToGfxUnits(aTextRect.width), 0); + params.ascent = presContext->AppUnitsToGfxUnits(ascent); + params.vertical = vertical; + + // XXX todo: vertical-mode support for decorations not tested yet, + // probably won't be positioned correctly + + // 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_STYLE_TEXT_DECORATION_LINE_OVERLINE | + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE)) { + fontMet->GetUnderline(offset, size); + params.lineSize.height = presContext->AppUnitsToGfxUnits(size); + if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) && + underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + params.color = underColor; + params.offset = presContext->AppUnitsToGfxUnits(offset); + params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; + params.style = underStyle; + nsCSSRendering::PaintDecorationLine(this, *drawTarget, params); + } + if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) && + overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + params.color = overColor; + params.offset = params.ascent; + params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE; + params.style = overStyle; + nsCSSRendering::PaintDecorationLine(this, *drawTarget, params); + } + } + + nsRenderingContext refContext( + PresContext()->PresShell()->CreateReferenceRenderingContext()); + DrawTarget* refDrawTarget = refContext.GetDrawTarget(); + + CalculateUnderline(refDrawTarget, *fontMet); + + nscolor c = aOverrideColor ? *aOverrideColor : StyleColor()->mColor; + ColorPattern color(ToDeviceColor(c)); + aRenderingContext.ThebesContext()->SetColor(Color::FromABGR(c)); + + nsresult rv = NS_ERROR_FAILURE; + + if (mState & NS_FRAME_IS_BIDI) { + presContext->SetBidiEnabled(); + nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(StyleContext()); + 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(), level, + presContext, aRenderingContext, + refDrawTarget, *fontMet, + baselinePt.x, baselinePt.y, + &posResolve, + 1); + mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips; + mAccessKeyInfo->mAccessWidth = posResolve.visualWidth; + } + else + { + rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level, + presContext, aRenderingContext, + refDrawTarget, *fontMet, + baselinePt.x, baselinePt.y); + } + } + if (NS_FAILED(rv)) { + fontMet->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 = nsLayoutUtils:: + AppUnitWidthOfString(mCroppedTitle.get(), + mAccessKeyInfo->mAccesskeyIndex, + *fontMet, refDrawTarget); + else + mAccessKeyInfo->mBeforeWidth = 0; + } + + fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(), + baselinePt.x, baselinePt.y, &aRenderingContext, + refDrawTarget); + } + + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + nsRect r(aTextRect.x + mAccessKeyInfo->mBeforeWidth, + aTextRect.y + mAccessKeyInfo->mAccessOffset, + mAccessKeyInfo->mAccessWidth, + mAccessKeyInfo->mAccessUnderlineSize); + Rect devPxRect = + NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(devPxRect, color); + } + + // 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_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) && + strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + fontMet->GetStrikeout(offset, size); + params.color = strikeColor; + params.lineSize.height = presContext->AppUnitsToGfxUnits(size); + params.offset = presContext->AppUnitsToGfxUnits(offset); + params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; + params.style = strikeStyle; + nsCSSRendering::PaintDecorationLine(this, *drawTarget, params); + } +} + +void +nsTextBoxFrame::CalculateUnderline(DrawTarget* aDrawTarget, + nsFontMetrics& aFontMetrics) +{ + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + // Calculate all fields of mAccessKeyInfo which + // are the same for both BiDi and non-BiDi frames. + const char16_t *titleString = mCroppedTitle.get(); + aFontMetrics.SetTextRunRTL(false); + mAccessKeyInfo->mAccessWidth = nsLayoutUtils:: + AppUnitWidthOfString(titleString[mAccessKeyInfo->mAccesskeyIndex], + aFontMetrics, aDrawTarget); + + nscoord offset, baseline; + aFontMetrics.GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize); + baseline = aFontMetrics.MaxAscent(); + mAccessKeyInfo->mAccessOffset = baseline - offset; + } +} + +nscoord +nsTextBoxFrame::CalculateTitleForWidth(nsRenderingContext& aRenderingContext, + nscoord aWidth) +{ + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + if (mTitle.IsEmpty()) { + mCroppedTitle.Truncate(); + return 0; + } + + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + + // see if the text will completely fit in the width given + nscoord titleWidth = + nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm, + aRenderingContext); + if (titleWidth <= aWidth) { + mCroppedTitle = mTitle; + if (HasRTLChars(mTitle) || + StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { + mState |= NS_FRAME_IS_BIDI; + } + return titleWidth; // fits, done. + } + + const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); + if (mCropType != CropNone) { + // 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?). + fm->SetTextRunRTL(false); + titleWidth = nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm, + drawTarget); + + 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; + } else { + mCroppedTitle.Truncate(0); + titleWidth = 0; + } + + using mozilla::unicode::ClusterIterator; + using mozilla::unicode::ClusterReverseIterator; + + // ok crop things + switch (mCropType) + { + case CropAuto: + case CropNone: + case CropRight: + { + ClusterIterator iter(mTitle.Data(), mTitle.Length()); + const char16_t* dataBegin = iter; + const char16_t* pos = dataBegin; + nscoord charWidth; + nscoord totalWidth = 0; + + while (!iter.AtEnd()) { + iter.Next(); + const char16_t* nextPos = iter; + ptrdiff_t length = nextPos - pos; + charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length, + *fm, + drawTarget); + if (totalWidth + charWidth > aWidth) { + break; + } + + if (UCS2_CHAR_IS_BIDI(*pos)) { + mState |= NS_FRAME_IS_BIDI; + } + pos = nextPos; + totalWidth += charWidth; + } + + if (pos == dataBegin) { + return titleWidth; + } + + // insert what character we can in. + nsAutoString title(mTitle); + title.Truncate(pos - dataBegin); + mCroppedTitle.Insert(title, 0); + } + break; + + case CropLeft: + { + ClusterReverseIterator iter(mTitle.Data(), mTitle.Length()); + const char16_t* dataEnd = iter; + const char16_t* prevPos = dataEnd; + nscoord charWidth; + nscoord totalWidth = 0; + + while (!iter.AtEnd()) { + iter.Next(); + const char16_t* pos = iter; + ptrdiff_t length = prevPos - pos; + charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length, + *fm, + drawTarget); + if (totalWidth + charWidth > aWidth) { + break; + } + + if (UCS2_CHAR_IS_BIDI(*pos)) { + mState |= NS_FRAME_IS_BIDI; + } + prevPos = pos; + totalWidth += charWidth; + } + + if (prevPos == dataEnd) { + return titleWidth; + } + + nsAutoString copy; + mTitle.Right(copy, dataEnd - prevPos); + mCroppedTitle += copy; + } + break; + + case CropCenter: + { + nscoord stringWidth = + nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm, + aRenderingContext); + 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; + ClusterIterator leftIter(mTitle.Data(), mTitle.Length()); + ClusterReverseIterator rightIter(mTitle.Data(), mTitle.Length()); + const char16_t* dataBegin = leftIter; + const char16_t* dataEnd = rightIter; + const char16_t* leftPos = dataBegin; + const char16_t* rightPos = dataEnd; + const char16_t* pos; + ptrdiff_t length; + nsAutoString leftString, rightString; + + while (leftPos < rightPos) { + leftIter.Next(); + pos = leftIter; + length = pos - leftPos; + charWidth = nsLayoutUtils::AppUnitWidthOfString(leftPos, length, + *fm, + drawTarget); + if (totalWidth + charWidth > aWidth) { + break; + } + + if (UCS2_CHAR_IS_BIDI(*leftPos)) { + mState |= NS_FRAME_IS_BIDI; + } + + leftString.Append(leftPos, length); + leftPos = pos; + totalWidth += charWidth; + + if (leftPos >= rightPos) { + break; + } + + rightIter.Next(); + pos = rightIter; + length = rightPos - pos; + charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length, + *fm, + drawTarget); + if (totalWidth + charWidth > aWidth) { + break; + } + + if (UCS2_CHAR_IS_BIDI(*pos)) { + mState |= NS_FRAME_IS_BIDI; + } + + rightString.Insert(pos, 0, length); + rightPos = pos; + totalWidth += charWidth; + } + + mCroppedTitle = leftString + kEllipsis + rightString; + } + break; + } + + return nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm, + aRenderingContext); +} + +#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 char16_t kLastChar = mTitle.Last(); + if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':')) + 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; + } + } +} + +void +nsTextBoxFrame::RecomputeTitle() +{ + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle); + + // This doesn't handle language-specific uppercasing/lowercasing + // rules, unlike textruns. + uint8_t textTransform = StyleText()->mTextTransform; + if (textTransform == NS_STYLE_TEXT_TRANSFORM_UPPERCASE) { + ToUpperCase(mTitle); + } else if (textTransform == NS_STYLE_TEXT_TRANSFORM_LOWERCASE) { + ToLowerCase(mTitle); + } + // We can't handle NS_STYLE_TEXT_TRANSFORM_CAPITALIZE because we + // have no clue about word boundaries here. We also don't handle + // NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH. +} + +void +nsTextBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + if (!aOldStyleContext) { + // We're just being initialized + return; + } + + const nsStyleText* oldTextStyle = aOldStyleContext->PeekStyleText(); + // We should really have oldTextStyle here, since we asked for our + // nsStyleText during Init(), but if it's not there for some reason + // just assume the worst and recompute mTitle. + if (!oldTextStyle || + oldTextStyle->mTextTransform != StyleText()->mTextTransform) { + RecomputeTitle(); + UpdateAccessTitle(); + } +} + +NS_IMETHODIMP +nsTextBoxFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState) +{ + if (mNeedsReflowCallback) { + nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this); + if (cb) { + PresContext()->PresShell()->PostReflowCallback(cb); + } + mNeedsReflowCallback = false; + } + + nsresult rv = nsLeafBoxFrame::DoXULLayout(aBoxLayoutState); + + CalcDrawRect(*aBoxLayoutState.GetRenderingContext()); + + const nsStyleText* textStyle = StyleText(); + + nsRect scrollBounds(nsPoint(0, 0), GetSize()); + nsRect textRect = mTextDrawRect; + + RefPtr<nsFontMetrics> fontMet = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + nsBoundingMetrics metrics = + fontMet->GetInkBoundsForVisualOverflow(mCroppedTitle.get(), + mCroppedTitle.Length(), + aBoxLayoutState.GetRenderingContext()->GetDrawTarget()); + + WritingMode wm = GetWritingMode(); + LogicalRect tr(wm, textRect, GetSize()); + + tr.IStart(wm) -= metrics.leftBearing; + tr.ISize(wm) = metrics.width; + // In DrawText() we always draw with the baseline at MaxAscent() (relative to mTextDrawRect), + tr.BStart(wm) += fontMet->MaxAscent() - metrics.ascent; + tr.BSize(wm) = metrics.ascent + metrics.descent; + + textRect = tr.GetPhysicalRect(wm, GetSize()); + + // 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::MarkIntrinsicISizesDirty() +{ + mNeedsRecalc = true; + nsLeafBoxFrame::MarkIntrinsicISizesDirty(); +} + +void +nsTextBoxFrame::GetTextSize(nsRenderingContext& aRenderingContext, + const nsString& aString, + nsSize& aSize, nscoord& aAscent) +{ + RefPtr<nsFontMetrics> fontMet = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + aSize.height = fontMet->MaxHeight(); + aSize.width = + nsLayoutUtils::AppUnitWidthOfStringBidi(aString, this, *fontMet, + aRenderingContext); + aAscent = fontMet->MaxAscent(); +} + +void +nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState) +{ + if (mNeedsRecalc) { + nsSize size; + nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext(); + if (rendContext) { + GetTextSize(*rendContext, mTitle, size, mAscent); + if (GetWritingMode().IsVertical()) { + Swap(size.width, size.height); + } + mTextSize = size; + mNeedsRecalc = false; + } + } +} + +void +nsTextBoxFrame::CalcDrawRect(nsRenderingContext &aRenderingContext) +{ + WritingMode wm = GetWritingMode(); + + LogicalRect textRect(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm)); + nsMargin borderPadding; + GetXULBorderAndPadding(borderPadding); + textRect.Deflate(wm, LogicalMargin(wm, borderPadding)); + + // determine (cropped) title and underline position + // determine (cropped) title which fits in aRect, and its width + // (where "width" is the text measure along its baseline, i.e. actually + // a physical height in vertical writing modes) + nscoord titleWidth = + CalculateTitleForWidth(aRenderingContext, textRect.ISize(wm)); + +#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 outerISize = textRect.ISize(wm); + textRect.ISize(wm) = titleWidth; + + // Align our text within the overall rect by checking our text-align property. + const nsStyleText* textStyle = StyleText(); + if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_CENTER) { + textRect.IStart(wm) += (outerISize - textRect.ISize(wm)) / 2; + } else if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_END || + (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_LEFT && + !wm.IsBidiLTR()) || + (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_RIGHT && + wm.IsBidiLTR())) { + textRect.IStart(wm) += (outerISize - textRect.ISize(wm)); + } + + mTextDrawRect = textRect.GetPhysicalRect(wm, GetSize()); +} + +/** + * Ok return our dimensions + */ +nsSize +nsTextBoxFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) +{ + CalcTextSize(aBoxLayoutState); + + nsSize size = mTextSize; + DISPLAY_PREF_SIZE(this, size); + + AddBorderAndPadding(size); + bool widthSet, heightSet; + nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet); + + return size; +} + +/** + * Ok return our dimensions + */ +nsSize +nsTextBoxFrame::GetXULMinSize(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 && mCropType != CropAuto) { + if (GetWritingMode().IsVertical()) { + size.height = 0; + } else { + size.width = 0; + } + } + + AddBorderAndPadding(size); + bool widthSet, heightSet; + nsIFrame::AddXULMinSize(aBoxLayoutState, this, size, widthSet, heightSet); + + return size; +} + +nscoord +nsTextBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) +{ + CalcTextSize(aBoxLayoutState); + + nscoord ascent = mAscent; + + nsMargin m(0,0,0,0); + GetXULBorderAndPadding(m); + + WritingMode wm = GetWritingMode(); + ascent += LogicalMargin(wm, m).BStart(wm); + + return ascent; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +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 + EventStateManager* 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/nsTextBoxFrame.h b/layout/xul/nsTextBoxFrame.h new file mode 100644 index 000000000..ca1b88748 --- /dev/null +++ b/layout/xul/nsTextBoxFrame.h @@ -0,0 +1,134 @@ +/* -*- 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; +class nsFontMetrics; + +class nsTextBoxFrame : public nsLeafBoxFrame +{ +public: + NS_DECL_QUERYFRAME_TARGET(nsTextBoxFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override; + NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override; + virtual void MarkIntrinsicISizesDirty() override; + + enum CroppingStyle { CropNone, CropLeft, CropRight, CropCenter, CropAuto }; + + friend nsIFrame* NS_NewTextBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* asPrevInFlow) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + void UpdateAttributes(nsIAtom* aAttribute, + bool& aResize, + bool& aRedraw); + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual ~nsTextBoxFrame(); + + void PaintTitle(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, + const nscolor* aOverrideColor); + + nsRect GetComponentAlphaBounds(); + + virtual bool ComputesOwnOverflowArea() override; + + void GetCroppedTitle(nsString& aTitle) const { aTitle = mCroppedTitle; } + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + +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(); + + // Recompute our title, ignoring the access key but taking into + // account text-transform. + void RecomputeTitle(); + + // REVIEW: SORRY! Couldn't resist devirtualizing these + void LayoutTitle(nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aRect); + + void CalculateUnderline(DrawTarget* aDrawTarget, nsFontMetrics& aFontMetrics); + + void CalcTextSize(nsBoxLayoutState& aBoxLayoutState); + + void CalcDrawRect(nsRenderingContext &aRenderingContext); + + explicit nsTextBoxFrame(nsStyleContext* aContext); + + nscoord CalculateTitleForWidth(nsRenderingContext& aRenderingContext, + nscoord aWidth); + + void GetTextSize(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/nsTitleBarFrame.cpp b/layout/xul/nsTitleBarFrame.cpp new file mode 100644 index 000000000..2792403dc --- /dev/null +++ b/layout/xul/nsTitleBarFrame.cpp @@ -0,0 +1,172 @@ +/* -*- 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 "nsIDocShell.h" +#include "nsPIDOMWindow.h" +#include "nsDisplayList.h" +#include "nsContentUtils.h" +#include "mozilla/MouseEvents.h" + +using namespace mozilla; + +// +// NS_NewTitleBarFrame +// +// Creates a new TitleBar frame and returns it +// +nsIFrame* +NS_NewTitleBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTitleBarFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTitleBarFrame) + +nsTitleBarFrame::nsTitleBarFrame(nsStyleContext* aContext) +:nsBoxFrame(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); +} + +nsresult +nsTitleBarFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + bool doDefault = true; + + switch (aEvent->mMessage) { + + case eMouseDown: { + if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) { + // titlebar has no effect in non-chrome shells + nsCOMPtr<nsIDocShellTreeItem> dsti = aPresContext->GetDocShell(); + if (dsti) { + if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) { + // we're tracking. + mTrackingMouseMove = true; + + // start capture. + nsIPresShell::SetCapturingContent(GetContent(), CAPTURE_IGNOREALLOWED); + + // remember current mouse coordinates. + mLastPoint = aEvent->mRefPoint; + } + } + + *aEventStatus = nsEventStatus_eConsumeNoDefault; + doDefault = false; + } + } + break; + + + case eMouseUp: { + if (mTrackingMouseMove && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) { + // we're done tracking. + mTrackingMouseMove = false; + + // end capture + nsIPresShell::SetCapturingContent(nullptr, 0); + + *aEventStatus = nsEventStatus_eConsumeNoDefault; + doDefault = false; + } + } + break; + + case eMouseMove: { + if(mTrackingMouseMove) + { + LayoutDeviceIntPoint nsMoveBy = aEvent->mRefPoint - 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(); + LayoutDeviceIntRect bounds = widget->GetScreenBounds(); + + CSSPoint cssPos = (bounds.TopLeft() + nsMoveBy) + / aPresContext->CSSToDevPixelScale(); + menuPopupFrame->MoveTo(RoundedToInt(cssPos), false); + } + else { + nsIPresShell* presShell = aPresContext->PresShell(); + nsPIDOMWindowOuter *window = presShell->GetDocument()->GetWindow(); + if (window) { + window->MoveBy(nsMoveBy.x, nsMoveBy.y); + } + } + + *aEventStatus = nsEventStatus_eConsumeNoDefault; + + doDefault = false; + } + } + break; + + case eMouseClick: { + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent->IsLeftClickEvent()) { + MouseClicked(mouseEvent); + } + break; + } + + default: + break; + } + + if ( doDefault ) + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + else + return NS_OK; +} + +void +nsTitleBarFrame::MouseClicked(WidgetMouseEvent* aEvent) +{ + // Execute the oncommand event handler. + nsContentUtils::DispatchXULCommand(mContent, aEvent && aEvent->IsTrusted()); +} diff --git a/layout/xul/nsTitleBarFrame.h b/layout/xul/nsTitleBarFrame.h new file mode 100644 index 000000000..17279c578 --- /dev/null +++ b/layout/xul/nsTitleBarFrame.h @@ -0,0 +1,39 @@ +/* -*- 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 "mozilla/EventForwards.h" +#include "nsBoxFrame.h" + +class nsTitleBarFrame : public nsBoxFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewTitleBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + explicit nsTitleBarFrame(nsStyleContext* aContext); + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual nsresult HandleEvent(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual void MouseClicked(mozilla::WidgetMouseEvent* aEvent); + + void UpdateMouseThrough() override { AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); } + +protected: + bool mTrackingMouseMove; + mozilla::LayoutDeviceIntPoint mLastPoint; + +}; // class nsTitleBarFrame + +#endif /* nsTitleBarFrame_h___ */ diff --git a/layout/xul/nsXULLabelFrame.cpp b/layout/xul/nsXULLabelFrame.cpp new file mode 100644 index 000000000..22b875461 --- /dev/null +++ b/layout/xul/nsXULLabelFrame.cpp @@ -0,0 +1,116 @@ +/* -*- 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 "mozilla/EventStateManager.h" +#include "nsXULLabelFrame.h" +#include "nsHTMLParts.h" +#include "nsNameSpaceManager.h" + +using namespace mozilla; + +nsIFrame* +NS_NewXULLabelFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + nsXULLabelFrame* it = new (aPresShell) nsXULLabelFrame(aContext); + + it->AddStateBits(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 + EventStateManager* 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, + nsContainerFrame* 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); +} + +nsresult +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_FRAME_DUMP +nsresult +nsXULLabelFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("XULLabel"), aResult); +} +#endif diff --git a/layout/xul/nsXULLabelFrame.h b/layout/xul/nsXULLabelFrame.h new file mode 100644 index 000000000..e97fc3fca --- /dev/null +++ b/layout/xul/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, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::XULLabelFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + +protected: + explicit 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/nsXULPopupManager.cpp b/layout/xul/nsXULPopupManager.cpp new file mode 100644 index 000000000..6e8cc3dda --- /dev/null +++ b/layout/xul/nsXULPopupManager.cpp @@ -0,0 +1,2870 @@ +/* -*- 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 "nsMenuBarListener.h" +#include "nsContentUtils.h" +#include "nsIDOMDocument.h" +#include "nsIDOMEvent.h" +#include "nsIDOMXULElement.h" +#include "nsIDOMXULMenuListElement.h" +#include "nsIXULDocument.h" +#include "nsIXULTemplateBuilder.h" +#include "nsCSSFrameConstructor.h" +#include "nsGlobalWindow.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 "nsIDOMKeyEvent.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/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Services.h" + +using namespace mozilla; +using namespace mozilla::dom; + +static_assert(nsIDOMKeyEvent::DOM_VK_HOME == nsIDOMKeyEvent::DOM_VK_END + 1 && + nsIDOMKeyEvent::DOM_VK_LEFT == nsIDOMKeyEvent::DOM_VK_END + 2 && + nsIDOMKeyEvent::DOM_VK_UP == nsIDOMKeyEvent::DOM_VK_END + 3 && + nsIDOMKeyEvent::DOM_VK_RIGHT == nsIDOMKeyEvent::DOM_VK_END + 4 && + nsIDOMKeyEvent::DOM_VK_DOWN == nsIDOMKeyEvent::DOM_VK_END + 5, + "nsXULPopupManager assumes some keyCode values are consecutive"); + +const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = { + { + eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END + eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME + eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_LEFT + eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP + eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_RIGHT + eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN + }, + { + eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END + eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME + eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_LEFT + eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP + eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_RIGHT + eNavigationDirection_After // nsIDOMKeyEvent::DOM_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); + } +} + +bool nsXULPopupManager::sDevtoolsDisableAutoHide = false; + +const char* kPrefDevtoolsDisableAutoHide = + "ui.popup.disable_autohide"; + +NS_IMPL_ISUPPORTS(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); + } + Preferences::AddBoolVarCache(&sDevtoolsDisableAutoHide, + kPrefDevtoolsDisableAutoHide, 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 char16_t *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, bool aFlush, + const nsIntPoint* pos, nsIContent** aLastRolledUp) +{ + // We can disable the autohide behavior via a pref to ease debugging. + if (nsXULPopupManager::sDevtoolsDisableAutoHide) { + // Required on linux to allow events to work on other targets. + if (mWidget) { + mWidget->CaptureRollupEvents(nullptr, false); + } + return false; + } + + 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 mousedown event is going to + // processed. Keep going up the menu chain to get the first level menu of + // the same type. If a different type is encountered it means we have, + // for example, a menulist or context menu inside a panel, and we want to + // treat these as distinct. 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()) { + nsMenuChainItem* parent = first->GetParent(); + if (first->Frame()->PopupType() != parent->Frame()->PopupType() || + first->IsContextMenu() != parent->IsContextMenu()) { + break; + } + first = parent; + } + + + *aLastRolledUp = first->Content(); + } + + ConsumeOutsideClicksResult consumeResult = item->Frame()->ConsumeOutsideClicks(); + consume = (consumeResult == ConsumeOutsideClicks_True); + + bool rollup = true; + + // If norolluponanchor is true, then don't rollup when clicking the anchor. + // This would be used to allow adjusting the caret position in an + // autocomplete field without hiding the popup for example. + bool noRollupOnAnchor = (!consume && pos && + item->Frame()->GetContent()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::norolluponanchor, nsGkAtoms::_true, eCaseMatters)); + + // When ConsumeOutsideClicks_ParentOnly is used, always consume the click + // when the click was over the anchor. This way, clicking on a menu doesn't + // reopen the menu. + if ((consumeResult == ConsumeOutsideClicks_ParentOnly || noRollupOnAnchor) && pos) { + nsMenuPopupFrame* popupFrame = item->Frame(); + nsIntRect anchorRect; + if (popupFrame->IsAnchored()) { + // Check if the popup has a screen anchor rectangle. If not, get the rectangle + // from the anchor element. + anchorRect = popupFrame->GetScreenAnchorRect(); + if (anchorRect.x == -1 || anchorRect.y == -1) { + nsCOMPtr<nsIContent> anchor = popupFrame->GetAnchor(); + + // Check if the anchor has indicated another node to use for checking + // for roll-up. That way, we can anchor a popup on anonymous content or + // an individual icon, while clicking elsewhere within a button or other + // container doesn't result in us re-opening the popup. + if (anchor) { + nsAutoString consumeAnchor; + anchor->GetAttr(kNameSpaceID_None, nsGkAtoms::consumeanchor, + consumeAnchor); + if (!consumeAnchor.IsEmpty()) { + nsIDocument* doc = anchor->GetOwnerDocument(); + nsIContent* newAnchor = doc->GetElementById(consumeAnchor); + if (newAnchor) { + anchor = newAnchor; + } + } + } + + if (anchor && anchor->GetPrimaryFrame()) { + anchorRect = anchor->GetPrimaryFrame()->GetScreenRect(); + } + } + } + + // It's possible that some other element is above the anchor at the same + // position, but the only thing that would happen is that the mouse + // event will get consumed, so here only a quick coordinates check is + // done rather than a slower complete check of what is at that location. + nsPresContext* presContext = item->Frame()->PresContext(); + nsIntPoint posCSSPixels(presContext->DevPixelsToIntCSSPixels(pos->x), + presContext->DevPixelsToIntCSSPixels(pos->y)); + if (anchorRect.Contains(posCSSPixels)) { + if (consumeResult == ConsumeOutsideClicks_ParentOnly) { + consume = true; + } + + if (noRollupOnAnchor) { + rollup = false; + } + } + } + + if (rollup) { + // 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(); + } + } + + nsPresContext* presContext = item->Frame()->PresContext(); + RefPtr<nsViewManager> viewManager = presContext->PresShell()->GetViewManager(); + + HidePopup(item->Content(), true, true, false, true, lastPopup); + + if (aFlush) { + // The popup's visibility doesn't update until the minimize animation has + // finished, so call UpdateWidgetGeometry to update it right away. + viewManager->UpdateWidgetGeometry(); + } + } + } + + 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(nsPIDOMWindowOuter* 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. + + // The items are added to a list so that they can be adjusted bottom to top. + nsTArray<nsMenuPopupFrame *> list; + + 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->GetUncomposedDoc(); + if (document) { + if (nsPIDOMWindowOuter* window = document->GetWindow()) { + window = window->GetPrivateRoot(); + if (window == aWindow) { + list.AppendElement(frame); + } + } + } + } + } + + item = item->GetParent(); + } + + for (int32_t l = list.Length() - 1; l >= 0; l--) { + list[l]->SetPopupPosition(nullptr, true, false, true); + } +} + +void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell) +{ + if (aPresShell->GetDocument()) { + AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow()); + } +} + +static +nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame) +{ + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame); + if (!menuPopupFrame) + return nullptr; + + // no point moving or resizing hidden popups + if (!menuPopupFrame->IsVisible()) + 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; + + nsView* view = menuPopupFrame->GetView(); + if (!view) + return; + + // Don't do anything if the popup is already at the specified location. This + // prevents recursive calls when a popup is positioned. + LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup); + nsIWidget* widget = menuPopupFrame->GetWidget(); + if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y && + (!widget || widget->GetClientOffset() == + menuPopupFrame->GetLastClientOffset())) { + return; + } + + // 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, false, true); + } + else { + CSSPoint cssPos = LayoutDeviceIntPoint::FromUnknownPoint(aPnt) + / menuPopupFrame->PresContext()->CSSToDevPixelScale(); + menuPopupFrame->MoveTo(RoundedToInt(cssPos), false); + } +} + +void +nsXULPopupManager::PopupResized(nsIFrame* aFrame, LayoutDeviceIntSize aSize) +{ + nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame); + if (!menuPopupFrame) + return; + + nsView* view = menuPopupFrame->GetView(); + if (!view) + return; + + LayoutDeviceIntRect 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; + + nsIContent* popup = menuPopupFrame->GetContent(); + + // Only set the width and height if the popup already has these attributes. + if (!popup->HasAttr(kNameSpaceID_None, nsGkAtoms::width) || + !popup->HasAttr(kNameSpaceID_None, nsGkAtoms::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(); + + CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width), + presContext->DevPixelsToIntCSSPixels(aSize.height)); + + 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->GetUncomposedDoc(); + 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 = LayoutDeviceIntPoint(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"); + WidgetEvent* event = aEvent->WidgetEventPtr(); + if (event) { + WidgetInputEvent* inputEvent = event->AsInputEvent(); + if (inputEvent) { + mCachedModifiers = inputEvent->mModifiers; + } + nsIDocument* doc = aPopup->GetUncomposedDoc(); + 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->mClass == eMouseEventClass || + event->mClass == eMouseScrollEventClass || + event->mClass == eWheelEventClass) && + !event->AsGUIEvent()->mWidget) { + // 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 = LayoutDeviceIntPoint(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; + +#ifdef XP_MACOSX + nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aMenu); + bool isNonEditableMenulist = false; + if (menulist) { + bool editable; + menulist->GetEditable(&editable); + isNonEditableMenulist = !editable; + } + + if (isNonEditableMenulist) { + position.AssignLiteral("selection"); + } + else +#endif + + 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(menuFrame->GetAnchor(), nullptr, position, 0, 0, + MenuPopupAnchorType_Node, 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, MenuPopupAnchorType_Node, 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::ShowPopupAtScreenRect(nsIContent* aPopup, + const nsAString& aPosition, + const nsIntRect& aRect, + bool aIsContextMenu, + bool aAttributesOverride, + nsIDOMEvent* aTriggerEvent) +{ + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); + if (!popupFrame || !MayShowPopup(popupFrame)) + return; + + nsCOMPtr<nsIContent> triggerContent; + InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent)); + + popupFrame->InitializePopupAtRect(triggerContent, aPosition, + aRect, aAttributesOverride); + + 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 = LayoutDeviceIntPoint(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<mozIDOMWindowProxy> window; + fm->GetFocusedWindow(getter_AddRefs(window)); + if (!window) + return; + + auto* piWindow = nsPIDOMWindowOuter::From(window); + MOZ_ASSERT(piWindow); + + nsCOMPtr<nsIDocument> focusedDoc = piWindow->GetDoc(); + if (!focusedDoc) + return; + + nsIPresShell* presShell = focusedDoc->GetShell(); + if (!presShell) + return; + + RefPtr<nsCaret> caret = presShell->GetCaret(); + if (!caret) + return; + caret->SchedulePaint(); + } +} + +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. + nsAutoString ignorekeys; + aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys); + if (ignorekeys.EqualsLiteral("true")) { + item->SetIgnoreKeys(eIgnoreKeys_True); + } else if (ignorekeys.EqualsLiteral("shortcuts")) { + item->SetIgnoreKeys(eIgnoreKeys_Shortcuts); + } + + 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); + 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, + bool aIsCancel, + 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; + } else { + // When the popup is in the popuppositioning state, it will not be in the + // mPopups list. We need another way to find it and make sure it does not + // continue the popup showing process. + popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); + if (popupFrame) { + if (popupFrame->PopupState() == ePopupPositioning) { + // Do basically the same thing we would have done if we had found the + // popup in the mPopups list. + deselectMenu = aDeselectMenu; + popupToHide = aPopup; + type = popupFrame->PopupType(); + } else { + // The popup is not positioning. If we were supposed to have handled + // closing it, it should have been in mPopups or mNoHidePanels + popupFrame = nullptr; + } + } + } + + 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, aIsCancel); + NS_DispatchToCurrentThread(event); + } + else { + FirePopupHidingEvent(popupToHide, nextPopup, lastPopup, + popupFrame->PresContext(), type, deselectMenu, aIsCancel); + } + } +} + +// This is used to hide the popup after a transition finishes. +class TransitionEnder : public nsIDOMEventListener +{ +protected: + virtual ~TransitionEnder() { } + +public: + + nsCOMPtr<nsIContent> mContent; + bool mDeselectMenu; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder) + + TransitionEnder(nsIContent* aContent, bool aDeselectMenu) + : mContent(aContent), mDeselectMenu(aDeselectMenu) + { + } + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override + { + mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false); + + nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + + // Now hide the popup. There could be other properties transitioning, but + // we'll assume they all end at the same time and just hide the popup upon + // the first one ending. + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && popupFrame) { + pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr, + popupFrame->PopupType(), mDeselectMenu); + } + + return NS_OK; + } +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent); + +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; + WidgetMouseEvent event(true, eXULPopupHidden, nullptr, + WidgetMouseEvent::eReal); + EventDispatcher::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, false); + } + } +} + +void +nsXULPopupManager::HidePopup(nsIFrame* aFrame) +{ + nsMenuPopupFrame* popup = do_QueryFrame(aFrame); + if (popup) + HidePopup(aFrame->GetContent(), false, true, false, 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) +{ + // 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); +} + +void +nsXULPopupManager::EnableRollup(nsIContent* aPopup, bool aShouldRollup) +{ +#ifndef MOZ_GTK + if (aShouldRollup) { + nsMenuChainItem* item = mNoHidePanels; + while (item) { + if (item->Content() == aPopup) { + item->Detach(&mNoHidePanels); + nsIContent* oldmenu = nullptr; + if (mPopups) + oldmenu = mPopups->Content(); + item->SetParent(mPopups); + mPopups = item; + SetCaptureState(oldmenu); + return; + } + item = item->GetParent(); + } + } else { + nsMenuChainItem* item = mPopups; + while (item) { + if (item->Content() == aPopup) { + item->Detach(&mPopups); + item->SetParent(mNoHidePanels); + mNoHidePanels = item; + SetCaptureState(nullptr); + return; + } + item = item->GetParent(); + } + } +#endif +} + +bool +nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected) +{ + nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell()); + 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); +} + +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); + } + + 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; + WidgetMouseEvent event(true, eXULPopupShowing, nullptr, + WidgetMouseEvent::eReal); + + // coordinates are relative to the root widget + nsPresContext* rootPresContext = + presShell->GetPresContext()->GetRootPresContext(); + if (rootPresContext) { + rootPresContext->PresShell()->GetViewManager()-> + GetRootWidget(getter_AddRefs(event.mWidget)); + } + else { + event.mWidget = nullptr; + } + + event.mRefPoint = mCachedMousePoint; + event.mModifiers = mCachedModifiers; + EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status); + + mCachedMousePoint = LayoutDeviceIntPoint(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->GetUncomposedDoc(); + + // 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 { + // Now check if we need to fire the popuppositioned event. If not, call + // ShowPopupCallback directly. + + // The popuppositioned event only fires on arrow panels for now. + if (popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::arrow, eCaseMatters)) { + popupFrame->ShowWithPositionedEvent(); + presShell->FrameNeedsReflow(popupFrame, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + else { + ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem); + } + } + } +} + +void +nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPresContext *aPresContext, + nsPopupType aPopupType, + bool aDeselectMenu, + bool aIsCancel) +{ + nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell(); + mozilla::Unused << presShell; // This presShell may be keeping things alive on non GTK platforms + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetMouseEvent event(true, eXULPopupHiding, nullptr, + WidgetMouseEvent::eReal); + EventDispatcher::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->GetUncomposedDoc(); + + // 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()) { + // XXXndeakin + // If an attempt was made to hide this popup before the popupshown event + // fired, then ePopupShown is set here even though it should be + // ePopupVisible. This probably isn't worth the hassle of handling. + popupFrame->SetPopupState(ePopupShown); + } + else { + // If the popup has an animate attribute and it is not set to false, check + // if it has a closing transition and wait for it to finish. The transition + // may still occur either way, but the view will be hidden and you won't be + // able to see it. If there is a next popup, indicating that mutliple popups + // are rolling up, don't wait and hide the popup right away since the effect + // would likely be undesirable. Transitions are currently disabled on Linux + // due to rendering issues on certain configurations. +#ifndef MOZ_WIDGET_GTK + if (!aNextPopup && aPopup->HasAttr(kNameSpaceID_None, nsGkAtoms::animate)) { + // If animate="false" then don't transition at all. If animate="cancel", + // only show the transition if cancelling the popup or rolling up. + // Otherwise, always show the transition. + nsAutoString animate; + aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::animate, animate); + + if (!animate.EqualsLiteral("false") && + (!animate.EqualsLiteral("cancel") || aIsCancel)) { + presShell->FlushPendingNotifications(Flush_Layout); + + // Get the frame again in case the flush caused it to go away + popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); + if (!popupFrame) + return; + + if (nsLayoutUtils::HasCurrentTransitions(popupFrame)) { + RefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu); + aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"), + ender, false, false); + return; + } + } + } +#endif + + 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(); + + // Iterate over both lists of popups + nsMenuChainItem* item = mPopups; + for (int32_t list = 0; list < 2; list++) { + while (item) { + // Skip panels which are not visible as well as popups that + // are transparent to mouse events. + if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) { + aPopups.AppendElement(item->Frame()); + } + + item = item->GetParent(); + } + + item = mNoHidePanels; + } +} + +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->GetUncomposedDoc() == aDocument && + aIsTooltip == mOpeningPopup->IsXULElement(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()->GetUncomposedDoc() == 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 == ePopupPositioning || + 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<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell(); + nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti); + if (!baseWin) + return false; + + nsCOMPtr<nsIDocShellTreeItem> root; + dsti->GetRootTreeItem(getter_AddRefs(root)); + if (!root) { + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> rootWin = root->GetWindow(); + + // chrome shells can always open popups, but other types of shells can only + // open popups when they are focused and visible + if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) { + // only allow popups in active windows + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm || !rootWin) + return false; + + nsCOMPtr<mozIDOMWindowProxy> 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; + } + +#ifdef XP_MACOSX + if (rootWin) { + auto globalWin = nsGlobalWindow::Cast(rootWin.get()); + if (globalWin->IsInModalState()) { + return false; + } + } +#endif + + // 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, false); + break; + } + + child = child->GetChild(); + } + } + + item->Detach(&mPopups); + delete item; + break; + } + + item = item->GetParent(); + } + + HidePopupsInList(popupsToHide); +} + +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() != eIgnoreKeys_True) { + newTarget = item->Content()->GetComposedDoc(); + } + isForMenu = item->PopupType() == ePopupTypeMenu; + } + else if (mActiveMenuBar) { + newTarget = mActiveMenuBar->GetContent()->GetComposedDoc(); + 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->GetUncomposedDoc(); + if (!document) { + return; + } + + for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild(); + grandChild; + grandChild = grandChild->GetNextSibling()) { + if (grandChild->IsXULElement(nsGkAtoms::menugroup)) { + if (grandChild->GetChildCount() == 0) { + continue; + } + grandChild = grandChild->GetFirstChild(); + } + if (grandChild->IsXULElement(nsGkAtoms::menuitem)) { + // 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 + RefPtr<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); + } + } + } + if (!grandChild->GetNextSibling() && + grandChild->GetParent()->IsXULElement(nsGkAtoms::menugroup)) { + grandChild = grandChild->GetParent(); + } + } +} + +// 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, false); + } + + mTimerMenu = nullptr; +} + +void +nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent) +{ + if (mCloseTimer && mTimerMenu == aMenuParent) { + mCloseTimer->Cancel(); + mCloseTimer = nullptr; + mTimerMenu = nullptr; + } +} + +bool +nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, + nsMenuPopupFrame* aFrame) +{ + // On Windows, don't check shortcuts when the accelerator key is down. +#ifdef XP_WIN + WidgetInputEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent(); + if (evt && evt->IsAccel()) { + return false; + } +#endif + + 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, true); + if (action) { + WidgetGUIEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsGUIEvent(); + 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 >= nsIDOMKeyEvent::DOM_VK_END && + aKeyCode <= nsIDOMKeyEvent::DOM_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, 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, true); + 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, true); + 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, false); + return true; + } + } + + return false; +} + +bool +nsXULPopupManager::HandleKeyboardEventWithKeyCode( + nsIDOMKeyEvent* aKeyEvent, + nsMenuChainItem* aTopVisibleMenuItem) +{ + uint32_t keyCode; + aKeyEvent->GetKeyCode(&keyCode); + + // Escape should close panels, but the other keys should have no effect. + if (aTopVisibleMenuItem && + aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) { + if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) { + HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true); + aKeyEvent->AsEvent()->StopPropagation(); + aKeyEvent->AsEvent()->StopCrossProcessForwarding(); + aKeyEvent->AsEvent()->PreventDefault(); + } + return true; + } + + bool consume = (mPopups || mActiveMenuBar); + switch (keyCode) { + case nsIDOMKeyEvent::DOM_VK_UP: + case nsIDOMKeyEvent::DOM_VK_DOWN: +#ifndef XP_MACOSX + // roll up the popup when alt+up/down are pressed within a menulist. + bool alt; + aKeyEvent->GetAltKey(&alt); + if (alt && aTopVisibleMenuItem && aTopVisibleMenuItem->Frame()->IsMenuList()) { + Rollup(0, false, nullptr, nullptr); + break; + } + MOZ_FALLTHROUGH; +#endif + + case nsIDOMKeyEvent::DOM_VK_LEFT: + case nsIDOMKeyEvent::DOM_VK_RIGHT: + case nsIDOMKeyEvent::DOM_VK_HOME: + case nsIDOMKeyEvent::DOM_VK_END: + HandleKeyboardNavigation(keyCode); + break; + + case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: + case nsIDOMKeyEvent::DOM_VK_PAGE_UP: + if (aTopVisibleMenuItem) { + aTopVisibleMenuItem->Frame()->ChangeByPage(keyCode == nsIDOMKeyEvent::DOM_VK_PAGE_UP); + } + break; + + case nsIDOMKeyEvent::DOM_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 (aTopVisibleMenuItem) { + HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true); + } else if (mActiveMenuBar) { + mActiveMenuBar->MenuClosed(); + } + break; + + case nsIDOMKeyEvent::DOM_VK_TAB: +#ifndef XP_MACOSX + case nsIDOMKeyEvent::DOM_VK_F10: +#endif + if (aTopVisibleMenuItem && + !aTopVisibleMenuItem->Frame()->GetContent()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::activateontab, nsGkAtoms::_true, eCaseMatters)) { + // close popups or deactivate menubar when Tab or F10 are pressed + Rollup(0, false, nullptr, nullptr); + break; + } else if (mActiveMenuBar) { + mActiveMenuBar->MenuClosed(); + break; + } + // Intentional fall-through to RETURN case + MOZ_FALLTHROUGH; + + case nsIDOMKeyEvent::DOM_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; + WidgetGUIEvent* GUIEvent = aKeyEvent->AsEvent()-> + WidgetEventPtr()->AsGUIEvent(); + + if (aTopVisibleMenuItem) { + menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent); + } else if (mActiveMenuBar) { + menuToOpen = mActiveMenuBar->Enter(GUIEvent); + } + if (menuToOpen) { + nsCOMPtr<nsIContent> content = menuToOpen->GetContent(); + ShowMenu(content, true, false); + } + break; + } + + default: + return false; + } + + if (consume) { + aKeyEvent->AsEvent()->StopPropagation(); + aKeyEvent->AsEvent()->StopCrossProcessForwarding(); + aKeyEvent->AsEvent()->PreventDefault(); + } + return true; +} + +nsMenuFrame* +nsXULPopupManager::GetNextMenuItem(nsContainerFrame* aParent, + nsMenuFrame* aStart, + bool aIsPopup) +{ + nsPresContext* presContext = aParent->PresContext(); + auto insertion = presContext->PresShell()-> + FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr); + nsContainerFrame* immediateParent = insertion.mParentFrame; + if (!immediateParent) + immediateParent = aParent; + + nsIFrame* currFrame = nullptr; + if (aStart) { + if (aStart->GetNextSibling()) + currFrame = aStart->GetNextSibling(); + else if (aStart->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup)) + currFrame = aStart->GetParent()->GetNextSibling(); + } + else + currFrame = immediateParent->PrincipalChildList().FirstChild(); + + while (currFrame) { + // See if it's a menu item. + nsIContent* currFrameContent = currFrame->GetContent(); + if (IsValidMenuItem(currFrameContent, aIsPopup)) { + return do_QueryFrame(currFrame); + } + if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) && + currFrameContent->GetChildCount() > 0) + currFrame = currFrame->PrincipalChildList().FirstChild(); + else if (!currFrame->GetNextSibling() && + currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup)) + currFrame = currFrame->GetParent()->GetNextSibling(); + else + currFrame = currFrame->GetNextSibling(); + } + + currFrame = immediateParent->PrincipalChildList().FirstChild(); + + // Still don't have anything. Try cycling from the beginning. + while (currFrame && currFrame != aStart) { + // See if it's a menu item. + nsIContent* currFrameContent = currFrame->GetContent(); + if (IsValidMenuItem(currFrameContent, aIsPopup)) { + return do_QueryFrame(currFrame); + } + if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) && + currFrameContent->GetChildCount() > 0) + currFrame = currFrame->PrincipalChildList().FirstChild(); + else if (!currFrame->GetNextSibling() && + currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup)) + currFrame = currFrame->GetParent()->GetNextSibling(); + else + currFrame = currFrame->GetNextSibling(); + } + + // No luck. Just return our start value. + return aStart; +} + +nsMenuFrame* +nsXULPopupManager::GetPreviousMenuItem(nsContainerFrame* aParent, + nsMenuFrame* aStart, + bool aIsPopup) +{ + nsPresContext* presContext = aParent->PresContext(); + auto insertion = presContext->PresShell()-> + FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr); + nsContainerFrame* immediateParent = insertion.mParentFrame; + if (!immediateParent) + immediateParent = aParent; + + const nsFrameList& frames(immediateParent->PrincipalChildList()); + + nsIFrame* currFrame = nullptr; + if (aStart) { + if (aStart->GetPrevSibling()) + currFrame = aStart->GetPrevSibling(); + else if (aStart->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup)) + currFrame = aStart->GetParent()->GetPrevSibling(); + } + else + currFrame = frames.LastChild(); + + while (currFrame) { + // See if it's a menu item. + nsIContent* currFrameContent = currFrame->GetContent(); + if (IsValidMenuItem(currFrameContent, aIsPopup)) { + return do_QueryFrame(currFrame); + } + if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) && + currFrameContent->GetChildCount() > 0) { + const nsFrameList& menugroupFrames(currFrame->PrincipalChildList()); + currFrame = menugroupFrames.LastChild(); + } + else if (!currFrame->GetPrevSibling() && + currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup)) + currFrame = currFrame->GetParent()->GetPrevSibling(); + else + 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. + nsIContent* currFrameContent = currFrame->GetContent(); + if (IsValidMenuItem(currFrameContent, aIsPopup)) { + return do_QueryFrame(currFrame); + } + if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) && + currFrameContent->GetChildCount() > 0) { + const nsFrameList& menugroupFrames(currFrame->PrincipalChildList()); + currFrame = menugroupFrames.LastChild(); + } + else if (!currFrame->GetPrevSibling() && + currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup)) + currFrame = currFrame->GetParent()->GetPrevSibling(); + else + currFrame = currFrame->GetPrevSibling(); + } + + // No luck. Just return our start value. + return aStart; +} + +bool +nsXULPopupManager::IsValidMenuItem(nsIContent* aContent, bool aOnPopup) +{ + if (aContent->IsXULElement()) { + if (!aContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) { + return false; + } + } + else if (!aOnPopup || !aContent->IsHTMLElement(nsGkAtoms::option)) { + return false; + } + + nsMenuFrame* menuFrame = do_QueryFrame(aContent->GetPrimaryFrame()); + + bool skipNavigatingDisabledMenuItem = true; + if (aOnPopup && (!menuFrame || menuFrame->GetParentMenuListType() == eNotMenuList)) { + 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); + + //handlers shouldn't be triggered by non-trusted events. + bool trustedEvent = false; + aEvent->GetIsTrusted(&trustedEvent); + if (!trustedEvent) { + return NS_OK; + } + + nsAutoString eventType; + aEvent->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; + + if (item->IgnoreKeys() == eIgnoreKeys_Shortcuts) { + aKeyEvent->AsEvent()->StopCrossProcessForwarding(); + return NS_OK; + } + } + + aKeyEvent->AsEvent()->StopPropagation(); + aKeyEvent->AsEvent()->StopCrossProcessForwarding(); + aKeyEvent->AsEvent()->PreventDefault(); + + return NS_OK; // I am consuming event +} + +nsresult +nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent) +{ + nsMenuChainItem* item = GetTopVisibleMenu(); + if (item && item->Frame()->IsMenuLocked()) + return NS_OK; + + if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) { + 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; + + // Since a menu was open, stop propagation of the event to keep other event + // listeners from becoming confused. + if (!item || item->IgnoreKeys() != eIgnoreKeys_Shortcuts) { + aKeyEvent->AsEvent()->StopPropagation(); + } + + 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. + nsMenuChainItem* item = GetTopVisibleMenu(); + if (mPopups && item && !item->Frame()->IsMenuList()) { + Rollup(0, false, nullptr, nullptr); + } else if (mActiveMenuBar) { + mActiveMenuBar->MenuClosed(); + } + + // Clear the item to avoid bugs as it may have been deleted during rollup. + item = nullptr; + } + aKeyEvent->AsEvent()->StopPropagation(); + aKeyEvent->AsEvent()->PreventDefault(); + } + } + + aKeyEvent->AsEvent()->StopCrossProcessForwarding(); + return NS_OK; +} + +nsresult +nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent) +{ + // Don't check prevent default flag -- menus always get first shot at key events. + + nsMenuChainItem* item = GetTopVisibleMenu(); + if (item && + (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) { + return NS_OK; + } + + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); + NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED); + // if a menu is open or a menubar is active, it consumes the key event + bool consume = (mPopups || mActiveMenuBar); + + WidgetInputEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent(); + bool isAccel = evt && evt->IsAccel(); + + // When ignorekeys="shortcuts" is used, we don't call preventDefault on the + // key event when the accelerator key is pressed. This allows another + // listener to handle keys. For instance, this allows global shortcuts to + // still apply while a menu is open. + if (item && item->IgnoreKeys() == eIgnoreKeys_Shortcuts && isAccel) { + consume = false; + } + + HandleShortcutNavigation(keyEvent, nullptr); + + aKeyEvent->AsEvent()->StopCrossProcessForwarding(); + if (consume) { + aKeyEvent->AsEvent()->StopPropagation(); + aKeyEvent->AsEvent()->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->GetUncomposedDoc(); + if (pm && document) { + nsIPresShell* presShell = document->GetShell(); + if (presShell) { + nsPresContext* context = presShell->GetPresContext(); + if (context) { + pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup, + context, mPopupType, mDeselectMenu, mIsRollup); + } + } + } + + return NS_OK; +} + +bool +nsXULPopupPositionedEvent::DispatchIfNeeded(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem) +{ + // The popuppositioned event only fires on arrow panels for now. + if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::arrow, eCaseMatters)) { + nsCOMPtr<nsIRunnable> event = + new nsXULPopupPositionedEvent(aPopup, aIsContextMenu, aSelectFirstItem); + NS_DispatchToCurrentThread(event); + + return true; + } + + return false; +} + +NS_IMETHODIMP +nsXULPopupPositionedEvent::Run() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame()); + if (popupFrame) { + // At this point, hidePopup may have been called but it currently has no + // way to stop this event. However, if hidePopup was called, the popup + // will now be in the hiding or closed state. If we are in the shown or + // positioning state instead, we can assume that we are still clear to + // open/move the popup + nsPopupState state = popupFrame->PopupState(); + if (state != ePopupPositioning && state != ePopupShown) { + return NS_OK; + } + nsEventStatus status = nsEventStatus_eIgnore; + WidgetMouseEvent event(true, eXULPopupPositioned, nullptr, + WidgetMouseEvent::eReal); + EventDispatcher::Dispatch(mPopup, popupFrame->PresContext(), + &event, nullptr, &status); + + // Get the popup frame and make sure it is still in the positioning + // state. If it isn't, someone may have tried to reshow or hide it + // during the popuppositioned event. + // Alternately, this event may have been fired in reponse to moving the + // popup rather than opening it. In that case, we are done. + nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame()); + if (popupFrame && popupFrame->PopupState() == ePopupPositioning) { + pm->ShowPopupCallback(mPopup, popupFrame, mIsContextMenu, mSelectFirstItem); + } + } + } + + 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(); + RefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager(); + mozilla::Unused << kungFuDeathGrip; // Not referred to directly within this function + + // Deselect ourselves. + if (mCloseMenuMode != CloseMenuMode_None) + menuFrame->SelectMenu(false); + + AutoHandlingUserInputStatePusher 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, false); + + return NS_OK; +} diff --git a/layout/xul/nsXULPopupManager.h b/layout/xul/nsXULPopupManager.h new file mode 100644 index 000000000..41644d7e9 --- /dev/null +++ b/layout/xul/nsXULPopupManager.h @@ -0,0 +1,823 @@ +/* -*- 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 "mozilla/Logging.h" +#include "nsIContent.h" +#include "nsIRollupListener.h" +#include "nsIDOMEventListener.h" +#include "nsPoint.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsIReflowCallback.h" +#include "nsThreadUtils.h" +#include "nsStyleConsts.h" +#include "nsWidgetInitData.h" +#include "mozilla/Attributes.h" +#include "Units.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 nsContainerFrame; +class nsMenuFrame; +class nsMenuPopupFrame; +class nsMenuBarFrame; +class nsMenuParent; +class nsIDOMKeyEvent; +class nsIDocShellTreeItem; +class nsPIDOMWindowOuter; + +// 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 +}; + +enum nsIgnoreKeys { + eIgnoreKeys_False, + eIgnoreKeys_True, + eIgnoreKeys_Shortcuts, +}; + +#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) + +static_assert(NS_STYLE_DIRECTION_LTR == 0 && NS_STYLE_DIRECTION_RTL == 1, + "Left to Right should be 0 and Right to Left should be 1"); + +/** + * 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 - nsIDOMKeyEvent::DOM_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 + nsIgnoreKeys mIgnoreKeys; // indicates how keyboard listeners should be used + + nsMenuChainItem* mParent; + nsMenuChainItem* mChild; + +public: + nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aIsContext, nsPopupType aPopupType) + : mFrame(aFrame), + mPopupType(aPopupType), + mIsContext(aIsContext), + mOnMenuBar(false), + mIgnoreKeys(eIgnoreKeys_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; } + nsIgnoreKeys IgnoreKeys() { return mIgnoreKeys; } + void SetIgnoreKeys(nsIgnoreKeys aIgnoreKeys) { mIgnoreKeys = aIgnoreKeys; } + bool IsOnMenuBar() { return mOnMenuBar; } + 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 mozilla::Runnable +{ +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() override; + +private: + nsCOMPtr<nsIContent> mPopup; + bool mIsContextMenu; + bool mSelectFirstItem; +}; + +// this class is used for dispatching popuphiding events asynchronously. +class nsXULPopupHidingEvent : public mozilla::Runnable +{ +public: + nsXULPopupHidingEvent(nsIContent *aPopup, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPopupType aPopupType, + bool aDeselectMenu, + bool aIsCancel) + : mPopup(aPopup), + mNextPopup(aNextPopup), + mLastPopup(aLastPopup), + mPopupType(aPopupType), + mDeselectMenu(aDeselectMenu), + mIsRollup(aIsCancel) + { + NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupHidingEvent constructor"); + // aNextPopup and aLastPopup may be null + } + + NS_IMETHOD Run() override; + +private: + nsCOMPtr<nsIContent> mPopup; + nsCOMPtr<nsIContent> mNextPopup; + nsCOMPtr<nsIContent> mLastPopup; + nsPopupType mPopupType; + bool mDeselectMenu; + bool mIsRollup; +}; + +// this class is used for dispatching popuppositioned events asynchronously. +class nsXULPopupPositionedEvent : public mozilla::Runnable +{ +public: + explicit nsXULPopupPositionedEvent(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem) + : mPopup(aPopup) + , mIsContextMenu(aIsContextMenu) + , mSelectFirstItem(aSelectFirstItem) + { + NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor"); + } + + NS_IMETHOD Run() override; + + // Asynchronously dispatch a popuppositioned event at aPopup if this is a + // panel that should receieve such events. Return true if the event was sent. + static bool DispatchIfNeeded(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem); + +private: + nsCOMPtr<nsIContent> mPopup; + bool mIsContextMenu; + bool mSelectFirstItem; +}; + +// this class is used for dispatching menu command events asynchronously. +class nsXULMenuCommandEvent : public mozilla::Runnable +{ +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() 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 final : public nsIDOMEventListener, + public nsIRollupListener, + public nsITimerCallback, + public nsIObserver +{ + +public: + friend class nsXULPopupShowingEvent; + friend class nsXULPopupHidingEvent; + friend class nsXULPopupPositionedEvent; + friend class nsXULMenuCommandEvent; + friend class TransitionEnder; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIDOMEVENTLISTENER + + // nsIRollupListener + virtual bool Rollup(uint32_t aCount, bool aFlush, + const nsIntPoint* pos, nsIContent** aLastRolledUp) override; + virtual bool ShouldRollupOnMouseWheelEvent() override; + virtual bool ShouldConsumeOnMouseWheelEvent() override; + virtual bool ShouldRollupOnMouseActivate() override; + virtual uint32_t GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain) override; + virtual void NotifyGeometryChange() override {} + virtual nsIWidget* GetRollupWidget() 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(); + + // This should be called when a window is moved or resized to adjust the + // popups accordingly. + void AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow); + void AdjustPopupsOnWindowChange(nsIPresShell* aPresShell); + + // 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(nsContainerFrame* aParent, + nsMenuFrame* aStart, + bool aIsPopup); + static nsMenuFrame* GetNextMenuItem(nsContainerFrame* 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(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 popup anchored at a screen rectangle specified by aRect. + * The remaining arguments are similar to ShowPopup. + */ + void ShowPopupAtScreenRect(nsIContent* aPopup, + const nsAString& aPosition, + const nsIntRect& aRect, + bool aIsContextMenu, + bool aAttributesOverride, + 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. + * aIsCancel - true if this popup is hiding due to being cancelled. + * 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, + bool aIsCancel, + 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); + + /** + * Enable or disable the dynamic noautohide state of a panel. + * + * aPanel - the panel whose state is to change + * aShouldRollup - whether the panel is no longer noautohide + */ + void EnableRollup(nsIContent* aPopup, bool aShouldRollup); + + /** + * 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 + * given device pixel size aSize. + */ + void PopupResized(nsIFrame* aFrame, mozilla::LayoutDeviceIntSize 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. 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); + } + + /** + * Handles the keyboard event with keyCode value. Returns true if the event + * has been handled. + */ + bool HandleKeyboardEventWithKeyCode(nsIDOMKeyEvent* aKeyEvent, + nsMenuChainItem* aTopVisibleMenuItem); + + 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. This function can + // cause style changes and frame destruction. + void HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames); + + // 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 + * aIsCancel - true if this popup is hiding due to being cancelled. + */ + void FirePopupHidingEvent(nsIContent* aPopup, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPresContext *aPresContext, + nsPopupType aPopupType, + bool aDeselectMenu, + bool aIsCancel); + + /** + * 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. + mozilla::LayoutDeviceIntPoint mCachedMousePoint; + + // cached modifiers + mozilla::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; + + // If true, all popups won't hide automatically on blur + static bool sDevtoolsDisableAutoHide; +}; + +#endif diff --git a/layout/xul/nsXULTooltipListener.cpp b/layout/xul/nsXULTooltipListener.cpp new file mode 100644 index 000000000..110f8c0e0 --- /dev/null +++ b/layout/xul/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 "nsIDOMMouseEvent.h" +#include "nsIDOMXULDocument.h" +#include "nsIDOMXULElement.h" +#include "nsIDocument.h" +#include "nsGkAtoms.h" +#include "nsMenuPopupFrame.h" +#include "nsIServiceManager.h" +#include "nsIDragService.h" +#include "nsIDragSession.h" +#ifdef MOZ_XUL +#include "nsITreeView.h" +#endif +#include "nsIScriptContext.h" +#include "nsPIDOMWindow.h" +#ifdef MOZ_XUL +#include "nsXULPopupManager.h" +#endif +#include "nsIRootBox.h" +#include "nsIBoxObject.h" +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() +#include "mozilla/dom/BoxObject.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_ISUPPORTS(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->GetUncomposedDoc()); + 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->IsXULElement(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) { + if (targetContent->IsAnyOfXULElements(nsGkAtoms::menupopup, + nsGkAtoms::panel, + 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 +void +nsXULTooltipListener::ToolbarTipsPrefChanged(const char *aPref, + void *aClosure) +{ + sShowTooltips = + Preferences::GetBool("browser.chrome.toolbar_tips", sShowTooltips); +} + +////////////////////////////////////////////////////////////////////////// +//// 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->GetComposedDoc(); + 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; + nsAutoString 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; + int16_t colType = -1; + if (col) { + col->GetType(&colType); + } + if (row >= 0 && obj.EqualsLiteral("text") && + colType != nsITreeColumn::TYPE_PASSWORD) { + 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->GetComposedDoc()); + if (xulDoc) { + // Make sure the target node is still attached to some document. + // It might have been deleted. + if (sourceNode->IsInComposedDoc()) { +#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->GetComposedDoc(); + 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_WARNING_ASSERTION(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, 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->IsXULElement(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->GetComposedDoc(); + if (!document) { + NS_WARNING("Unable to retrieve the tooltip node document."); + return NS_ERROR_FAILURE; + } + nsPIDOMWindowOuter *window = document->GetWindow(); + if (!window) { + return NS_OK; + } + + if (window->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() && aTarget->IsInUncomposedDoc()) { + // tooltip must be an id, use getElementById to find it + //XXXsmaug If aTarget is in shadow dom, should we use + // ShadowRoot::GetElementById()? + 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->GetComposedDoc(); + 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) +{ + RefPtr<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/nsXULTooltipListener.h b/layout/xul/nsXULTooltipListener.h new file mode 100644 index 000000000..51e0a2c3f --- /dev/null +++ b/layout/xul/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 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 void 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/reftest/image-scaling-min-height-1-ref.xul b/layout/xul/reftest/image-scaling-min-height-1-ref.xul new file mode 100644 index 000000000..595450fe4 --- /dev/null +++ b/layout/xul/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/reftest/image-scaling-min-height-1.xul b/layout/xul/reftest/image-scaling-min-height-1.xul new file mode 100644 index 000000000..5c45d6b0c --- /dev/null +++ b/layout/xul/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/reftest/image-size-ref.xul b/layout/xul/reftest/image-size-ref.xul new file mode 100644 index 000000000..c67364686 --- /dev/null +++ b/layout/xul/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="box-sizing: border-box; width: 80px; height: 64px; border: 8px solid yellow;"/><html:img + src="image4x3.png" style="box-sizing: border-box; width: 72px; height: 58px; border: 8px solid green;"/><html:img + src="image4x3.png" style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid yellow;"/><html:img + src="image4x3.png" style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/><html:img + src="image4x3.png" style="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="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; box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 72px; height: 58px; padding: 8px; box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 24px; height: 22px; padding: 8px; box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 24px; height: 22px; padding: 8px; box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 67px; height: 60px; padding: 4px 2px 8px 1px; box-sizing: border-box;"/><html:img + src="image4x3.png" style="width: 11px; height: 18px; padding: 4px 2px 8px 1px; 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="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/> +</html:div> + +<html:div><html:img + src="image4x3.png" style="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="box-sizing: border-box; width: 60px; height: 49px; border: 8px solid green;"/><html:img + src="image4x3.png" style="box-sizing: border-box; width: 112px; height: 88px; border: 8px solid yellow;"/><html:img + src="image4x3.png" style="box-sizing: border-box; width: 96px; height: 76px; border: 8px solid green;"/><html:img + src="image4x3.png" style="box-sizing: border-box; width: 112px; height: 88px; border: 8px solid yellow;"/><html:img + src="image4x3.png" style="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="box-sizing: border-box; width: 60px; height: 49px; padding: 8px;"/><html:img + src="image4x3.png" style="box-sizing: border-box; width: 112px; height: 88px; padding: 8px;"/><html:img + src="image4x3.png" style="box-sizing: border-box; width: 96px; height: 76px; padding: 8px;"/><html:img + src="image4x3.png" style="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="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/></html:div> + +<html:div><html:div style="box-sizing: border-box; width: 24px; height: 22px; border: 8px solid green;"/></html:div> + +</window> diff --git a/layout/xul/reftest/image-size.xul b/layout/xul/reftest/image-size.xul new file mode 100644 index 000000000..732dc51f3 --- /dev/null +++ b/layout/xul/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/reftest/image4x3.png b/layout/xul/reftest/image4x3.png Binary files differnew file mode 100644 index 000000000..6719bf5ce --- /dev/null +++ b/layout/xul/reftest/image4x3.png diff --git a/layout/xul/reftest/popup-explicit-size-ref.xul b/layout/xul/reftest/popup-explicit-size-ref.xul new file mode 100644 index 000000000..4c864f103 --- /dev/null +++ b/layout/xul/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/reftest/popup-explicit-size.xul b/layout/xul/reftest/popup-explicit-size.xul new file mode 100644 index 000000000..6f1bf8582 --- /dev/null +++ b/layout/xul/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/reftest/reftest-stylo.list b/layout/xul/reftest/reftest-stylo.list new file mode 100644 index 000000000..5a96bfb11 --- /dev/null +++ b/layout/xul/reftest/reftest-stylo.list @@ -0,0 +1,14 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == textbox-multiline-noresize.xul textbox-multiline-noresize.xul +# reference is blank on Android (due to no native theme support?) +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == textbox-multiline-resize.xul textbox-multiline-resize.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == popup-explicit-size.xul popup-explicit-size.xul +# Initial mulet triage: parity with B2G/B2G Desktop +random-if(Android) skip-if((B2G&&browserIsRemote)||Mulet) == image-size.xul image-size.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == image-scaling-min-height-1.xul image-scaling-min-height-1.xul +# Initial mulet triage: parity with B2G/B2G Desktop +skip-if((B2G&&browserIsRemote)||Mulet) == textbox-text-transform.xul textbox-text-transform.xul +# Initial mulet triage: parity with B2G/B2G Desktop diff --git a/layout/xul/reftest/reftest.list b/layout/xul/reftest/reftest.list new file mode 100644 index 000000000..39f353577 --- /dev/null +++ b/layout/xul/reftest/reftest.list @@ -0,0 +1,6 @@ +fails-if(Android) == 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 +== textbox-text-transform.xul textbox-text-transform-ref.xul diff --git a/layout/xul/reftest/textbox-multiline-noresize.xul b/layout/xul/reftest/textbox-multiline-noresize.xul new file mode 100644 index 000000000..27945ae9a --- /dev/null +++ b/layout/xul/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/reftest/textbox-multiline-ref.xul b/layout/xul/reftest/textbox-multiline-ref.xul new file mode 100644 index 000000000..23d279a75 --- /dev/null +++ b/layout/xul/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/reftest/textbox-multiline-resize.xul b/layout/xul/reftest/textbox-multiline-resize.xul new file mode 100644 index 000000000..d7a25a669 --- /dev/null +++ b/layout/xul/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/reftest/textbox-text-transform-ref.xul b/layout/xul/reftest/textbox-text-transform-ref.xul new file mode 100644 index 000000000..cbe95e079 --- /dev/null +++ b/layout/xul/reftest/textbox-text-transform-ref.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="UPPERCASE"/> +<label value="lowercase"/> +</window> diff --git a/layout/xul/reftest/textbox-text-transform.xul b/layout/xul/reftest/textbox-text-transform.xul new file mode 100644 index 000000000..5de1c6af2 --- /dev/null +++ b/layout/xul/reftest/textbox-text-transform.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 style="text-transform: uppercase" value="uppercase"/> +<label style="text-transform: lowercase" value="LOWERCASE"/> +</window> diff --git a/layout/xul/test/browser.ini b/layout/xul/test/browser.ini new file mode 100644 index 000000000..3fa28b153 --- /dev/null +++ b/layout/xul/test/browser.ini @@ -0,0 +1,8 @@ +[DEFAULT] + +[browser_bug685470.js] +[browser_bug703210.js] +[browser_bug706743.js] +skip-if = (os == 'linux') # Bug 1157576 +[browser_bug1163304.js] +skip-if = os != 'linux' && os != 'win' // Due to testing menubar behavior with keyboard diff --git a/layout/xul/test/browser_bug1163304.js b/layout/xul/test/browser_bug1163304.js new file mode 100644 index 000000000..b8e9409dd --- /dev/null +++ b/layout/xul/test/browser_bug1163304.js @@ -0,0 +1,35 @@ +function test() { + waitForExplicitFinish(); + + let searchBar = BrowserSearch.searchBar; + searchBar.focus(); + + let DOMWindowUtils = EventUtils._getDOMWindowUtils(); + is(DOMWindowUtils.IMEStatus, DOMWindowUtils.IME_STATUS_ENABLED, + "IME should be available when searchbar has focus"); + + let searchPopup = document.getElementById("PopupSearchAutoComplete"); + searchPopup.addEventListener("popupshown", function (aEvent) { + searchPopup.removeEventListener("popupshown", arguments.callee); + setTimeout(function () { + is(DOMWindowUtils.IMEStatus, DOMWindowUtils.IME_STATUS_ENABLED, + "IME should be available even when the popup of searchbar is open"); + searchPopup.addEventListener("popuphidden", function (aEvent) { + searchPopup.removeEventListener("popuphidden", arguments.callee); + setTimeout(function () { + is(DOMWindowUtils.IMEStatus, DOMWindowUtils.IME_STATUS_DISABLED, + "IME should not be available when menubar is active"); + // Inactivate the menubar (and restore the focus to the searchbar + EventUtils.synthesizeKey("VK_ESCAPE", {}); + is(DOMWindowUtils.IMEStatus, DOMWindowUtils.IME_STATUS_ENABLED, + "IME should be available after focus is back to the searchbar"); + finish(); + }, 0); + }); + // Activate the menubar, then, the popup should be closed + EventUtils.synthesizeKey("VK_ALT", {}); + }, 0); + }); + // Open popup of the searchbar + EventUtils.synthesizeKey("VK_F4", {}); +} diff --git a/layout/xul/test/browser_bug685470.js b/layout/xul/test/browser_bug685470.js new file mode 100644 index 000000000..c86231bc7 --- /dev/null +++ b/layout/xul/test/browser_bug685470.js @@ -0,0 +1,19 @@ +add_task(function* () { + const html = "<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>"; + yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html," + html); + + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve); + }); + + yield BrowserTestUtils.synthesizeMouseAtCenter("#p1", { type: "mousemove" }, + gBrowser.selectedBrowser); + yield BrowserTestUtils.synthesizeMouseAtCenter("#p1", { }, gBrowser.selectedBrowser); + + // Wait until the tooltip timeout triggers that would normally have opened the popup. + yield new Promise(resolve => setTimeout(resolve, 0)); + is(document.getElementById("aHTMLTooltip").state, "closed", "local tooltip is closed"); + is(document.getElementById("remoteBrowserTooltip").state, "closed", "remote tooltip is closed"); + + gBrowser.removeCurrentTab(); +}); diff --git a/layout/xul/test/browser_bug703210.js b/layout/xul/test/browser_bug703210.js new file mode 100644 index 000000000..cfd626b1c --- /dev/null +++ b/layout/xul/test/browser_bug703210.js @@ -0,0 +1,33 @@ +add_task(function* () { + const url = "data:text/html," + + "<html onmousemove='event.stopPropagation()'" + + " onmouseenter='event.stopPropagation()' onmouseleave='event.stopPropagation()'" + + " onmouseover='event.stopPropagation()' onmouseout='event.stopPropagation()'>" + + "<p id=\"p1\" title=\"tooltip is here\">This paragraph has a tooltip.</p>" + + "<p id=\"p2\">This paragraph doesn't have tooltip.</p></html>"; + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + let browser = gBrowser.selectedBrowser; + + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve); + }); + + // Send a mousemove at a known position to start the test. + yield BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser); + let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown", false, event => { + is(event.originalTarget.localName, "tooltip", "tooltip is showing"); + return true; + }); + yield BrowserTestUtils.synthesizeMouseAtCenter("#p1", { type: "mousemove" }, browser); + yield popupShownPromise; + + let popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden", false, event => { + is(event.originalTarget.localName, "tooltip", "tooltip is hidden"); + return true; + }); + yield BrowserTestUtils.synthesizeMouseAtCenter("#p2", { type: "mousemove" }, browser); + yield popupHiddenPromise; + + gBrowser.removeCurrentTab(); +}); diff --git a/layout/xul/test/browser_bug706743.js b/layout/xul/test/browser_bug706743.js new file mode 100644 index 000000000..b7262e812 --- /dev/null +++ b/layout/xul/test/browser_bug706743.js @@ -0,0 +1,84 @@ +add_task(function* () { + const url = "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>"; + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + let browser = gBrowser.selectedBrowser; + + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve); + }); + + // Send a mousemove at a known position to start the test. + yield BrowserTestUtils.synthesizeMouse("#target", -5, -5, { type: "mousemove" }, browser); + + // show tooltip by mousemove into target. + let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown"); + yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser); + yield popupShownPromise; + + // hide tooltip by mousemove to outside. + let popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden"); + yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser); + yield popupHiddenPromise; + + // 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. + + // Emulate a buggy mousemove event. widget might dispatch mousemove event + // during drag. + + function tooltipNotExpected() + { + ok(false, "tooltip is shown during drag"); + } + addEventListener("popupshown", tooltipNotExpected, true); + + let dragService = Components.classes["@mozilla.org/widget/dragservice;1"]. + getService(Components.interfaces.nsIDragService); + dragService.startDragSession(); + yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser); + + yield new Promise(resolve => setTimeout(resolve, 100)); + removeEventListener("popupshown", tooltipNotExpected, true); + dragService.endDragSession(true); + + yield BrowserTestUtils.synthesizeMouse("#target", -5, -5, { type: "mousemove" }, browser); + + // If tooltip listener used a flag for managing D&D state, we would need + // to test if the tooltip is shown after drag. + + // show tooltip by mousemove into target. + popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown"); + yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser); + yield popupShownPromise; + + // hide tooltip by mousemove to outside. + popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden"); + yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser); + yield popupHiddenPromise; + + // Show tooltip after mousedown + popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown"); + yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousemove" }, browser); + yield popupShownPromise; + + popupHiddenPromise = BrowserTestUtils.waitForEvent(document, "popuphidden"); + yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mousedown" }, browser); + yield popupHiddenPromise; + + yield BrowserTestUtils.synthesizeMouse("#target", 5, 15, { type: "mouseup" }, browser); + yield BrowserTestUtils.synthesizeMouse("#target", -5, 15, { type: "mousemove" }, browser); + + ok(true, "tooltips appear properly"); + + gBrowser.removeCurrentTab(); +}); + + + + diff --git a/layout/xul/test/chrome.ini b/layout/xul/test/chrome.ini new file mode 100644 index 000000000..2a647b6b7 --- /dev/null +++ b/layout/xul/test/chrome.ini @@ -0,0 +1,24 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + window_resizer.xul + window_resizer_element.xul + +[test_bug159346.xul] +[test_bug372685.xul] +[test_bug381167.xhtml] +[test_bug393970.xul] +[test_bug398982-1.xul] +[test_bug398982-2.xul] +[test_bug467442.xul] +[test_bug477754.xul] +[test_bug703150.xul] +[test_bug987230.xul] +skip-if = os == 'linux' # No native mousedown event on Linux +[test_popupReflowPos.xul] +[test_popupSizeTo.xul] +[test_popupZoom.xul] +[test_resizer.xul] +[test_stack.xul] +[test_submenuClose.xul] +[test_windowminmaxsize.xul] diff --git a/layout/xul/test/mochitest.ini b/layout/xul/test/mochitest.ini new file mode 100644 index 000000000..540abc730 --- /dev/null +++ b/layout/xul/test/mochitest.ini @@ -0,0 +1,12 @@ +[DEFAULT] + +[test_bug386386.html] +[test_bug394800.xhtml] +[test_bug511075.html] +skip-if = toolkit == 'android' #bug 798806 +[test_bug563416.html] +[test_bug1197913.xul] +skip-if = toolkit == 'android' +[test_resizer_incontent.xul] +[test_splitter.xul] +skip-if = toolkit == 'android' # no XUL theme diff --git a/layout/xul/test/test_bug1197913.xul b/layout/xul/test/test_bug1197913.xul new file mode 100644 index 000000000..6f922402d --- /dev/null +++ b/layout/xul/test/test_bug1197913.xul @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1197913 +--> +<window title="Mozilla Bug 1197913" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="SimpleTest.waitForFocus(nextTest, window)"> + <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"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1197913" + target="_blank">Mozilla Bug 1197913</a> + </body> + + <hbox align="center" pack="center"> + <menulist> + <menupopup> + <menuitem label="Car" /> + <menuitem label="Taxi" id="target" /> + <menuitem label="Bus" /> + </menupopup> + </menulist> + </hbox> + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + SimpleTest.waitForExplicitFinish(); + + let menulist = document.getElementsByTagName("menulist")[0]; + let menuitem = document.getElementById("target"); + + function onDOMMenuItemActive(e) { + menuitem.removeEventListener("DOMMenuItemActive", onDOMMenuItemActive); + + synthesizeMouse(menuitem, 0, 0, { type: "mousemove" }); + synthesizeMouse(menuitem, -1, 0, { type: "mousemove" }); + + setTimeout(() => { + if (navigator.platform.toLowerCase().startsWith("win")) { + ok(menuitem.getAttribute("_moz-menuactive")); + } else { + ok(!menuitem.getAttribute("_moz-menuactive")); + } + + SimpleTest.finish(); + }); + } + + function onPopupShown(e) { + menulist.removeEventListener("popupshown", onPopupShown); + menuitem.addEventListener("DOMMenuItemActive", onDOMMenuItemActive); + synthesizeMouse(menuitem, 0, 0, { type: "mousemove" }); + synthesizeMouse(menuitem, 1, 0, { type: "mousemove" }); + } + + function nextTest(e) { + menulist.addEventListener("popupshown", onPopupShown); + synthesizeMouseAtCenter(menulist, {}); + } + + ]]> + </script> +</window> 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_bug381167.xhtml b/layout/xul/test/test_bug381167.xhtml new file mode 100644 index 000000000..3f98d7a4d --- /dev/null +++ b/layout/xul/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('KeyboardEvent'); + 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/test/test_bug386386.html b/layout/xul/test/test_bug386386.html new file mode 100644 index 000000000..b39ccede2 --- /dev/null +++ b/layout/xul/test/test_bug386386.html @@ -0,0 +1,34 @@ +<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(); +SimpleTest.requestFlakyTimeout("untriaged"); +addLoadEvent(do_test); +</script> + +</body> +</html> diff --git a/layout/xul/test/test_bug393970.xul b/layout/xul/test/test_bug393970.xul new file mode 100644 index 000000000..fcb0f9e43 --- /dev/null +++ b/layout/xul/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/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_bug467442.xul b/layout/xul/test/test_bug467442.xul new file mode 100644 index 000000000..809e90cb8 --- /dev/null +++ b/layout/xul/test/test_bug467442.xul @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=467442 +--> +<window title="Mozilla Bug 467442" + onload="onload()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test code goes here --> + <popupset> + <panel id="panel"> + Hello. + </panel> + </popupset> + <hbox> + <button id="anchor" label="Anchor hello on here" style="transform: translate(100px, 0)"/> + </hbox> + <script type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function onload() { + /** Test for Bug 467442 **/ + let panel = document.getElementById("panel"); + let anchor = document.getElementById("anchor"); + + panel.addEventListener("popupshown", function onpopupshown() { + panel.removeEventListener("popupshown", onpopupshown); + let panelRect = panel.getBoundingClientRect(); + let anchorRect = anchor.getBoundingClientRect(); + is(panelRect.left, anchorRect.left, "Panel should be anchored to the button"); + panel.addEventListener("popuphidden", function onpopuphidden() { + panel.removeEventListener("popuphidden", onpopuphidden); + SimpleTest.finish(); + }); + panel.hidePopup(); + }); + + panel.openPopup(anchor, "after_start", 0, 0, false, false); + } + + ]]> + </script> + + <!-- 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=467442" + target="_blank">Mozilla Bug 467442</a> + </body> +</window> diff --git a/layout/xul/test/test_bug477754.xul b/layout/xul/test/test_bug477754.xul new file mode 100644 index 000000000..f72b1fffa --- /dev/null +++ b/layout/xul/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 - + 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/test/test_bug511075.html b/layout/xul/test/test_bug511075.html new file mode 100644 index 000000000..ecb228884 --- /dev/null +++ b/layout/xul/test/test_bug511075.html @@ -0,0 +1,121 @@ +<!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(); +SimpleTest.requestFlakyTimeout("untriaged"); + +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/test/test_bug563416.html b/layout/xul/test/test_bug563416.html new file mode 100644 index 000000000..dd245de0b --- /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="box-sizing:content-box; -moz-appearance:none; height: 0px; padding: 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..fab7d1677 --- /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/test/test_bug987230.xul b/layout/xul/test/test_bug987230.xul new file mode 100644 index 000000000..b2b47f470 --- /dev/null +++ b/layout/xul/test/test_bug987230.xul @@ -0,0 +1,125 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=987230 +--> +<window title="Mozilla Bug 987230" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="SimpleTest.waitForFocus(nextTest, window)"> + <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"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=987230" + target="_blank">Mozilla Bug 987230</a> + </body> + + <vbox> + <toolbar> + <toolbarbutton id="toolbarbutton-anchor" + label="Anchor" + consumeanchor="toolbarbutton-anchor" + onclick="onAnchorClick(event)" + style="padding: 50px !important; list-style-image: url(chrome://branding/content/icon32.png)"/> + </toolbar> + <spacer flex="1"/> + <hbox id="hbox-anchor" + style="padding: 20px" + onclick="onAnchorClick(event)"> + <hbox id="inner-anchor" + consumeanchor="hbox-anchor" + > + Another anchor + </hbox> + </hbox> + <spacer flex="1"/> + </vbox> + + <panel id="mypopup" + type="arrow" + onpopupshown="onMyPopupShown(event)" + onpopuphidden="onMyPopupHidden(event)">This is a test popup</panel> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 987230 **/ + SimpleTest.waitForExplicitFinish(); + + SimpleTest.requestCompleteLog(); + + const Ci = Components.interfaces; + const Cc = Components.classes; + + let platform = navigator.platform.toLowerCase(); + let isWindows = platform.startsWith("win"); + let mouseDown = isWindows ? 2 : 1; + let mouseUp = isWindows ? 4 : 2; + let mouseMove = isWindows ? 1 : 5; + let utils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let scale = utils.screenPixelsPerCSSPixel; + + + function synthesizeNativeMouseClick(aElement, aOffsetX, aOffsetY) { + let rect = aElement.getBoundingClientRect(); + let win = aElement.ownerDocument.defaultView; + let x = aOffsetX + win.mozInnerScreenX + rect.left; + let y = aOffsetY + win.mozInnerScreenY + rect.top; + + info("Sending mousedown+up for offsets: " + aOffsetX + ", " + aOffsetY + + "; innerscreen: " + win.mozInnerScreenX + ", " + win.mozInnerScreenY + + "; rect: " + rect.left + ", " + rect.top + "."); + info("Resulting x, y, scale: " + x + ", " + y + ", " + scale); + info("Final params: " + (x * scale) + ", " + (y * scale)); + utils.sendNativeMouseEvent(x * scale, y * scale, mouseDown, 0, null); + utils.sendNativeMouseEvent(x * scale, y * scale, mouseUp, 0, null); + } + + function onMyPopupHidden(e) { + ok(true, "Popup hidden"); + if (outerAnchor.id == "toolbarbutton-anchor") { + popupHasShown = false; + outerAnchor = document.getElementById("hbox-anchor"); + anchor = document.getElementById("inner-anchor"); + nextTest(); + } else { + //XXXgijs set mouse position back outside the iframe: + let frameRect = window.frameElement.getBoundingClientRect(); + let outsideOfFrameX = (window.mozInnerScreenX + frameRect.width + 100) * scale; + let outsideOfFrameY = Math.max(0, window.mozInnerScreenY - 100) * scale; + + info("Mousemove: " + outsideOfFrameX + ", " + outsideOfFrameY + + " (from innerscreen " + window.mozInnerScreenX + ", " + window.mozInnerScreenY + + " and rect width " + frameRect.width + " and scale " + scale + ")"); + utils.sendNativeMouseEvent(outsideOfFrameX, outsideOfFrameY, mouseMove, 0, null); + SimpleTest.finish(); + } + } + + let popupHasShown = false; + function onMyPopupShown(e) { + popupHasShown = true; + synthesizeNativeMouseClick(outerAnchor, 5, 5); + } + + function onAnchorClick(e) { + info("click: " + e.target.id); + ok(!popupHasShown, "Popup should only be shown once"); + popup.openPopup(anchor, "bottomcenter topright"); + } + + let popup = document.getElementById("mypopup"); + let outerAnchor = document.getElementById("toolbarbutton-anchor"); + let anchor = document.getAnonymousElementByAttribute(outerAnchor, "class", "toolbarbutton-icon"); + + function nextTest(e) { + synthesizeMouse(outerAnchor, 5, 5, {}); + } + + ]]> + </script> +</window> diff --git a/layout/xul/test/test_popupReflowPos.xul b/layout/xul/test/test_popupReflowPos.xul new file mode 100644 index 000000000..deeeff62e --- /dev/null +++ b/layout/xul/test/test_popupReflowPos.xul @@ -0,0 +1,76 @@ +<?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="XUL Panel reflow placement test" + xmlns:html="http://www.w3.org/1999/xhtml" + 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() + { + synthesizeMouseAtCenter(document.getElementById("thebutton"), {}, window); + } + + function popupShown(event) + { + document.getElementById("parent").className = ""; + + var buttonbcr = document.getElementById("thebutton").getBoundingClientRect(); + var popupbcr = document.getElementById("thepopup").getOuterScreenRect(); + + ok(Math.abs(popupbcr.x - window.mozInnerScreenX - buttonbcr.x) < 3, "x pos is correct"); + ok(Math.abs(popupbcr.y - window.mozInnerScreenY - buttonbcr.bottom) < 3, "y pos is correct"); + + event.target.hidePopup(); + } + + SimpleTest.waitForFocus(openPopup); + ]]></script> + + <html:style> + .mbox { + display: inline-block; + width: 33%; + height: 50px; + background: green; + vertical-align: middle; + } + .orange { + background: orange; + } + .change > .mbox { + width: 60px; + } + </html:style> + + <html:div style="width: 300px; height: 200px;"> + <html:div id="parent" class="change" style="background: red; border: 1px solid black; width: 300px; height: 200px;"> + <html:div class="mbox"></html:div> + <html:div class="mbox"></html:div> + <html:div class="mbox"></html:div> + <html:div class="mbox orange"> + + <button label="Show" type="menu" id="thebutton"> + <menupopup id="thepopup" onpopupshown="popupShown(event)" onpopuphidden="SimpleTest.finish()"> + <menuitem label="New"/> + <menuitem label="Open"/> + <menuitem label="Save"/> + <menuseparator/> + <menuitem label="Exit"/> + </menupopup> + </button> + + </html:div> + </html:div> + </html:div> + +</window> diff --git a/layout/xul/test/test_popupSizeTo.xul b/layout/xul/test/test_popupSizeTo.xul new file mode 100644 index 000000000..a135e1980 --- /dev/null +++ b/layout/xul/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/test/test_popupZoom.xul b/layout/xul/test/test_popupZoom.xul new file mode 100644 index 000000000..b8e15ba1d --- /dev/null +++ b/layout/xul/test/test_popupZoom.xul @@ -0,0 +1,57 @@ +<?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="XUL Panel zoom test" + 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(); + + var docviewer; + var savedzoom; + + function openPopup() + { + docviewer = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation) + .QueryInterface(Components.interfaces.nsIDocShell) + .contentViewer; + savedzoom = docviewer.fullZoom; + docviewer.fullZoom = 2; + + document.getElementById("panel"). + openPopup(document.getElementById("anchor"), "after_start", 0, 0, false, false, null); + } + + function popupShown(event) + { + var panel = document.getElementById("panel"); + var panelbcr = panel.getBoundingClientRect(); + var anchorbcr = document.getElementById("anchor").getBoundingClientRect(); + + ok(Math.abs(panelbcr.x - anchorbcr.x) < 3, "x pos is correct"); + ok(Math.abs(panelbcr.y - anchorbcr.bottom) < 3, "y pos is correct"); + + docviewer.fullZoom = savedzoom; + + event.target.hidePopup(); + } + + SimpleTest.waitForFocus(openPopup); + ]]></script> + +<description id="anchor" value="Sometext to this some texts"/> +<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/test/test_resizer.xul b/layout/xul/test/test_resizer.xul new file mode 100644 index 000000000..2ba971d05 --- /dev/null +++ b/layout/xul/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/test/test_resizer_incontent.xul b/layout/xul/test/test_resizer_incontent.xul new file mode 100644 index 000000000..068bd5bb1 --- /dev/null +++ b/layout/xul/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/test/test_splitter.xul b/layout/xul/test/test_splitter.xul new file mode 100644 index 000000000..697ad4be8 --- /dev/null +++ b/layout/xul/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/test/test_stack.xul b/layout/xul/test/test_stack.xul new file mode 100644 index 000000000..781a6f298 --- /dev/null +++ b/layout/xul/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(String(stackRect.width), child.getAttribute("stackwidth"), child.id + " stack width" + (dyn ? " dynamic" : "")); + is(String(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/test/test_submenuClose.xul b/layout/xul/test/test_submenuClose.xul new file mode 100644 index 000000000..907736d99 --- /dev/null +++ b/layout/xul/test/test_submenuClose.xul @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1181560 +--> +<window title="Mozilla Bug 1181560" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="SimpleTest.waitForFocus(nextTest, window)"> + <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"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181560" + target="_blank">Mozilla Bug 1181560</a> + </body> + + <vbox> + <menubar> + <menu id="menu" label="MyMenu"> + <menupopup> + <menuitem label="A"/> + <menu id="b" label="B"> + <menupopup> + <menuitem label="B1"/> + </menupopup> + </menu> + <menu id="c" label="C"> + <menupopup> + <menuitem label="C1"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menubar> + </vbox> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 1181560 **/ + SimpleTest.waitForExplicitFinish(); + + let menuB, menuC, mainMenu, menuBOpen, menuCOpen; + let menuBOpenCount = 0; + + function handleBOpens() { + menuBOpenCount++; + menuBOpen = true; + ok(!menuCOpen, "Menu C should not be open when menu B has opened"); + if (menuBOpenCount >= 2) { + SimpleTest.finish(); + return; + } + sendKey("LEFT", window); + sendKey("DOWN", window); + sendKey("RIGHT", window); + } + + function handleBCloses() { + menuBOpen = false; + } + + function handleCOpens() { + menuCOpen = true; + ok(!menuBOpen, "Menu B should not be open when menu C has opened"); + synthesizeMouseAtCenter(menuB, {}, window); + } + + function handleCCloses() { + menuCOpen = false; + } + + function nextTest(e) { + mainMenu = document.getElementById("menu"); + menuB = document.getElementById("b"); + menuC = document.getElementById("c"); + menuB.firstChild.addEventListener("popupshown", handleBOpens, false); + menuB.firstChild.addEventListener("popuphidden", handleBCloses, false); + menuC.firstChild.addEventListener("popupshown", handleCOpens, false); + menuC.firstChild.addEventListener("popuphidden", handleCCloses, false); + mainMenu.addEventListener("popupshown", ev => { + synthesizeMouseAtCenter(menuB, {}, window); + }); + mainMenu.open = true; + } + ]]> + </script> +</window> diff --git a/layout/xul/test/test_windowminmaxsize.xul b/layout/xul/test/test_windowminmaxsize.xul new file mode 100644 index 000000000..5909039cf --- /dev/null +++ b/layout/xul/test/test_windowminmaxsize.xul @@ -0,0 +1,245 @@ +<?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"); + + // Prevent event loop starvation as a result of popup events being + // synchronous. See bug 1131576. + SimpleTest.executeSoon(() => { + // Non-chrome shells require focus to open a popup. + SimpleTest.waitForFocus(() => { panel.openPopup() }); + }); + } +} + +function titledPanelWindowOpened(panelwindow) +{ + var panel = panelwindow.document.documentElement.firstChild; + panel.openPopup(); + panel.addEventListener("popupshown", () => doTitledPanelTest(panel), false); + panel.addEventListener("popuphidden", () => 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/test/window_resizer.xul b/layout/xul/test/window_resizer.xul new file mode 100644 index 000000000..4e349d125 --- /dev/null +++ b/layout/xul/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/test/window_resizer_element.xul b/layout/xul/test/window_resizer_element.xul new file mode 100644 index 000000000..b0c58d1a1 --- /dev/null +++ b/layout/xul/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/tree/TreeBoxObject.cpp b/layout/xul/tree/TreeBoxObject.cpp new file mode 100644 index 000000000..2265d9ee5 --- /dev/null +++ b/layout/xul/tree/TreeBoxObject.cpp @@ -0,0 +1,695 @@ +/* -*- 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/dom/TreeBoxObject.h" +#include "nsCOMPtr.h" +#include "nsIDOMXULElement.h" +#include "nsIScriptableRegion.h" +#include "nsIXULTemplateBuilder.h" +#include "nsTreeContentView.h" +#include "nsITreeSelection.h" +#include "ChildIterator.h" +#include "nsContentUtils.h" +#include "nsError.h" +#include "nsTreeBodyFrame.h" +#include "mozilla/dom/TreeBoxObjectBinding.h" +#include "nsITreeColumns.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ToJSValue.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(TreeBoxObject, BoxObject, + mView) + +NS_IMPL_ADDREF_INHERITED(TreeBoxObject, BoxObject) +NS_IMPL_RELEASE_INHERITED(TreeBoxObject, BoxObject) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TreeBoxObject) + NS_INTERFACE_MAP_ENTRY(nsITreeBoxObject) +NS_INTERFACE_MAP_END_INHERITING(BoxObject) + +void +TreeBoxObject::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; + + BoxObject::Clear(); +} + + +TreeBoxObject::TreeBoxObject() + : mTreeBody(nullptr) +{ +} + +TreeBoxObject::~TreeBoxObject() +{ +} + +static nsIContent* FindBodyElement(nsIContent* aParent) +{ + mozilla::dom::FlattenedChildIterator iter(aParent); + for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) { + mozilla::dom::NodeInfo *ni = content->NodeInfo(); + if (ni->Equals(nsGkAtoms::treechildren, kNameSpaceID_XUL)) { + return content; + } else if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) { + // There are nesting tree elements. Only the innermost should + // find the treechilren. + return nullptr; + } else if (content->IsElement() && + !ni->Equals(nsGkAtoms::_template, kNameSpaceID_XUL)) { + nsIContent* result = FindBodyElement(content); + if (result) + return result; + } + } + + return nullptr; +} + +nsTreeBodyFrame* +TreeBoxObject::GetTreeBodyFrame(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 = nullptr; + 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()); + 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 +TreeBoxObject::GetView(nsITreeView * *aView) +{ + if (!mTreeBody) { + if (!GetTreeBodyFrame()) { + // 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; +} + +already_AddRefed<nsITreeView> +TreeBoxObject::GetView() { + nsCOMPtr<nsITreeView> view; + GetView(getter_AddRefs(view)); + return view.forget(); +} + +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 TreeBoxObject::SetView(nsITreeView * aView) +{ + if (!CanTrustView(aView)) + return NS_ERROR_DOM_SECURITY_ERR; + + mView = aView; + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + body->SetView(aView); + + return NS_OK; +} + +void TreeBoxObject::SetView(nsITreeView* aView, ErrorResult& aRv) +{ + aRv = SetView(aView); +} + +bool TreeBoxObject::Focused() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->GetFocused(); + return false; +} + +NS_IMETHODIMP TreeBoxObject::GetFocused(bool* aFocused) +{ + *aFocused = Focused(); + return NS_OK; +} + +NS_IMETHODIMP TreeBoxObject::SetFocused(bool aFocused) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->SetFocused(aFocused); + return NS_OK; +} + +NS_IMETHODIMP TreeBoxObject::GetTreeBody(nsIDOMElement** aElement) +{ + *aElement = nullptr; + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->GetTreeBody(aElement); + return NS_OK; +} + +already_AddRefed<Element> +TreeBoxObject::GetTreeBody() +{ + nsCOMPtr<nsIDOMElement> el; + GetTreeBody(getter_AddRefs(el)); + nsCOMPtr<Element> ret(do_QueryInterface(el)); + return ret.forget(); +} + +already_AddRefed<nsTreeColumns> +TreeBoxObject::GetColumns() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->Columns(); + return nullptr; +} + +NS_IMETHODIMP TreeBoxObject::GetColumns(nsITreeColumns** aColumns) +{ + *aColumns = GetColumns().take(); + return NS_OK; +} + +int32_t TreeBoxObject::RowHeight() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->RowHeight(); + return 0; +} + +int32_t TreeBoxObject::RowWidth() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->RowWidth(); + return 0; +} + +NS_IMETHODIMP TreeBoxObject::GetRowHeight(int32_t* aRowHeight) +{ + *aRowHeight = RowHeight(); + return NS_OK; +} + +NS_IMETHODIMP TreeBoxObject::GetRowWidth(int32_t *aRowWidth) +{ + *aRowWidth = RowWidth(); + return NS_OK; +} + +int32_t TreeBoxObject::GetFirstVisibleRow() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->FirstVisibleRow(); + return 0; +} + +NS_IMETHODIMP TreeBoxObject::GetFirstVisibleRow(int32_t *aFirstVisibleRow) +{ + *aFirstVisibleRow = GetFirstVisibleRow(); + return NS_OK; +} + +int32_t TreeBoxObject::GetLastVisibleRow() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->LastVisibleRow(); + return 0; +} + +NS_IMETHODIMP TreeBoxObject::GetLastVisibleRow(int32_t *aLastVisibleRow) +{ + *aLastVisibleRow = GetLastVisibleRow(); + return NS_OK; +} + +int32_t TreeBoxObject::HorizontalPosition() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->GetHorizontalPosition(); + return 0; +} + +NS_IMETHODIMP TreeBoxObject::GetHorizontalPosition(int32_t *aHorizontalPosition) +{ + *aHorizontalPosition = HorizontalPosition(); + return NS_OK; +} + +int32_t TreeBoxObject::GetPageLength() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->PageLength(); + return 0; +} + +NS_IMETHODIMP TreeBoxObject::GetPageLength(int32_t *aPageLength) +{ + *aPageLength = GetPageLength(); + return NS_OK; +} + +NS_IMETHODIMP TreeBoxObject::GetSelectionRegion(nsIScriptableRegion **aRegion) +{ + *aRegion = nullptr; + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->GetSelectionRegion(aRegion); + return NS_OK; +} + +already_AddRefed<nsIScriptableRegion> +TreeBoxObject::SelectionRegion() +{ + nsCOMPtr<nsIScriptableRegion> region; + GetSelectionRegion(getter_AddRefs(region)); + return region.forget(); +} + +NS_IMETHODIMP +TreeBoxObject::EnsureRowIsVisible(int32_t aRow) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->EnsureRowIsVisible(aRow); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->EnsureCellIsVisible(aRow, aCol); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::ScrollToRow(int32_t aRow) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(true); + if (body) + return body->ScrollToRow(aRow); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::ScrollByLines(int32_t aNumLines) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->ScrollByLines(aNumLines); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::ScrollByPages(int32_t aNumPages) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->ScrollByPages(aNumPages); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::ScrollToCell(int32_t aRow, nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->ScrollToCell(aRow, aCol); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::ScrollToColumn(nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->ScrollToColumn(aCol); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::ScrollToHorizontalPosition(int32_t aHorizontalPosition) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->ScrollToHorizontalPosition(aHorizontalPosition); + return NS_OK; +} + +NS_IMETHODIMP TreeBoxObject::Invalidate() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->Invalidate(); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::InvalidateColumn(nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->InvalidateColumn(aCol); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::InvalidateRow(int32_t aIndex) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->InvalidateRow(aIndex); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::InvalidateCell(int32_t aRow, nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->InvalidateCell(aRow, aCol); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::InvalidateRange(int32_t aStart, int32_t aEnd) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->InvalidateRange(aStart, aEnd); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->InvalidateColumnRange(aStart, aEnd, aCol); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::GetRowAt(int32_t x, int32_t y, int32_t *aRow) +{ + *aRow = 0; + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->GetRowAt(x, y, aRow); + return NS_OK; +} + +int32_t +TreeBoxObject::GetRowAt(int32_t x, int32_t y) +{ + int32_t row; + GetRowAt(x, y, &row); + return row; +} + +NS_IMETHODIMP +TreeBoxObject::GetCellAt(int32_t aX, int32_t aY, int32_t *aRow, + nsITreeColumn** aCol, nsAString& aChildElt) +{ + *aRow = 0; + *aCol = nullptr; + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) { + nsAutoCString element; + nsresult retval = body->GetCellAt(aX, aY, aRow, aCol, element); + CopyUTF8toUTF16(element, aChildElt); + return retval; + } + return NS_OK; +} + +void +TreeBoxObject::GetCellAt(int32_t x, int32_t y, TreeCellInfo& aRetVal, ErrorResult& aRv) +{ + nsCOMPtr<nsITreeColumn> col; + GetCellAt(x, y, &aRetVal.mRow, getter_AddRefs(col), aRetVal.mChildElt); + aRetVal.mCol = col.forget().downcast<nsTreeColumn>(); +} + +void +TreeBoxObject::GetCellAt(JSContext* cx, + int32_t x, int32_t y, + JS::Handle<JSObject*> rowOut, + JS::Handle<JSObject*> colOut, + JS::Handle<JSObject*> childEltOut, + ErrorResult& aRv) +{ + int32_t row; + nsITreeColumn* col; + nsAutoString childElt; + GetCellAt(x, y, &row, &col, childElt); + + JS::Rooted<JS::Value> v(cx); + + if (!ToJSValue(cx, row, &v) || + !JS_SetProperty(cx, rowOut, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } + if (!dom::WrapObject(cx, col, &v) || + !JS_SetProperty(cx, colOut, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } + if (!ToJSValue(cx, childElt, &v) || + !JS_SetProperty(cx, childEltOut, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } +} + +NS_IMETHODIMP +TreeBoxObject::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsAString& aElement, + int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight) +{ + *aX = *aY = *aWidth = *aHeight = 0; + nsTreeBodyFrame* body = GetTreeBodyFrame(); + NS_ConvertUTF16toUTF8 element(aElement); + if (body) + return body->GetCoordsForCellItem(aRow, aCol, element, aX, aY, aWidth, aHeight); + return NS_OK; +} + +already_AddRefed<DOMRect> +TreeBoxObject::GetCoordsForCellItem(int32_t row, nsTreeColumn& col, const nsAString& element, ErrorResult& aRv) +{ + int32_t x, y, w, h; + GetCoordsForCellItem(row, &col, element, &x, &y, &w, &h); + RefPtr<DOMRect> rect = new DOMRect(mContent, x, y, w, h); + return rect.forget(); +} + +void +TreeBoxObject::GetCoordsForCellItem(JSContext* cx, + int32_t row, + nsTreeColumn& col, + const nsAString& element, + JS::Handle<JSObject*> xOut, + JS::Handle<JSObject*> yOut, + JS::Handle<JSObject*> widthOut, + JS::Handle<JSObject*> heightOut, + ErrorResult& aRv) +{ + int32_t x, y, w, h; + GetCoordsForCellItem(row, &col, element, &x, &y, &w, &h); + JS::Rooted<JS::Value> v(cx, JS::Int32Value(x)); + if (!JS_SetProperty(cx, xOut, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } + v.setInt32(y); + if (!JS_SetProperty(cx, yOut, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } + v.setInt32(w); + if (!JS_SetProperty(cx, widthOut, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } + v.setInt32(h); + if (!JS_SetProperty(cx, heightOut, "value", v)) { + aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL); + return; + } +} + +NS_IMETHODIMP +TreeBoxObject::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *aIsCropped) +{ + *aIsCropped = false; + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->IsCellCropped(aRow, aCol, aIsCropped); + return NS_OK; +} + +bool +TreeBoxObject::IsCellCropped(int32_t row, nsITreeColumn* col, ErrorResult& aRv) +{ + bool ret; + aRv = IsCellCropped(row, col, &ret); + return ret; +} + +NS_IMETHODIMP +TreeBoxObject::RowCountChanged(int32_t aIndex, int32_t aDelta) +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->RowCountChanged(aIndex, aDelta); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::BeginUpdateBatch() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->BeginUpdateBatch(); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::EndUpdateBatch() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->EndUpdateBatch(); + return NS_OK; +} + +NS_IMETHODIMP +TreeBoxObject::ClearStyleAndImageCaches() +{ + nsTreeBodyFrame* body = GetTreeBodyFrame(); + if (body) + return body->ClearStyleAndImageCaches(); + return NS_OK; +} + +void +TreeBoxObject::ClearCachedValues() +{ + mTreeBody = nullptr; +} + +JSObject* +TreeBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return TreeBoxObjectBinding::Wrap(aCx, this, aGivenProto); +} + +} // namespace dom +} // namespace mozilla + +// Creation Routine /////////////////////////////////////////////////////////////////////// + +using namespace mozilla::dom; + +nsresult +NS_NewTreeBoxObject(nsIBoxObject** aResult) +{ + NS_ADDREF(*aResult = new TreeBoxObject()); + return NS_OK; +} diff --git a/layout/xul/tree/TreeBoxObject.h b/layout/xul/tree/TreeBoxObject.h new file mode 100644 index 000000000..d997b5a63 --- /dev/null +++ b/layout/xul/tree/TreeBoxObject.h @@ -0,0 +1,128 @@ +/* -*- 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 mozilla_dom_TreeBoxObject_h +#define mozilla_dom_TreeBoxObject_h + +#include "mozilla/dom/BoxObject.h" +#include "nsITreeView.h" +#include "nsITreeBoxObject.h" + +class nsTreeBodyFrame; +class nsTreeColumn; +class nsTreeColumns; + +namespace mozilla { +namespace dom { + +struct TreeCellInfo; +class DOMRect; + +class TreeBoxObject final : public BoxObject, + public nsITreeBoxObject +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TreeBoxObject, BoxObject) + NS_DECL_NSITREEBOXOBJECT + + TreeBoxObject(); + + nsTreeBodyFrame* GetTreeBodyFrame(bool aFlushLayout = false); + nsTreeBodyFrame* GetCachedTreeBodyFrame() { return mTreeBody; } + + //NS_PIBOXOBJECT interfaces + virtual void Clear() override; + virtual void ClearCachedValues() override; + + // WebIDL + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + already_AddRefed<nsTreeColumns> GetColumns(); + + already_AddRefed<nsITreeView> GetView(); + + void SetView(nsITreeView* arg, ErrorResult& aRv); + + bool Focused(); + + already_AddRefed<Element> GetTreeBody(); + + int32_t RowHeight(); + + int32_t RowWidth(); + + int32_t HorizontalPosition(); + + already_AddRefed<nsIScriptableRegion> SelectionRegion(); + + int32_t GetFirstVisibleRow(); + + int32_t GetLastVisibleRow(); + + int32_t GetPageLength(); + + int32_t GetRowAt(int32_t x, int32_t y); + + void GetCellAt(int32_t x, int32_t y, TreeCellInfo& aRetVal, ErrorResult& aRv); + + already_AddRefed<DOMRect> GetCoordsForCellItem(int32_t row, + nsTreeColumn& col, + const nsAString& element, + ErrorResult& aRv); + + bool IsCellCropped(int32_t row, nsITreeColumn* col, ErrorResult& aRv); + + // Deprecated APIs from old IDL + void GetCellAt(JSContext* cx, + int32_t x, int32_t y, + JS::Handle<JSObject*> rowOut, + JS::Handle<JSObject*> colOut, + JS::Handle<JSObject*> childEltOut, + ErrorResult& aRv); + + void GetCoordsForCellItem(JSContext* cx, + int32_t row, + nsTreeColumn& col, + const nsAString& element, + JS::Handle<JSObject*> xOut, + JS::Handle<JSObject*> yOut, + JS::Handle<JSObject*> widthOut, + JS::Handle<JSObject*> heightOut, + ErrorResult& aRv); + + // Same signature (except for nsresult return type) as the XPIDL impls + // void Invalidate(); + // void BeginUpdateBatch(); + // void EndUpdateBatch(); + // void ClearStyleAndImageCaches(); + // void SetFocused(bool arg); + // void EnsureRowIsVisible(int32_t index); + // void EnsureCellIsVisible(int32_t row, nsITreeColumn* col); + // void ScrollToRow(int32_t index); + // void ScrollByLines(int32_t numLines); + // void ScrollByPages(int32_t numPages); + // void ScrollToCell(int32_t row, nsITreeColumn* col); + // void ScrollToColumn(nsITreeColumn* col); + // void ScrollToHorizontalPosition(int32_t horizontalPosition); + // void InvalidateColumn(nsITreeColumn* col); + // void InvalidateRow(int32_t index); + // void InvalidateCell(int32_t row, nsITreeColumn* col); + // void InvalidateRange(int32_t startIndex, int32_t endIndex); + // void InvalidateColumnRange(int32_t startIndex, int32_t endIndex, nsITreeColumn* col); + // void RowCountChanged(int32_t index, int32_t count); + +protected: + nsTreeBodyFrame* mTreeBody; + nsCOMPtr<nsITreeView> mView; + +private: + ~TreeBoxObject(); +}; + +} // namespace dom +} // namespace mozilla + +#endif 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..ce9d29448 --- /dev/null +++ b/layout/xul/tree/crashtests/585815-iframe.xul @@ -0,0 +1,72 @@ +<?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.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..a31c3b63f --- /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/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..02b3a307e --- /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/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..2cf74d1b9 --- /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/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..d462e9550 --- /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 +load 366583-1.xul +load 380217-1.xul +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..ccac5bde9 --- /dev/null +++ b/layout/xul/tree/moz.build @@ -0,0 +1,53 @@ +# -*- Mode: python; 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/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: XUL') + +XPIDL_SOURCES += [ + 'nsITreeBoxObject.idl', + 'nsITreeColumns.idl', + 'nsITreeContentView.idl', + 'nsITreeSelection.idl', + 'nsITreeView.idl', +] + +XPIDL_MODULE = 'layout_xul_tree' + +EXPORTS += [ + 'nsTreeColFrame.h', + 'nsTreeColumns.h', + 'nsTreeUtils.h', +] + +EXPORTS.mozilla.dom += [ + 'TreeBoxObject.h' +] + +UNIFIED_SOURCES += [ + 'nsTreeBodyFrame.cpp', + 'nsTreeColFrame.cpp', + 'nsTreeColumns.cpp', + 'nsTreeContentView.cpp', + 'nsTreeImageListener.cpp', + 'nsTreeSelection.cpp', + 'nsTreeStyleCache.cpp', + 'nsTreeUtils.cpp', + 'TreeBoxObject.cpp', +] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '..', + '../../base', + '../../forms', + '../../generic', + '../../style', + '/dom/base', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/layout/xul/tree/nsITreeBoxObject.idl b/layout/xul/tree/nsITreeBoxObject.idl new file mode 100644 index 000000000..638c8a41c --- /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" + +interface nsIDOMElement; +interface nsITreeView; +interface nsITreeSelection; +interface nsITreeColumn; +interface nsITreeColumns; +interface nsIScriptableRegion; + +[scriptable, uuid(f3da0c5e-51f5-45f0-b2cd-6be3ab6847ae)] +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 AString childElt); + + /** + * Find the coordinates of an element within a specific cell. + */ + void getCoordsForCellItem(in long row, in nsITreeColumn col, in AString 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..a601f3776 --- /dev/null +++ b/layout/xul/tree/nsITreeColumns.idl @@ -0,0 +1,96 @@ +/* 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; + const short TYPE_PASSWORD = 4; + 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..0b5d178aa --- /dev/null +++ b/layout/xul/tree/nsITreeContentView.idl @@ -0,0 +1,22 @@ +/* -*- 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(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..66920f81b --- /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 "nsISupports.idl" + +interface nsITreeBoxObject; +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..deba04a36 --- /dev/null +++ b/layout/xul/tree/nsTreeBodyFrame.cpp @@ -0,0 +1,4945 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TextEditRules.h" + +#include "gfxUtils.h" +#include "nsAlgorithm.h" +#include "nsCOMPtr.h" +#include "nsFontMetrics.h" +#include "nsPresContext.h" +#include "nsNameSpaceManager.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 "nsIDOMCustomEvent.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 "nsVariant.h" +#include "nsWidgetsCID.h" +#include "nsBoxFrame.h" +#include "nsIURL.h" +#include "nsBoxLayoutState.h" +#include "nsTreeContentView.h" +#include "nsTreeUtils.h" +#include "nsThemeConstants.h" +#include "nsITheme.h" +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "imgILoader.h" +#include "mozilla/dom/NodeInfo.h" +#include "nsContentUtils.h" +#include "nsLayoutUtils.h" +#include "nsIScrollableFrame.h" +#include "nsDisplayList.h" +#include "mozilla/dom/TreeBoxObject.h" +#include "nsRenderingContext.h" +#include "nsIScriptableRegion.h" +#include <algorithm> +#include "ScrollbarActivity.h" + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#include "nsIWritablePropertyBag2.h" +#endif +#include "nsBidiUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; +using namespace mozilla::layout; + +// Function that cancels all the image requests in our cache. +void +nsTreeBodyFrame::CancelImageRequests() +{ + for (auto iter = mImageCache.Iter(); !iter.Done(); iter.Next()) { + // If our imgIRequest object was registered with the refresh driver + // then we need to deregister it. + nsTreeImageCacheEntry entry = iter.UserData(); + nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request, + nullptr); + entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED); + } +} + +// +// NS_NewTreeFrame +// +// Creates a new tree frame +// +nsIFrame* +NS_NewTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTreeBodyFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame) + +NS_QUERYFRAME_HEAD(nsTreeBodyFrame) + NS_QUERYFRAME_ENTRY(nsIScrollbarMediator) + NS_QUERYFRAME_ENTRY(nsTreeBodyFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame) + +// Constructor +nsTreeBodyFrame::nsTreeBodyFrame(nsStyleContext* aContext) +:nsLeafBoxFrame(aContext), + mSlots(nullptr), + mImageCache(), + 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), + mCheckingOverflow(false) +{ + mColumns = new nsTreeColumns(this); +} + +// Destructor +nsTreeBodyFrame::~nsTreeBodyFrame() +{ + CancelImageRequests(); + DetachImageListeners(); + delete mSlots; +} + +static void +GetBorderPadding(nsStyleContext* aContext, nsMargin& aMargin) +{ + aMargin.SizeTo(0, 0, 0, 0); + aContext->StylePadding()->GetPadding(aMargin); + 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, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow); + + mIndentation = GetIndentation(); + mRowHeight = GetRowHeight(); + + EnsureBoxObject(); + + if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { + mScrollbarActivity = new ScrollbarActivity( + static_cast<nsIScrollbarMediator*>(this)); + } +} + +nsSize +nsTreeBodyFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) +{ + EnsureView(); + + nsIContent* baseElement = GetBaseElement(); + + nsSize min(0,0); + int32_t desiredRows; + if (MOZ_UNLIKELY(!baseElement)) { + desiredRows = 0; + } + else if (baseElement->IsHTMLElement(nsGkAtoms::select)) { + 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::AddXULMinSize(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; + + nsRenderingContext rc( + PresContext()->PresShell()->CreateReferenceRenderingContext()); + + 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->GetComposedDoc(); + 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<dom::TreeBoxObject*>(realTreeBoxObject.get()) + ->GetCachedTreeBodyFrame(); + 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(u"topRow", 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(u"topRow"); + } + } + } +} + +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::SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, + bool aRemoveOverflowArea) +{ + nscoord horzWidth = CalcHorzWidth(GetScrollParts()); + ManageReflowCallback(aRect, horzWidth); + mHorzWidth = horzWidth; + + nsLeafBoxFrame::SetXULBounds(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::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); +} + +int32_t +nsTreeBodyFrame::RowHeight() const +{ + return nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); +} + +int32_t +nsTreeBodyFrame::RowWidth() +{ + return nsPresContext::AppUnitsToIntCSSPixels(CalcHorzWidth(GetScrollParts())); +} + +int32_t +nsTreeBodyFrame::GetHorizontalPosition() const +{ + return nsPresContext::AppUnitsToIntCSSPixels(mHorzPosition); +} + +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(); + + RefPtr<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); + + region.forget(aRegion); + 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; + + RefPtr<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; + + RefPtr<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; + + RefPtr<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->IsXULHorizontal()) { + if (!aResult->mVScrollbar) { + aResult->mVScrollbar = sf; + } + } else { + if (!aResult->mHScrollbar) { + aResult->mHScrollbar = sf; + } + } + // don't bother searching inside a scrollbar + return; + } + + nsIFrame* child = aCurrFrame->PrincipalChildList().FirstChild(); + 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; + } + } + } + + nsWeakFrame weakFrame(this); + + RefPtr<nsPresContext> presContext = PresContext(); + nsCOMPtr<nsIPresShell> presShell = presContext->GetPresShell(); + nsCOMPtr<nsIContent> content = mContent; + + if (verticalOverflowChanged) { + InternalScrollPortEvent event(true, + mVerticalOverflow ? eScrollPortOverflow : eScrollPortUnderflow, + nullptr); + event.mOrient = InternalScrollPortEvent::eVertical; + EventDispatcher::Dispatch(content, presContext, &event); + } + + if (horizontalOverflowChanged) { + InternalScrollPortEvent event(true, + mHorizontalOverflow ? eScrollPortOverflow : eScrollPortUnderflow, + nullptr); + event.mOrient = InternalScrollPortEvent::eHorizontal; + EventDispatcher::Dispatch(content, presContext, &event); + } + + // The synchronous event dispatch above can trigger reflow notifications. + // Flush those explicitly now, so that we can guard against potential infinite + // recursion. See bug 905909. + if (!weakFrame.IsAlive()) { + return; + } + NS_ASSERTION(!mCheckingOverflow, "mCheckingOverflow should not already be set"); + // Don't use AutoRestore since we want to not touch mCheckingOverflow if we fail + // the weakFrame.IsAlive() check below + mCheckingOverflow = true; + presShell->FlushPendingNotifications(Flush_Layout); + if (!weakFrame.IsAlive()) { + return; + } + mCheckingOverflow = false; +} + +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); + + nsRenderingContext rc( + presContext->PresShell()->CreateReferenceRenderingContext()); + + // 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, + 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); + + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForStyleContext(textContext); + 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; + + AdjustForCellText(cellText, aRow, currCol, rc, *fm, 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, + nsFontMetrics& aFontMetrics, + nsRect& aTextRect) +{ + NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); + + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + nscoord maxWidth = aTextRect.width; + bool widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(aText, + aFontMetrics, + drawTarget, + maxWidth); + + if (aColumn->Overflow()) { + DebugOnly<nsresult> rv; + nsTreeColumn* nextColumn = aColumn->GetNext(); + while (nextColumn && widthIsGreater) { + 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; + widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(aText, + aFontMetrics, + drawTarget, + maxWidth); + + nextColumn = nextColumn->GetNext(); + } + else { + nextColumn = nullptr; + } + } + } + } + + nscoord width; + if (widthIsGreater) { + // See if the width is even smaller than the ellipsis + // If so, clear the text completely. + const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); + aFontMetrics.SetTextRunRTL(false); + nscoord ellipsisWidth = + nsLayoutUtils::AppUnitWidthOfString(kEllipsis, aFontMetrics, drawTarget); + + 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) { + char16_t ch = aText[i]; + // XXX this is horrible and doesn't handle clusters + cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics, + drawTarget); + 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) { + char16_t ch = aText[i]; + cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics, + drawTarget); + 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) { + char16_t ch = aText[leftPos]; + cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics, + drawTarget); + twidth += cwidth; + if (twidth > width) + break; + leftStr.Append(ch); + + ch = aText[rightPos]; + cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics, + drawTarget); + twidth += cwidth; + if (twidth > width) + break; + rightStr.Insert(ch, 0); + --rightPos; + } + aText = leftStr; + aText.Append(kEllipsis); + aText += rightStr; + } + break; + } + } + } + + width = nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this, aFontMetrics, + aRenderingContext); + + 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(); + nsRenderingContext rc( + presContext->PresShell()->CreateReferenceRenderingContext()); + + 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, + 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); + + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForStyleContext(textContext); + AdjustForCellText(cellText, aRowIndex, aColumn, rc, *fm, 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(), + 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); + + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForStyleContext(textContext); + // Get the width of the text itself + nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(cellText, this, *fm, + *aRenderingContext); + 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; + + RefPtr<nsTreeColumn> col = GetColumnImpl(aCol); + if (!col) + return NS_ERROR_INVALID_ARG; + + nsRenderingContext rc( + PresContext()->PresShell()->CreateReferenceRenderingContext()); + + 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->IsHTMLElement(nsGkAtoms::select)) { + // 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, + 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) { + LayoutDeviceIntSize minTwistySizePx; + bool canOverride = true; + theme->GetMinimumWidgetSize(aPresContext, 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); + RefPtr<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; + nsresult rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + 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; + + RefPtr<imgRequestProxy> imageRequest; + if (styleRequest) { + styleRequest->Clone(imgNotificationObserver, getter_AddRefs(imageRequest)); + } else { + nsIDocument* doc = mContent->GetComposedDoc(); + 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... + nsresult rv = nsContentUtils::LoadImage(srcURI, + mContent, + doc, + mContent->NodePrincipal(), + doc->GetDocumentURI(), + doc->GetReferrerPolicy(), + imgNotificationObserver, + nsIRequest::LOAD_NORMAL, + EmptyString(), + 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; +} + +nsresult +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()->GetComposedDoc()->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(WidgetGUIEvent* aEvent) +{ + NS_ASSERTION(aEvent->mClass == eDragEventClass, "wrong event type"); + WidgetDragEvent* dragEvent = aEvent->AsDragEvent(); + nsContentUtils::SetDataTransferInEvent(dragEvent); + + uint32_t action = 0; + if (dragEvent->mDataTransfer) { + dragEvent->mDataTransfer->GetDropEffectInt(&action); + } + return action; +} + +nsresult +nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + if (aEvent->mMessage == eMouseOver || aEvent->mMessage == eMouseMove) { + 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->mMessage == eMouseOut) { + if (mMouseOverRow != -1) { + InvalidateRow(mMouseOverRow); + mMouseOverRow = -1; + } + } else if (aEvent->mMessage == eDragEnter) { + 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->mMessage == eDragOver) { + // 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; + mView->CanDrop(mSlots->mDropRow, mSlots->mDropOrient, + aEvent->AsDragEvent()->mDataTransfer, + &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->mMessage == eDrop) { + // 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->mClass == eDragEventClass, "wrong event type"); + WidgetDragEvent* dragEvent = aEvent->AsDragEvent(); + nsContentUtils::SetDataTransferInEvent(dragEvent); + + mView->Drop(mSlots->mDropRow, mSlots->mDropOrient, + dragEvent->mDataTransfer); + mSlots->mDropRow = -1; + mSlots->mDropOrient = -1; + mSlots->mIsDragging = false; + *aEventStatus = nsEventStatus_eConsumeNoDefault; // already handled the drop + } else if (aEvent->mMessage == eDragExit) { + // 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; +} + +class nsDisplayTreeBody final : public nsDisplayItem { +public: + nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsFrame* aFrame) : + nsDisplayItem(aBuilder, aFrame), + mDisableSubpixelAA(false) { + MOZ_COUNT_CTOR(nsDisplayTreeBody); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayTreeBody() { + MOZ_COUNT_DTOR(nsDisplayTreeBody); + } +#endif + + nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override + { + return new nsDisplayItemGenericImageGeometry(this, aBuilder); + } + + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion *aInvalidRegion) override + { + auto geometry = + static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + + nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); + } + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override + { + DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(), + mDisableSubpixelAA); + + DrawResult result = static_cast<nsTreeBodyFrame*>(mFrame) + ->PaintTreeBody(*aCtx, mVisibleRect, ToReferenceFrame()); + + nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); + } + + NS_DISPLAY_DECL_NAME("XULTreeBody", TYPE_XUL_TREE_BODY) + + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override + { + bool snap; + return GetBounds(aBuilder, &snap); + } + virtual void DisableComponentAlpha() override { + mDisableSubpixelAA = true; + } + + bool mDisableSubpixelAA; +}; + +// 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 ()->GetComposedDoc()->GetWindow()) + return; + +#ifdef XP_MACOSX + nsIContent* baseElement = GetBaseElement(); + nsIFrame* treeFrame = + baseElement ? baseElement->GetPrimaryFrame() : nullptr; + nsCOMPtr<nsITreeSelection> selection; + mView->GetSelection(getter_AddRefs(selection)); + nsITheme* theme = PresContext()->GetTheme(); + // On Mac, we support native theming of selected rows. On 10.10 and higher, + // this means applying vibrancy which require us to register the theme + // geometrics for the row. In order to make the vibrancy effect to work + // properly, we also need the tree to be themed as a source list. + if (selection && treeFrame && theme && + treeFrame->StyleDisplay()->mAppearance == NS_THEME_MAC_SOURCE_LIST) { + // Loop through our onscreen rows. If the row is selected and a + // -moz-appearance is provided, RegisterThemeGeometry might be necessary. + const auto end = std::min(mRowCount, LastVisibleRow() + 1); + for (auto i = FirstVisibleRow(); i < end; i++) { + bool isSelected; + selection->IsSelected(i, &isSelected); + if (isSelected) { + PrefillPropertyArray(i, nullptr); + nsAutoString properties; + mView->GetRowProperties(i, properties); + nsTreeUtils::TokenizeProperties(properties, mScratchArray); + nsStyleContext* rowContext = + GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); + auto appearance = rowContext->StyleDisplay()->mAppearance; + if (appearance) { + if (theme->ThemeSupportsWidget(PresContext(), this, appearance)) { + nsITheme::ThemeGeometryType type = + theme->ThemeGeometryTypeForWidget(this, appearance); + if (type != nsITheme::eThemeGeometryTypeUnknown) { + nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight * + (i - FirstVisibleRow()), mInnerBox.width, + mRowHeight); + aBuilder->RegisterThemeGeometry(type, + LayoutDeviceIntRect::FromUnknownRect( + (rowRect + aBuilder->ToReferenceFrame(this)).ToNearestPixels( + PresContext()->AppUnitsPerDevPixel()))); + } + } + } + } + } + } +#endif + + aLists.Content()->AppendNewToTop(new (aBuilder) + nsDisplayTreeBody(aBuilder, this)); +} + +DrawResult +nsTreeBodyFrame::PaintTreeBody(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt) +{ + // Update our available height and our page count. + CalcInnerBox(); + + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + gfxContext* gfx = aRenderingContext.ThebesContext(); + + gfx->Save(); + gfx->Clip(NSRectToSnappedRect(mInnerBox + aPt, + PresContext()->AppUnitsPerDevPixel(), + *drawTarget)); + 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_WARNING_ASSERTION(mRowCount == rowCount, "row count changed unexpectedly"); +#endif + + DrawResult result = DrawResult::SUCCESS; + + // 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)) { + result &= + 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)) { + result &= + 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)) { + result &= + PaintDropFeedback(feedbackRect, PresContext(), aRenderingContext, + aDirtyRect, aPt); + } + } + gfx->Restore(); + + return result; +} + + + +DrawResult +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); + + return PaintBackgroundLayer(colContext, aPresContext, aRenderingContext, + colRect, aDirtyRect); +} + +DrawResult +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 DrawResult::SUCCESS; + } + + 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); + + DrawResult result = DrawResult::SUCCESS; + + // Paint our borders and background for our row rect. + nsITheme* theme = nullptr; + auto appearance = rowContext->StyleDisplay()->mAppearance; + if (appearance) { + theme = aPresContext->GetTheme(); + } + gfxContext* ctx = aRenderingContext.ThebesContext(); + // Save the current font smoothing background color in case we change it. + Color originalColor(ctx->GetFontSmoothingBackgroundColor()); + if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, appearance)) { + nscolor color; + if (theme->WidgetProvidesFontSmoothingBackgroundColor(this, appearance, + &color)) { + // Set the font smoothing background color provided by the widget. + ctx->SetFontSmoothingBackgroundColor(ToDeviceColor(color)); + } + nsRect dirty; + dirty.IntersectRect(rowRect, aDirtyRect); + theme->DrawWidgetBackground(&aRenderingContext, this, appearance, rowRect, + dirty); + } else { + result &= 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 result; + } + + 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)) { + result &= 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; + result &= 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; + } + result &= 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)) + result &= PaintCell(aRowIndex, currCol, cellRect, aPresContext, + aRenderingContext, aDirtyRect, dummy, aPt); + } + } + } + // If we've changed the font smoothing background color for this row, restore + // the color to the original one. + if (originalColor != ctx->GetFontSmoothingBackgroundColor()) { + ctx->SetFontSmoothingBackgroundColor(originalColor); + } + + return result; +} + +DrawResult +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; + } + + DrawResult result = DrawResult::SUCCESS; + + // 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; + + result &= PaintBackgroundLayer(separatorContext, aPresContext, + aRenderingContext, separatorRect, + aDirtyRect); + } + + return result; +} + +DrawResult +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. + DrawResult result = 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, + twistyContext); + + nsMargin twistyMargin; + twistyContext->StyleMargin()->GetMargin(twistyMargin); + twistyRect.Inflate(twistyMargin); + + aRenderingContext.ThebesContext()->Save(); + + const nsStyleBorder* borderStyle = lineContext->StyleBorder(); + // Resolve currentcolor values against the treeline context + nscolor color = lineContext->StyleColor()-> + CalcComplexColor(borderStyle->mBorderLeftColor); + ColorPattern colorPatt(ToDeviceColor(color)); + + uint8_t style = borderStyle->GetBorderStyle(NS_SIDE_LEFT); + StrokeOptions strokeOptions; + nsLayoutUtils::InitDashPattern(strokeOptions, style); + + nscoord srcX = currX + twistyRect.width - mIndentation / 2; + nscoord lineY = (aRowIndex - mTopRowIndex) * mRowHeight + aPt.y; + + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + nsPresContext* pc = PresContext(); + + // 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); + } + Point p1(pc->AppUnitsToGfxUnits(srcX), + pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2)); + Point p2(pc->AppUnitsToGfxUnits(destX), + pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2)); + SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget, + strokeOptions.mLineWidth); + drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions); + } + + 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 || i == level) { + Point p1(pc->AppUnitsToGfxUnits(srcX), + pc->AppUnitsToGfxUnits(lineY)); + Point p2; + p2.x = pc->AppUnitsToGfxUnits(srcX); + + if (hasNextSibling) + p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight); + else if (i == level) + p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2); + + SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget, + strokeOptions.mLineWidth); + drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions); + } + } + + int32_t parent; + if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) || parent < 0) + break; + currentParent = parent; + srcX -= mIndentation; + } + + aRenderingContext.ThebesContext()->Restore(); + } + + // Always leave space for the twisty. + nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height); + result &= 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)) { + result &= 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)) { + switch (aColumn->GetType()) { + case nsITreeColumn::TYPE_TEXT: + case nsITreeColumn::TYPE_PASSWORD: + result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext, + aRenderingContext, aDirtyRect, currX); + break; + case nsITreeColumn::TYPE_CHECKBOX: + result &= 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: + result &= PaintProgressMeter(aRowIndex, aColumn, elementRect, + aPresContext, aRenderingContext, + aDirtyRect); + break; + case nsITreeView::PROGRESS_NONE: + default: + result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext, + aRenderingContext, aDirtyRect, currX); + break; + } + break; + } + } + } + + aCurrX = currX; + + return result; +} + +DrawResult +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, 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; + + DrawResult result = DrawResult::SUCCESS; + + if (shouldPaint) { + // Paint our borders and background for our image rect. + result &= 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. + result &= + nsLayoutUtils::DrawSingleUnscaledImage( + *aRenderingContext.ThebesContext(), aPresContext, image, + SamplingFilter::POINT, pt, &aDirtyRect, + imgIContainer::FLAG_NONE, &imageSize); + } + } + } + + return result; +} + +DrawResult +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->StyleEffects()->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 DrawResult::SUCCESS; + } + + // 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; + } + } + + DrawResult result = DrawResult::SUCCESS; + + if (image) { + if (isRTL) + imageRect.x = rightEdge - imageRect.width; + // Paint our borders and background for our image rect + result &= 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); + + // Compute the area where our whole image would be mapped, to get the + // desired subregion onto our actual destRect: + nsRect wholeImageDest; + CSSIntSize rawImageCSSIntSize; + if (NS_SUCCEEDED(image->GetWidth(&rawImageCSSIntSize.width)) && + NS_SUCCEEDED(image->GetHeight(&rawImageCSSIntSize.height))) { + // 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. + nsSize rawImageSize(CSSPixel::ToAppUnits(rawImageCSSIntSize)); + wholeImageDest = + nsLayoutUtils::GetWholeImageDestination(rawImageSize, sourceRect, + nsRect(destRect.TopLeft(), + imageDestSize)); + } else { + // GetWidth/GetHeight failed, so we can't easily map a subregion of the + // source image onto the destination area. + // * If this happens with a RasterImage, it probably means the image is + // in an error state, and we shouldn't draw anything. Hence, we leave + // wholeImageDest as an empty rect (its initial state). + // * If this happens with a VectorImage, it probably means the image has + // no explicit width or height attribute -- but we can still proceed and + // just treat the destination area as our whole SVG image area. Hence, we + // set wholeImageDest to the full destRect. + if (image->GetType() == imgIContainer::TYPE_VECTOR) { + wholeImageDest = destRect; + } + } + + gfxContext* ctx = aRenderingContext.ThebesContext(); + if (opacity != 1.0f) { + ctx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity); + } + + result &= + nsLayoutUtils::DrawImage(*ctx, aPresContext, image, + nsLayoutUtils::GetSamplingFilterForFrame(this), + wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect, + imgIContainer::FLAG_NONE); + + if (opacity != 1.0f) { + ctx->PopGroupAndBlend(); + } + } + + // Update the aRemainingWidth and aCurrX values. + imageRect.Inflate(imageMargin); + aRemainingWidth -= imageRect.width; + if (!isRTL) { + aCurrX += imageRect.width; + } + + return result; +} + +// Disable PGO for PaintText because MSVC 2015 seems to have decided +// that it can null out the alreadyAddRefed<nsFontMetrics> used to +// initialize fontMet after storing fontMet on the stack in the same +// space, overwriting fontMet's stack storage with null. +#ifdef _MSC_VER +# pragma optimize("g", off) +#endif +DrawResult +nsTreeBodyFrame::PaintText(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aTextRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aCurrX) +{ + 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); + + if (aColumn->Type() == nsITreeColumn::TYPE_PASSWORD) { + TextEditRules::FillBufWithPWChars(&text, text.Length()); + } + + // We're going to paint this text so we need to ensure bidi is enabled if + // necessary + CheckTextForBidi(text); + + DrawResult result = DrawResult::SUCCESS; + + if (text.Length() == 0) { + // Don't paint an empty string. XXX What about background/borders? Still paint? + return result; + } + + int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + // 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->StyleEffects()->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. + RefPtr<nsFontMetrics> fontMet = + nsLayoutUtils::GetFontMetricsForStyleContext(textContext); + + 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. + AdjustForCellText(text, aRowIndex, aColumn, aRenderingContext, *fontMet, textRect); + textRect.Inflate(bp); + + // Subtract out the remaining width. + if (!isRTL) + aCurrX += textRect.width + textMargin.LeftRight(); + + result &= PaintBackgroundLayer(textContext, aPresContext, aRenderingContext, + textRect, aDirtyRect); + + // Time to paint our text. + textRect.Deflate(bp); + + // Set our color. + ColorPattern color(ToDeviceColor(textContext->StyleColor()->mColor)); + + // Draw decorations. + uint8_t decorations = textContext->StyleTextReset()->mTextDecorationLine; + + nscoord offset; + nscoord size; + if (decorations & (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE | + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE)) { + fontMet->GetUnderline(offset, size); + if (decorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) { + nsRect r(textRect.x, textRect.y, textRect.width, size); + Rect devPxRect = + NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(devPxRect, color); + } + if (decorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) { + nsRect r(textRect.x, textRect.y + baseline - offset, + textRect.width, size); + Rect devPxRect = + NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(devPxRect, color); + } + } + if (decorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) { + fontMet->GetStrikeout(offset, size); + nsRect r(textRect.x, textRect.y + baseline - offset, textRect.width, size); + Rect devPxRect = + NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(devPxRect, color); + } + nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); + + gfxContext* ctx = aRenderingContext.ThebesContext(); + if (opacity != 1.0f) { + ctx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity); + } + + ctx->SetColor(Color::FromABGR(textContext->StyleColor()->mColor)); + nsLayoutUtils::DrawString(this, *fontMet, &aRenderingContext, text.get(), + text.Length(), + textRect.TopLeft() + nsPoint(0, baseline), + cellContext); + + if (opacity != 1.0f) { + ctx->PopGroupAndBlend(); + } + + return result; +} +#ifdef _MSC_VER +# pragma optimize("", on) +#endif + +DrawResult +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. + DrawResult result = 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. + result &= + nsLayoutUtils::DrawSingleUnscaledImage(*aRenderingContext.ThebesContext(), + aPresContext, + image, SamplingFilter::POINT, pt, &aDirtyRect, + imgIContainer::FLAG_NONE, &imageSize); + } + + return result; +} + +DrawResult +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. + DrawResult result = 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); + + // 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()); + result &= + nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(), + aPresContext, image, + nsLayoutUtils::GetSamplingFilterForFrame(this), + nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), + aDirtyRect, imgIContainer::FLAG_NONE); + } else { + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); + Rect rect = + NSRectToSnappedRect(meterRect, appUnitsPerDevPixel, *drawTarget); + ColorPattern color(ToDeviceColor(meterContext->StyleColor()->mColor)); + drawTarget->FillRect(rect, color); + } + } + 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()); + result &= + nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(), + aPresContext, image, + nsLayoutUtils::GetSamplingFilterForFrame(this), + nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), + aDirtyRect, imgIContainer::FLAG_NONE); + } + } + + return result; +} + + +DrawResult +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); + + DrawResult result = DrawResult::SUCCESS; + + // 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, 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. + result &= PaintBackgroundLayer(feedbackContext, aPresContext, + aRenderingContext, feedbackRect, + aDirtyRect); + } + + return result; +} + +DrawResult +nsTreeBodyFrame::PaintBackgroundLayer(nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aRect, + const nsRect& aDirtyRect) +{ + const nsStyleBorder* myBorder = aStyleContext->StyleBorder(); + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForAllLayers(*aPresContext, aRenderingContext, + aDirtyRect, aRect, this, + nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES); + DrawResult result = + nsCSSRendering::PaintBackgroundWithSC(params, aStyleContext, *myBorder); + + result &= + nsCSSRendering::PaintBorderWithStyleBorder(aPresContext, aRenderingContext, + this, aDirtyRect, aRect, + *myBorder, mStyleContext, + PaintBorderFlags::SYNC_DECODE_IMAGES); + + nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this, + aDirtyRect, aRect, aStyleContext); + + return result; +} + +// 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) +{ + RefPtr<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) +{ + RefPtr<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(); + ScrollToRowInternal(parts, aRow); + UpdateScrollbars(parts); + return NS_OK; +} + +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; + ScrollToRow(newIndex); + return NS_OK; +} + +nsresult +nsTreeBodyFrame::ScrollByPages(int32_t aNumPages) +{ + if (!mView) { + return NS_OK; + } + int32_t newIndex = mTopRowIndex + aNumPages * mPageLength; + ScrollToRow(newIndex); + return NS_OK; +} + +nsresult +nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts, int32_t aRow) +{ + if (!mView) { + return NS_OK; + } + + // Note that we may be "over scrolled" at this point; that is the + // current mTopRowIndex may be larger than mRowCount - mPageLength. + // This can happen when items are removed for example. (bug 1085050) + + int32_t maxTopRowIndex = std::max(0, mRowCount - mPageLength); + aRow = mozilla::clamped(aRow, 0, maxTopRowIndex); + if (aRow == mTopRowIndex) { + return NS_OK; + } + mTopRowIndex = aRow; + 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; +} + +void +nsTreeBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap) +{ + // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored + MOZ_ASSERT(aScrollbar != nullptr); + ScrollByPages(aDirection); +} + +void +nsTreeBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap) +{ + // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored + MOZ_ASSERT(aScrollbar != nullptr); + int32_t newIndex = aDirection < 0 ? 0 : mTopRowIndex; + ScrollToRow(newIndex); +} + +void +nsTreeBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap) +{ + // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored + MOZ_ASSERT(aScrollbar != nullptr); + ScrollByLines(aDirection); +} + +void +nsTreeBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar) +{ + ScrollParts parts = GetScrollParts(); + int32_t increment = aScrollbar->GetIncrement(); + int32_t direction = 0; + if (increment < 0) { + direction = -1; + } else if (increment > 0) { + direction = 1; + } + bool isHorizontal = aScrollbar->IsXULHorizontal(); + + nsWeakFrame weakFrame(this); + if (isHorizontal) { + int32_t curpos = aScrollbar->MoveToNewPosition(); + if (weakFrame.IsAlive()) { + ScrollHorzInternal(parts, curpos); + } + } else { + ScrollToRowInternal(parts, mTopRowIndex+direction); + } + + if (weakFrame.IsAlive() && mScrollbarActivity) { + mScrollbarActivity->ActivityOccurred(); + } + if (weakFrame.IsAlive()) { + UpdateScrollbars(parts); + } +} + +void +nsTreeBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar, + nscoord aOldPos, + nscoord aNewPos) +{ + ScrollParts parts = GetScrollParts(); + + if (aOldPos == aNewPos) + return; + + nsWeakFrame weakFrame(this); + + // Vertical Scrollbar + if (parts.mVScrollbar == aScrollbar) { + nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); + nscoord newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos); + nscoord newrow = newIndex/rh; + ScrollInternal(parts, newrow); + // Horizontal Scrollbar + } else if (parts.mHScrollbar == aScrollbar) { + int32_t newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos); + ScrollHorzInternal(parts, newIndex); + } + if (weakFrame.IsAlive()) { + UpdateScrollbars(parts); + } +} + +// 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) { + dom::NodeInfo* ni = content->NodeInfo(); + + if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL) || + (ni->Equals(nsGkAtoms::select) && + content->IsHTMLElement())) + return content; + } + + parent = parent->GetParent(); + } + + return nullptr; +} + +nsresult +nsTreeBodyFrame::ClearStyleAndImageCaches() +{ + mStyleCache.Clear(); + CancelImageRequests(); + 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(WidgetGUIEvent* 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(); + WidgetGUIEvent event(true, eScroll, nullptr); + // scroll events fired at elements don't bubble + event.mFlags.mBubbles = false; + EventDispatcher::Dispatch(GetContent(), PresContext(), &event); +} + +void +nsTreeBodyFrame::PostScrollEvent() +{ + if (mScrollEvent.IsPending()) + return; + + RefPtr<ScrollEvent> ev = new ScrollEvent(this); + if (NS_FAILED(NS_DispatchToCurrentThread(ev))) { + NS_WARNING("failed to dispatch ScrollEvent"); + } else { + mScrollEvent = ev; + } +} + +void +nsTreeBodyFrame::ScrollbarActivityStarted() const +{ + if (mScrollbarActivity) { + mScrollbarActivity->ActivityStarted(); + } +} + +void +nsTreeBodyFrame::ScrollbarActivityStopped() const +{ + if (mScrollbarActivity) { + mScrollbarActivity->ActivityStopped(); + } +} + +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("customevent"), + getter_AddRefs(event)); + + nsCOMPtr<nsIDOMCustomEvent> treeEvent(do_QueryInterface(event)); + if (!treeEvent) + return; + + nsCOMPtr<nsIWritablePropertyBag2> propBag( + do_CreateInstance("@mozilla.org/hash-property-bag;1")); + if (!propBag) + return; + + // Set 'index' data - the row index rows are changed from. + propBag->SetPropertyAsInt32(NS_LITERAL_STRING("index"), aIndex); + + // Set 'count' data - the number of changed rows. + propBag->SetPropertyAsInt32(NS_LITERAL_STRING("count"), aCount); + + RefPtr<nsVariant> detailVariant(new nsVariant()); + + detailVariant->SetAsISupports(propBag); + treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeRowCountChanged"), + true, false, detailVariant); + + event->SetTrusted(true); + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(content, event); + asyncDispatcher->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("customevent"), + getter_AddRefs(event)); + + nsCOMPtr<nsIDOMCustomEvent> treeEvent(do_QueryInterface(event)); + if (!treeEvent) + return; + + nsCOMPtr<nsIWritablePropertyBag2> propBag( + do_CreateInstance("@mozilla.org/hash-property-bag;1")); + if (!propBag) + return; + + if (aStartRowIdx != -1 && aEndRowIdx != -1) { + // Set 'startrow' data - the start index of invalidated rows. + propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startrow"), + aStartRowIdx); + + // Set 'endrow' data - the end index of invalidated rows. + propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endrow"), + aEndRowIdx); + } + + if (aStartCol && aEndCol) { + // Set 'startcolumn' data - the start index of invalidated rows. + int32_t startColIdx = 0; + nsresult rv = aStartCol->GetIndex(&startColIdx); + if (NS_FAILED(rv)) + return; + + propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"), + startColIdx); + + // Set 'endcolumn' data - the start index of invalidated rows. + int32_t endColIdx = 0; + rv = aEndCol->GetIndex(&endColIdx); + if (NS_FAILED(rv)) + return; + + propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"), + endColIdx); + } + + RefPtr<nsVariant> detailVariant(new nsVariant()); + + detailVariant->SetAsISupports(propBag); + treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeInvalidated"), + true, false, detailVariant); + + event->SetTrusted(true); + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(content, event); + asyncDispatcher->PostDOMEvent(); +} +#endif + +class nsOverflowChecker : public Runnable +{ +public: + explicit nsOverflowChecker(nsTreeBodyFrame* aFrame) : mFrame(aFrame) {} + NS_IMETHOD Run() override + { + 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); + + // Overflow checking dispatches synchronous events, which can cause infinite + // recursion during reflow. Do the first overflow check synchronously, but + // force any nested checks to round-trip through the event loop. See bug + // 905909. + RefPtr<nsOverflowChecker> checker = new nsOverflowChecker(this); + if (!mCheckingOverflow) { + nsContentUtils::AddScriptRunner(checker); + } else { + NS_DispatchToCurrentThread(checker); + } + 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..9620c8ccb --- /dev/null +++ b/layout/xul/tree/nsTreeBodyFrame.h @@ -0,0 +1,651 @@ +/* -*- 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 "nsDataHashtable.h" +#include "imgIRequest.h" +#include "imgINotificationObserver.h" +#include "nsScrollbarFrame.h" +#include "nsThreadUtils.h" +#include "mozilla/LookAndFeel.h" + +class nsFontMetrics; +class nsOverflowChecker; +class nsTreeImageListener; + +namespace mozilla { +namespace layout { +class ScrollbarActivity; +} // namespace layout +} // namespace mozilla + +// 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 final + : public nsLeafBoxFrame + , public nsICSSPseudoComparator + , public nsIScrollbarMediator + , public nsIReflowCallback +{ + typedef mozilla::layout::ScrollbarActivity ScrollbarActivity; + typedef mozilla::image::DrawResult DrawResult; + +public: + explicit nsTreeBodyFrame(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 + { + RefPtr<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); + bool GetFocused() const { return mFocused; } + nsresult SetFocused(bool aFocused); + nsresult GetTreeBody(nsIDOMElement **aElement); + int32_t RowHeight() const; + int32_t RowWidth(); + int32_t GetHorizontalPosition() const; + 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 CancelImageRequests(); + + void ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth); + + virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override; + virtual void SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, + bool aRemoveOverflowArea = false) override; + + // nsIReflowCallback + virtual bool ReflowFinished() override; + virtual void ReflowCallbackCanceled() override; + + // nsICSSPseudoComparator + virtual bool PseudoMatches(nsCSSSelector* aSelector) override; + + // nsIScrollbarMediator + virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap + = nsIScrollbarMediator::DISABLE_SNAP) override; + virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap + = nsIScrollbarMediator::DISABLE_SNAP) override; + virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection, + nsIScrollbarMediator::ScrollSnapMode aSnap + = nsIScrollbarMediator::DISABLE_SNAP) override; + virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) override; + virtual void ThumbMoved(nsScrollbarFrame* aScrollbar, + nscoord aOldPos, + nscoord aNewPos) override; + virtual void ScrollbarReleased(nsScrollbarFrame* aScrollbar) override {} + virtual void VisibilityChanged(bool aVisible) override { Invalidate(); } + virtual nsIFrame* GetScrollbarBox(bool aVertical) override { + ScrollParts parts = GetScrollParts(); + return aVertical ? parts.mVScrollbar : parts.mHScrollbar; + } + virtual void ScrollbarActivityStarted() const override; + virtual void ScrollbarActivityStopped() const override; + virtual bool IsScrollbarOnRight() const override { + return (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR); + } + virtual bool ShouldSuppressScrollbarRepaints() const override { + return false; + } + + // Overridden from nsIFrame to cache our pres context. + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual nsresult GetCursor(const nsPoint& aPoint, + nsIFrame::Cursor& aCursor) override; + + virtual nsresult HandleEvent(nsPresContext* aPresContext, + mozilla::WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) 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; + }; + + DrawResult 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. + DrawResult PaintColumn(nsTreeColumn* aColumn, + const nsRect& aColumnRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + // This method paints a single row in the tree. + DrawResult 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. + DrawResult 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. + DrawResult 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. + DrawResult 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. + DrawResult 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. + DrawResult PaintText(int32_t aRowIndex, + nsTreeColumn* aColumn, + const nsRect& aTextRect, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nscoord& aCurrX); + + // This method paints the checkbox inside a particular cell of the tree. + DrawResult 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. + DrawResult 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. + DrawResult 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. + DrawResult 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, + nsFontMetrics& aFontMetrics, + 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, + 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(mozilla::WidgetGUIEvent* 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 mozilla::Runnable { + public: + NS_DECL_NSIRUNNABLE + explicit 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 + * nsIDOMCustomEvent 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 + * nsIDOMCustomEvent 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; + + RefPtr<ScrollbarActivity> mScrollbarActivity; + + // The cached box object parent. + nsCOMPtr<nsITreeBoxObject> mTreeBoxObject; + + // Cached column information. + RefPtr<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; + + // Set while we flush layout to take account of effects of + // overflow/underflow event handlers + bool mCheckingOverflow; + + // 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/nsTreeColFrame.cpp b/layout/xul/tree/nsTreeColFrame.cpp new file mode 100644 index 000000000..649c0b0b4 --- /dev/null +++ b/layout/xul/tree/nsTreeColFrame.cpp @@ -0,0 +1,201 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsIBoxObject.h" +#include "mozilla/dom/TreeBoxObject.h" +#include "nsIDOMElement.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(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTreeColFrame) + +// Destructor +nsTreeColFrame::~nsTreeColFrame() +{ +} + +void +nsTreeColFrame::Init(nsIContent* aContent, + nsContainerFrame* 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) override; + 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)); +} + +nsresult +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::SetXULBounds(nsBoxLayoutState& aBoxLayoutState, + const nsRect& aRect, + bool aRemoveOverflowArea) +{ + nscoord oldWidth = mRect.width; + + nsBoxFrame::SetXULBounds(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<mozilla::dom::TreeBoxObject*> + (treeBoxObject)->GetCachedTreeBodyFrame(); + 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..8fc3219d5 --- /dev/null +++ b/layout/xul/tree/nsTreeColFrame.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/. */ + +#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 + + explicit nsTreeColFrame(nsStyleContext* aContext): + nsBoxFrame(aContext) {} + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + virtual void SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, + bool aRemoveOverflowArea = false) 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..c6ee19342 --- /dev/null +++ b/layout/xul/tree/nsTreeColumns.cpp @@ -0,0 +1,765 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsIDOMElement.h" +#include "nsIBoxObject.h" +#include "nsTreeColumns.h" +#include "nsTreeUtils.h" +#include "nsStyleContext.h" +#include "nsDOMClassInfoID.h" +#include "nsContentUtils.h" +#include "nsTreeBodyFrame.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TreeBoxObject.h" +#include "mozilla/dom/TreeColumnBinding.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_CLASS(nsTreeColumn) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTreeColumn) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + 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_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsTreeColumn) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumn) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumn) + +// QueryInterface implementation for nsTreeColumn +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumn) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsITreeColumn) + NS_INTERFACE_MAP_ENTRY(nsISupports) + 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 char16_t** 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 = NS_Atomize(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; + // START or END alignment sometimes means RIGHT + if ((mTextAlignment == NS_STYLE_TEXT_ALIGN_START && + 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_START || + 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, &nsGkAtoms::password, + nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, + typestrings, eCaseMatters)) { + case 0: mType = nsITreeColumn::TYPE_CHECKBOX; break; + case 1: mType = nsITreeColumn::TYPE_PROGRESSMETER; break; + case 2: mType = nsITreeColumn::TYPE_PASSWORD; 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; +} + +nsIContent* +nsTreeColumn::GetParentObject() const +{ + return mContent; +} + +/* virtual */ JSObject* +nsTreeColumn::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return dom::TreeColumnBinding::Wrap(aCx, this, aGivenProto); +} + +mozilla::dom::Element* +nsTreeColumn::GetElement(mozilla::ErrorResult& aRv) +{ + nsCOMPtr<nsIDOMElement> element; + aRv = GetElement(getter_AddRefs(element)); + if (aRv.Failed()) { + return nullptr; + } + nsCOMPtr<nsINode> node = do_QueryInterface(element); + return node->AsElement(); +} + +int32_t +nsTreeColumn::GetX(mozilla::ErrorResult& aRv) +{ + int32_t x; + aRv = GetX(&x); + return x; +} + +int32_t +nsTreeColumn::GetWidth(mozilla::ErrorResult& aRv) +{ + int32_t width; + aRv = GetWidth(&width); + return width; +} + +void +nsTreeColumn::Invalidate(mozilla::ErrorResult& aRv) +{ + aRv = Invalidate(); +} + +nsTreeColumns::nsTreeColumns(nsTreeBodyFrame* aTree) + : mTree(aTree) +{ +} + +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*> aGivenProto) +{ + return dom::TreeColumnsBinding::Wrap(aCx, this, aGivenProto); +} + +dom::TreeBoxObject* +nsTreeColumns::GetTree() const +{ + return mTree ? static_cast<mozilla::dom::TreeBoxObject*>(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); + } + mFirstColumn = nullptr; + 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->PrincipalChildList().FirstChild(); + 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 { + 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..0e27c6a03 --- /dev/null +++ b/layout/xul/tree/nsTreeColumns.h @@ -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/. */ + +#ifndef nsTreeColumns_h__ +#define nsTreeColumns_h__ + +#include "nsITreeColumns.h" +#include "nsITreeBoxObject.h" +#include "mozilla/Attributes.h" +#include "nsCoord.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "nsString.h" + +class nsTreeBodyFrame; +class nsTreeColumns; +class nsIFrame; +class nsIContent; +struct nsRect; + +namespace mozilla { +class ErrorResult; +namespace dom { +class Element; +class TreeBoxObject; +} // 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 final : public nsITreeColumn + , public nsWrapperCache +{ +public: + nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent); + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TREECOLUMN_IMPL_CID) + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsTreeColumn) + NS_DECL_NSITREECOLUMN + + // WebIDL + nsIContent* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + mozilla::dom::Element* GetElement(mozilla::ErrorResult& aRv); + + nsTreeColumns* GetColumns() const { return mColumns; } + + int32_t GetX(mozilla::ErrorResult& aRv); + int32_t GetWidth(mozilla::ErrorResult& aRv); + + // GetId is fine + int32_t Index() const { return mIndex; } + + bool Primary() const { return mIsPrimary; } + bool Cycler() const { return mIsCycler; } + bool Editable() const { return mIsEditable; } + bool Selectable() const { return mIsSelectable; } + int16_t Type() const { return mType; } + + nsTreeColumn* GetNext() const { return mNext; } + nsTreeColumn* GetPrevious() const { return mPrevious; } + + void Invalidate(mozilla::ErrorResult& aRv); + + friend class nsTreeBodyFrame; + friend class nsTreeColumns; + +protected: + ~nsTreeColumn(); + 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; } + + 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; + + RefPtr<nsTreeColumn> mNext; + nsTreeColumn* mPrevious; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsTreeColumn, NS_TREECOLUMN_IMPL_CID) + +class nsTreeColumns final : public nsITreeColumns + , public nsWrapperCache +{ +private: + ~nsTreeColumns(); + +public: + explicit nsTreeColumns(nsTreeBodyFrame* aTree); + + 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*> aGivenProto) override; + + // WebIDL + mozilla::dom::TreeBoxObject* 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. + */ + RefPtr<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..66cc0072f --- /dev/null +++ b/layout/xul/tree/nsTreeContentView.cpp @@ -0,0 +1,1372 @@ +/* -*- 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 "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsIBoxObject.h" +#include "nsTreeUtils.h" +#include "nsTreeContentView.h" +#include "ChildIterator.h" +#include "nsDOMClassInfoID.h" +#include "nsError.h" +#include "nsIXULSortService.h" +#include "nsContentUtils.h" +#include "nsTreeBodyFrame.h" +#include "mozilla/dom/Element.h" +#include "nsServiceManagerUtils.h" +#include "nsIDocument.h" + +using namespace mozilla; + +#define NS_ENSURE_NATIVE_COLUMN(_col) \ + RefPtr<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(nsTreeContentView, + mBoxObject, + mSelection, + mRoot, + mBody) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView) +NS_IMPL_CYCLE_COLLECTING_RELEASE(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::LegacyIsCallerChromeOrNativeCode()) + 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].get(); + 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].get(); + 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].get(); + while (row->mParentIndex != parentIndex) { + lastIndex = row->mParentIndex; + row = mRows[lastIndex].get(); + } + + *_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].get(); + while (row->mParentIndex >= 0) { + level++; + row = mRows[row->mParentIndex].get(); + } + *_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].get(); + + 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].get(); + + 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].get(); + + 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].get(); + + // 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; + + if (row->mContent->IsXULElement(nsGkAtoms::treeitem)) { + 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->GetComposedDoc(); + 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].get(); + + 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.Append(' '); + 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].get(); + + 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].get(); + + 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].get(); + + 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].get(); + + 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 char16_t* aAction) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::PerformActionOnRow(const char16_t* aAction, int32_t aRow) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTreeContentView::PerformActionOnCell(const char16_t* 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].get(); + 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, + const nsAttrValue* aOldValue) +{ + // 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. + + if (mBoxObject && (aElement == mRoot || aElement == mBody)) { + mBoxObject->ClearStyleAndImageCaches(); + mBoxObject->Invalidate(); + } + + // We don't consider non-XUL nodes. + nsIContent* parent = nullptr; + if (!aElement->IsXULElement() || + ((parent = aElement->GetParent()) && !parent->IsXULElement())) { + return; + } + if (!aElement->IsAnyOfXULElements(nsGkAtoms::treecol, + nsGkAtoms::treeitem, + nsGkAtoms::treeseparator, + nsGkAtoms::treerow, + 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 + if (element->IsXULElement(nsGkAtoms::tree)) + return; // this is not for us + } + + // Handle changes of the hidden attribute. + if (aAttribute == nsGkAtoms::hidden && + aElement->IsAnyOfXULElements(nsGkAtoms::treeitem, + 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 (aElement->IsXULElement(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 (aElement->IsXULElement(nsGkAtoms::treeitem)) { + int32_t index = FindContent(aElement); + if (index >= 0) { + Row* row = mRows[index].get(); + 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 (aElement->IsXULElement(nsGkAtoms::treeseparator)) { + int32_t index = FindContent(aElement); + if (index >= 0) { + if (aAttribute == nsGkAtoms::properties && mBoxObject) { + mBoxObject->InvalidateRow(index); + } + } + } + else if (aElement->IsXULElement(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 (aElement->IsXULElement(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->IsXULElement()) { + 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. + + // Don't allow non-XUL nodes. + if (!aChild->IsXULElement() || !aContainer->IsXULElement()) + return; + + if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, + nsGkAtoms::treeseparator, + nsGkAtoms::treechildren, + nsGkAtoms::treerow, + 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 + if (element->IsXULElement(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 (aChild->IsXULElement(nsGkAtoms::treechildren)) { + int32_t index = FindContent(aContainer); + if (index >= 0) { + Row* row = mRows[index].get(); + 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 (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, + nsGkAtoms::treeseparator)) { + InsertRowFor(aContainer, aChild); + } + else if (aChild->IsXULElement(nsGkAtoms::treerow)) { + int32_t index = FindContent(aContainer); + if (index >= 0 && mBoxObject) + mBoxObject->InvalidateRow(index); + } + else if (aChild->IsXULElement(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. + + // We don't consider non-XUL nodes. + if (!aChild->IsXULElement() || !aContainer->IsXULElement()) + return; + + if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, + nsGkAtoms::treeseparator, + nsGkAtoms::treechildren, + nsGkAtoms::treerow, + 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 + if (element->IsXULElement(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 (aChild->IsXULElement(nsGkAtoms::treechildren)) { + int32_t index = FindContent(aContainer); + if (index >= 0) { + Row* row = mRows[index].get(); + 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 (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, + nsGkAtoms::treeseparator)) { + int32_t index = FindContent(aChild); + if (index >= 0) { + int32_t count = RemoveRow(index); + if (mBoxObject) + mBoxObject->RowCountChanged(index, -count); + } + } + else if (aChild->IsXULElement(nsGkAtoms::treerow)) { + int32_t index = FindContent(aContainer); + if (index >= 0 && mBoxObject) + mBoxObject->InvalidateRow(index); + } + else if (aChild->IsXULElement(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<UniquePtr<Row>>& aRows) +{ + // Don't allow non-XUL nodes. + if (!aContent->IsXULElement()) + return; + + dom::FlattenedChildIterator iter(aContent); + for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) { + int32_t count = aRows.Length(); + + if (content->IsXULElement(nsGkAtoms::treeitem)) { + SerializeItem(content, aParentIndex, aIndex, aRows); + } else if (content->IsXULElement(nsGkAtoms::treeseparator)) { + SerializeSeparator(content, aParentIndex, aIndex, aRows); + } + + *aIndex += aRows.Length() - count; + } +} + +void +nsTreeContentView::SerializeItem(nsIContent* aContent, int32_t aParentIndex, + int32_t* aIndex, nsTArray<UniquePtr<Row>>& aRows) +{ + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + return; + + aRows.AppendElement(MakeUnique<Row>(aContent, aParentIndex)); + Row* row = aRows.LastElement().get(); + + 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->IsXULElement()) { + // 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<UniquePtr<Row>>& aRows) +{ + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + return; + + auto row = MakeUnique<Row>(aContent, aParentIndex); + row->SetSeparator(true); + aRows.AppendElement(Move(row)); +} + +void +nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer, + nsIContent* aContent, int32_t* aIndex) +{ + uint32_t childCount = aContainer->GetChildCount(); + + if (!aContainer->IsXULElement()) + return; + + for (uint32_t i = 0; i < childCount; i++) { + nsIContent *content = aContainer->GetChildAt(i); + + if (content == aContent) + break; + + if (content->IsXULElement(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->IsXULElement()) + GetIndexInSubtree(child, aContent, aIndex); + } + } + } + else if (content->IsXULElement(nsGkAtoms::treeseparator)) { + if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + (*aIndex)++; + } + } +} + +int32_t +nsTreeContentView::EnsureSubtree(int32_t aIndex) +{ + Row* row = mRows[aIndex].get(); + + nsIContent* child; + child = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren); + if (!child || !child->IsXULElement()) { + return 0; + } + + AutoTArray<UniquePtr<Row>, 8> rows; + int32_t index = 0; + Serialize(child, aIndex, &index, rows); + // Insert |rows| into |mRows| at position |aIndex|, by first creating empty + // UniquePtr entries and then Move'ing |rows|'s entries into them. (Note + // that we can't simply use InsertElementsAt with an array argument, since + // the destination can't steal ownership from its const source argument.) + UniquePtr<Row>* newRows = mRows.InsertElementsAt(aIndex + 1, + rows.Length()); + for (nsTArray<Row>::index_type i = 0; i < rows.Length(); i++) { + newRows[i] = Move(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].get(); + 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(); + + if (grandParent->IsXULElement(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) +{ + AutoTArray<UniquePtr<Row>, 8> rows; + if (aContent->IsXULElement(nsGkAtoms::treeitem)) { + SerializeItem(aContent, aParentIndex, &aIndex, rows); + } else if (aContent->IsXULElement(nsGkAtoms::treeseparator)) { + SerializeSeparator(aContent, aParentIndex, &aIndex, rows); + } + + // We can't use InsertElementsAt since the destination can't steal + // ownership from its const source argument. + int32_t count = rows.Length(); + for (nsTArray<Row>::index_type i = 0; i < size_t(count); i++) { + mRows.InsertElementAt(aParentIndex + aIndex + i + 1, Move(rows[i])); + } + + 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].get(); + 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].get(); + 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].get(); + 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].get(); + 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].get(); + 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; + dom::FlattenedChildIterator iter(aContainer); + for (nsIContent* cell = iter.GetNextChild(); cell; cell = iter.GetNextChild()) { + if (cell->IsXULElement(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..c2c3f5030 --- /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 "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" +#include "mozilla/UniquePtr.h" + +class nsIDocument; +class Row; + +nsresult NS_NewTreeContentView(nsITreeView** aResult); + +class nsTreeContentView final : public nsINativeTreeView, + public nsITreeContentView, + public nsStubDocumentObserver +{ + public: + 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() 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: + ~nsTreeContentView(void); + + // Recursive methods which deal with serializing of nested content. + void Serialize(nsIContent* aContent, int32_t aParentIndex, int32_t* aIndex, + nsTArray<mozilla::UniquePtr<Row>>& aRows); + + void SerializeItem(nsIContent* aContent, int32_t aParentIndex, + int32_t* aIndex, nsTArray<mozilla::UniquePtr<Row>>& aRows); + + void SerializeSeparator(nsIContent* aContent, int32_t aParentIndex, + int32_t* aIndex, nsTArray<mozilla::UniquePtr<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<mozilla::UniquePtr<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..f559be042 --- /dev/null +++ b/layout/xul/tree/nsTreeImageListener.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/. */ + +#include "nsTreeImageListener.h" +#include "nsITreeBoxObject.h" +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "nsIContent.h" + +NS_IMPL_ISUPPORTS(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..573b246e5 --- /dev/null +++ b/layout/xul/tree/nsTreeImageListener.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 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 final : public imgINotificationObserver +{ +public: + explicit nsTreeImageListener(nsTreeBodyFrame *aTreeFrame); + + NS_DECL_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + + NS_IMETHOD ClearFrame(); + + friend class nsTreeBodyFrame; + +protected: + ~nsTreeImageListener(); + + 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: + explicit 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..1d251a096 --- /dev/null +++ b/layout/xul/tree/nsTreeSelection.cpp @@ -0,0 +1,866 @@ +/* -*- 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/AsyncEventDispatcher.h" +#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 "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsComponentManagerUtils.h" + +using namespace mozilla; + +// 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(nsTreeSelection, mTree, mCurrentColumn) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeSelection) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeSelection) + +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"); + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(treeDOMNode, + (aIndex != -1 ? DOMMenuItemActive : + DOMMenuItemInactive), + true, false); + return asyncDispatcher->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); + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(node, NS_LITERAL_STRING("select"), true, false); + asyncDispatcher->RunDOMEventWhenSafe(); + return NS_OK; +} + +void +nsTreeSelection::SelectCallback(nsITimer *aTimer, void *aClosure) +{ + RefPtr<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..41ffe31fb --- /dev/null +++ b/layout/xul/tree/nsTreeSelection.h @@ -0,0 +1,56 @@ +/* -*- 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 final : public nsINativeTreeSelection +{ +public: + explicit nsTreeSelection(nsITreeBoxObject* aTree); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsTreeSelection) + NS_DECL_NSITREESELECTION + + // nsINativeTreeSelection: Untrusted code can use us + NS_IMETHOD EnsureNative() override { return NS_OK; } + + friend struct nsTreeRange; + +protected: + ~nsTreeSelection(); + + 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..74b8a89c4 --- /dev/null +++ b/layout/xul/tree/nsTreeStyleCache.cpp @@ -0,0 +1,99 @@ +/* -*- 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" +#include "mozilla/StyleSetHandle.h" +#include "mozilla/StyleSetHandleInlines.h" + +nsTreeStyleCache::Transition::Transition(DFAState aState, nsIAtom* aSymbol) + : mState(aState), mInputSymbol(aSymbol) +{ +} + +bool +nsTreeStyleCache::Transition::operator==(const Transition& aOther) const +{ + return aOther.mState == mState && aOther.mInputSymbol == mInputSymbol; +} + +uint32_t +nsTreeStyleCache::Transition::Hash() const +{ + // Make a 32-bit integer that combines the low-order 16 bits of the state and the input symbol. + uint32_t hb = mState << 16; + uint32_t lb = (NS_PTR_TO_UINT32(mInputSymbol.get()) << 16) >> 16; + return hb+lb; +} + + +// 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(); + + // Go ahead and init the transition table. + if (!mTransitionTable) { + // Automatic miss. Build the table + mTransitionTable = new TransitionTable(); + } + + // The first transition is always made off the supplied pseudo-element. + Transition transition(0, aPseudoElement); + DFAState currState = mTransitionTable->Get(transition); + + if (!currState) { + // We had a miss. Make a new state and add it to our hash. + currState = mNextState; + mNextState++; + mTransitionTable->Put(transition, currState); + } + + for (uint32_t i = 0; i < count; i++) { + Transition transition(currState, aInputWord[i]); + currState = mTransitionTable->Get(transition); + + if (!currState) { + // We had a miss. Make a new state and add it to our hash. + currState = mNextState; + mNextState++; + mTransitionTable->Put(transition, currState); + } + } + + // We're in a final state. + // Look up our style context for this state. + nsStyleContext* result = nullptr; + if (mCache) { + result = mCache->GetWeak(currState); + } + if (!result) { + // We missed the cache. Resolve this pseudo-style. + // XXXheycam ServoStyleSets do not support XUL tree styles. + if (aPresContext->StyleSet()->IsServo()) { + MOZ_CRASH("stylo: ServoStyleSets should not support XUL tree styles yet"); + } + RefPtr<nsStyleContext> newResult = aPresContext->StyleSet()->AsGecko()-> + ResolveXULTreePseudoStyle(aContent->AsElement(), aPseudoElement, + aContext, aComparator); + + // Put the style context in our table, transferring the owning reference to the table. + if (!mCache) { + mCache = new StyleContextCache(); + } + result = newResult.get(); + mCache->Put(currState, newResult.forget()); + } + + return result; +} + diff --git a/layout/xul/tree/nsTreeStyleCache.h b/layout/xul/tree/nsTreeStyleCache.h new file mode 100644 index 000000000..1020b0435 --- /dev/null +++ b/layout/xul/tree/nsTreeStyleCache.h @@ -0,0 +1,89 @@ +/* -*- 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 "nsAutoPtr.h" +#include "nsIAtom.h" +#include "nsCOMArray.h" +#include "nsICSSPseudoComparator.h" +#include "nsRefPtrHashtable.h" +#include "nsStyleContext.h" + +typedef nsCOMArray<nsIAtom> AtomArray; + +class nsTreeStyleCache +{ +public: + nsTreeStyleCache() + : mNextState(0) + { + } + + ~nsTreeStyleCache() + { + Clear(); + } + + void Clear() + { + mTransitionTable = nullptr; + mCache = nullptr; + mNextState = 0; + } + + nsStyleContext* GetStyleContext(nsICSSPseudoComparator* aComparator, + nsPresContext* aPresContext, + nsIContent* aContent, + nsStyleContext* aContext, + nsIAtom* aPseudoElement, + const AtomArray & aInputWord); + +protected: + typedef uint32_t DFAState; + + class Transition final + { + public: + Transition(DFAState aState, nsIAtom* aSymbol); + bool operator==(const Transition& aOther) const; + uint32_t Hash() const; + + private: + DFAState mState; + nsCOMPtr<nsIAtom> mInputSymbol; + }; + + typedef nsDataHashtable<nsGenericHashKey<Transition>, DFAState> TransitionTable; + + // 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. + nsAutoPtr<TransitionTable> 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. + typedef nsRefPtrHashtable<nsUint32HashKey, nsStyleContext> StyleContextCache; + nsAutoPtr<StyleContextCache> mCache; + + // An integer counter that is used when we need to make new states in the + // DFA. + DFAState mNextState; +}; + +#endif // nsTreeStyleCache_h__ diff --git a/layout/xul/tree/nsTreeUtils.cpp b/layout/xul/tree/nsTreeUtils.cpp new file mode 100644 index 000000000..81a56f0e9 --- /dev/null +++ b/layout/xul/tree/nsTreeUtils.cpp @@ -0,0 +1,140 @@ +/* -*- 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 "ChildIterator.h" +#include "nsCRT.h" +#include "nsIAtom.h" +#include "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" + +using namespace mozilla; + +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 = NS_Atomize(Substring(first, iter)); + aPropertiesArray.AppendElement(atom); + } while (iter != end); + + return NS_OK; +} + +nsIContent* +nsTreeUtils::GetImmediateChild(nsIContent* aContainer, nsIAtom* aTag) +{ + dom::FlattenedChildIterator iter(aContainer); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + if (child->IsXULElement(aTag)) { + return child; + } + } + + return nullptr; +} + +nsIContent* +nsTreeUtils::GetDescendantChild(nsIContent* aContainer, nsIAtom* aTag) +{ + dom::FlattenedChildIterator iter(aContainer); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + if (child->IsXULElement(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__ |