diff options
Diffstat (limited to 'layout/xul/tree')
52 files changed, 12051 insertions, 0 deletions
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__ |