/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nscore.h" #include "nsError.h" #include "nsIContent.h" #include "mozilla/dom/NodeInfo.h" #include "nsIDOMElement.h" #include "nsIBoxObject.h" #include "nsITreeBoxObject.h" #include "nsITreeSelection.h" #include "nsITreeColumns.h" #include "nsITreeView.h" #include "nsTreeUtils.h" #include "nsIServiceManager.h" #include "nsReadableUtils.h" #include "nsQuickSort.h" #include "nsTreeRows.h" #include "nsTemplateRule.h" #include "nsTemplateMatch.h" #include "nsGkAtoms.h" #include "nsXULContentUtils.h" #include "nsXULTemplateBuilder.h" #include "nsIXULSortService.h" #include "nsTArray.h" #include "nsUnicharUtils.h" #include "nsNameSpaceManager.h" #include "nsDOMClassInfoID.h" #include "nsWhitespaceTokenizer.h" #include "nsTreeContentView.h" #include "nsIXULStore.h" #include "mozilla/BinarySearch.h" #include "mozilla/Logging.h" using mozilla::LogLevel; // For security check #include "nsIDocument.h" /** * A XUL template builder that serves as an tree view, allowing * (pretty much) arbitrary RDF to be presented in an tree. */ class nsXULTreeBuilder : public nsXULTemplateBuilder, public nsIXULTreeBuilder, public nsINativeTreeView { public: // nsISupports NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder) // nsIXULTreeBuilder NS_DECL_NSIXULTREEBUILDER // nsITreeView NS_DECL_NSITREEVIEW // nsINativeTreeView: Untrusted code can use us NS_IMETHOD EnsureNative() override { return NS_OK; } // nsIMutationObserver NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED protected: friend nsresult NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult); friend struct ResultComparator; nsXULTreeBuilder(); ~nsXULTreeBuilder(); /** * Uninitialize the template builder */ virtual void Uninit(bool aIsFinal) override; /** * Get sort variables from the active */ nsresult EnsureSortVariables(); virtual nsresult RebuildAll() override; /** * Given a row, use the row's match to figure out the appropriate * in the rule's . */ nsresult GetTemplateActionRowFor(int32_t aRow, nsIContent** aResult); /** * Given a row and a column ID, use the row's match to figure out * the appropriate in the rule's . */ nsresult GetTemplateActionCellFor(int32_t aRow, nsITreeColumn* aCol, nsIContent** aResult); /** * Return the resource corresponding to a row in the tree. */ nsresult GetResourceFor(int32_t aRow, nsIRDFResource** aResource); /** * Open a container row, inserting the container's children into * the view. */ nsresult OpenContainer(int32_t aIndex, nsIXULTemplateResult* aResult); /** * Helper for OpenContainer, recursively open subtrees, remembering * persisted ``open'' state */ nsresult OpenSubtreeOf(nsTreeRows::Subtree* aSubtree, int32_t aIndex, nsIXULTemplateResult *aResult, int32_t* aDelta); nsresult OpenSubtreeForQuerySet(nsTreeRows::Subtree* aSubtree, int32_t aIndex, nsIXULTemplateResult *aResult, nsTemplateQuerySet* aQuerySet, int32_t* aDelta, nsTArray& open); /** * Close a container row, removing the container's childrem from * the view. */ nsresult CloseContainer(int32_t aIndex); /** * Remove the matches for the rows in a subtree */ nsresult RemoveMatchesFor(nsTreeRows::Subtree& subtree); /** * Helper method that determines if the specified container is open. */ bool IsContainerOpen(nsIXULTemplateResult* aResource); /** * A sorting callback for NS_QuickSort(). */ static int Compare(const void* aLeft, const void* aRight, void* aClosure); /** * The real sort routine */ int32_t CompareResults(nsIXULTemplateResult* aLeft, nsIXULTemplateResult* aRight); /** * Sort the specified subtree, and recursively sort any subtrees * beneath it. */ nsresult SortSubtree(nsTreeRows::Subtree* aSubtree); NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource, nsIAtom* aTag, bool* aGenerated) override; // GetInsertionLocations, ReplaceMatch and SynchronizeResult are inherited // from nsXULTemplateBuilder /** * Return true if the result can be inserted into the template as a new * row. */ bool GetInsertionLocations(nsIXULTemplateResult* aResult, nsCOMArray** aLocations) override; /** * Implement result replacement */ virtual nsresult ReplaceMatch(nsIXULTemplateResult* aOldResult, nsTemplateMatch* aNewMatch, nsTemplateRule* aNewMatchRule, void* aContext) override; /** * Implement match synchronization */ virtual nsresult SynchronizeResult(nsIXULTemplateResult* aResult) override; /** * The tree's box object, used to communicate with the front-end. */ nsCOMPtr mBoxObject; /** * The tree's selection object. */ nsCOMPtr mSelection; /** * The datasource that's used to persist open folder information */ nsCOMPtr mPersistStateStore; /** * The rows in the view */ nsTreeRows mRows; /** * The currently active sort variable */ nsCOMPtr mSortVariable; enum Direction { eDirection_Descending = -1, eDirection_Natural = 0, eDirection_Ascending = +1 }; /** * The currently active sort order */ Direction mSortDirection; /* * Sort hints (compare case, etc) */ uint32_t mSortHints; /** * The builder observers. */ nsCOMArray mObservers; /* * XUL store for holding open container state */ nsCOMPtr mLocalStore; }; //---------------------------------------------------------------------- nsresult NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult) { *aResult = nullptr; NS_PRECONDITION(aOuter == nullptr, "no aggregation"); if (aOuter) return NS_ERROR_NO_AGGREGATION; nsresult rv; nsXULTreeBuilder* result = new nsXULTreeBuilder(); NS_ADDREF(result); // stabilize rv = result->InitGlobals(); if (NS_SUCCEEDED(rv)) rv = result->QueryInterface(aIID, aResult); NS_RELEASE(result); return rv; } NS_IMPL_ADDREF_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder) NS_IMPL_RELEASE_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder) NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder, mBoxObject, mSelection, mPersistStateStore, mLocalStore, mObservers) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder) NS_INTERFACE_MAP_ENTRY(nsIXULTreeBuilder) NS_INTERFACE_MAP_ENTRY(nsITreeView) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTreeBuilder) NS_INTERFACE_MAP_END_INHERITING(nsXULTemplateBuilder) nsXULTreeBuilder::nsXULTreeBuilder() : mSortDirection(eDirection_Natural), mSortHints(0) { } nsXULTreeBuilder::~nsXULTreeBuilder() { } void nsXULTreeBuilder::Uninit(bool aIsFinal) { int32_t count = mRows.Count(); mRows.Clear(); if (mBoxObject) { mBoxObject->BeginUpdateBatch(); mBoxObject->RowCountChanged(0, -count); if (mBoxObject) { mBoxObject->EndUpdateBatch(); } } nsXULTemplateBuilder::Uninit(aIsFinal); } //---------------------------------------------------------------------- // // nsIXULTreeBuilder methods // NS_IMETHODIMP nsXULTreeBuilder::GetResourceAtIndex(int32_t aRowIndex, nsIRDFResource** aResult) { if (aRowIndex < 0 || aRowIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; return GetResourceFor(aRowIndex, aResult); } NS_IMETHODIMP nsXULTreeBuilder::GetIndexOfResource(nsIRDFResource* aResource, int32_t* aResult) { NS_ENSURE_ARG_POINTER(aResource); nsTreeRows::iterator iter = mRows.FindByResource(aResource); if (iter == mRows.Last()) *aResult = -1; else *aResult = iter.GetRowIndex(); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::AddObserver(nsIXULTreeBuilderObserver* aObserver) { return mObservers.AppendObject(aObserver) ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsXULTreeBuilder::RemoveObserver(nsIXULTreeBuilderObserver* aObserver) { return mObservers.RemoveObject(aObserver) ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsXULTreeBuilder::Sort(nsIDOMElement* aElement) { nsCOMPtr header = do_QueryInterface(aElement); if (! header) return NS_ERROR_FAILURE; if (header->AttrValueIs(kNameSpaceID_None, nsGkAtoms::sortLocked, nsGkAtoms::_true, eCaseMatters)) return NS_OK; nsAutoString sort; header->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort); if (sort.IsEmpty()) return NS_OK; // Grab the new sort variable mSortVariable = NS_Atomize(sort); nsAutoString hints; header->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints); bool hasNaturalState = true; nsWhitespaceTokenizer tokenizer(hints); while (tokenizer.hasMoreTokens()) { const nsDependentSubstring& token(tokenizer.nextToken()); if (token.EqualsLiteral("comparecase")) mSortHints |= nsIXULSortService::SORT_COMPARECASE; else if (token.EqualsLiteral("integer")) mSortHints |= nsIXULSortService::SORT_INTEGER; else if (token.EqualsLiteral("twostate")) hasNaturalState = false; } // Cycle the sort direction nsAutoString dir; header->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, dir); if (dir.EqualsLiteral("ascending")) { dir.AssignLiteral("descending"); mSortDirection = eDirection_Descending; } else if (hasNaturalState && dir.EqualsLiteral("descending")) { dir.AssignLiteral("natural"); mSortDirection = eDirection_Natural; } else { dir.AssignLiteral("ascending"); mSortDirection = eDirection_Ascending; } // Sort it. SortSubtree(mRows.GetRoot()); mRows.InvalidateCachedRow(); if (mBoxObject) mBoxObject->Invalidate(); nsTreeUtils::UpdateSortIndicators(header, dir); return NS_OK; } //---------------------------------------------------------------------- // // nsITreeView methods // NS_IMETHODIMP nsXULTreeBuilder::GetRowCount(int32_t* aRowCount) { *aRowCount = mRows.Count(); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetSelection(nsITreeSelection** aSelection) { NS_IF_ADDREF(*aSelection = mSelection.get()); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::SetSelection(nsITreeSelection* aSelection) { NS_ENSURE_TRUE(!aSelection || nsTreeContentView::CanTrustTreeSelection(aSelection), NS_ERROR_DOM_SECURITY_ERR); mSelection = aSelection; return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetRowProperties(int32_t aIndex, nsAString& aProps) { NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row"); if (aIndex < 0 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsCOMPtr row; GetTemplateActionRowFor(aIndex, getter_AddRefs(row)); if (row) { nsAutoString raw; row->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, raw); if (!raw.IsEmpty()) { SubstituteText(mRows[aIndex]->mMatch->mResult, raw, aProps); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetCellProperties(int32_t aRow, nsITreeColumn* aCol, nsAString& aProps) { NS_ENSURE_ARG_POINTER(aCol); NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad row"); if (aRow < 0 || aRow >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsCOMPtr cell; GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell)); if (cell) { nsAutoString raw; cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, raw); if (!raw.IsEmpty()) { SubstituteText(mRows[aRow]->mMatch->mResult, raw, aProps); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps) { NS_ENSURE_ARG_POINTER(aCol); // XXX sortactive fu return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsContainer(int32_t aIndex, bool* aResult) { NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row"); if (aIndex < 0 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsTreeRows::iterator iter = mRows[aIndex]; bool isContainer; iter->mMatch->mResult->GetIsContainer(&isContainer); iter->mContainerType = isContainer ? nsTreeRows::eContainerType_Container : nsTreeRows::eContainerType_Noncontainer; *aResult = (iter->mContainerType == nsTreeRows::eContainerType_Container); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsContainerOpen(int32_t aIndex, bool* aOpen) { NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row"); if (aIndex < 0 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsTreeRows::iterator iter = mRows[aIndex]; if (iter->mContainerState == nsTreeRows::eContainerState_Unknown) { bool isOpen = IsContainerOpen(iter->mMatch->mResult); iter->mContainerState = isOpen ? nsTreeRows::eContainerState_Open : nsTreeRows::eContainerState_Closed; } *aOpen = (iter->mContainerState == nsTreeRows::eContainerState_Open); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsContainerEmpty(int32_t aIndex, bool* aResult) { NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row"); if (aIndex < 0 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsTreeRows::iterator iter = mRows[aIndex]; NS_ASSERTION(iter->mContainerType == nsTreeRows::eContainerType_Container, "asking for empty state on non-container"); // if recursion is disabled, pretend that the container is empty. This // ensures that folders are still displayed as such, yet won't display // their children if ((mFlags & eDontRecurse) && (iter->mMatch->mResult != mRootResult)) { *aResult = true; return NS_OK; } if (iter->mContainerFill == nsTreeRows::eContainerFill_Unknown) { bool isEmpty; iter->mMatch->mResult->GetIsEmpty(&isEmpty); iter->mContainerFill = isEmpty ? nsTreeRows::eContainerFill_Empty : nsTreeRows::eContainerFill_Nonempty; } *aResult = (iter->mContainerFill == nsTreeRows::eContainerFill_Empty); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsSeparator(int32_t aIndex, bool* aResult) { NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row"); if (aIndex < 0 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsAutoString type; nsTreeRows::Row& row = *(mRows[aIndex]); row.mMatch->mResult->GetType(type); *aResult = type.EqualsLiteral("separator"); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetParentIndex(int32_t aRowIndex, int32_t* aResult) { NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < mRows.Count(), "bad row"); if (aRowIndex < 0 || aRowIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; // Construct a path to the row nsTreeRows::iterator iter = mRows[aRowIndex]; // The parent of the row will be at the top of the path nsTreeRows::Subtree* parent = iter.GetParent(); // Now walk through our previous siblings, subtracting off each // one's subtree size int32_t index = iter.GetChildIndex(); while (--index >= 0) aRowIndex -= mRows.GetSubtreeSizeFor(parent, index) + 1; // Now the parent's index will be the first row's index, less one. *aResult = aRowIndex - 1; return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex, bool* aResult) { NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < mRows.Count(), "bad row"); if (aRowIndex < 0 || aRowIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; // Construct a path to the row nsTreeRows::iterator iter = mRows[aRowIndex]; // The parent of the row will be at the top of the path nsTreeRows::Subtree* parent = iter.GetParent(); // We have a next sibling if the child is not the last in the // subtree. *aResult = iter.GetChildIndex() != parent->Count() - 1; return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetLevel(int32_t aRowIndex, int32_t* aResult) { NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < mRows.Count(), "bad row"); if (aRowIndex < 0 || aRowIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; // Construct a path to the row; the ``level'' is the path length // less one. nsTreeRows::iterator iter = mRows[aRowIndex]; *aResult = iter.GetDepth() - 1; return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& aResult) { NS_ENSURE_ARG_POINTER(aCol); NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index"); if (aRow < 0 || aRow >= mRows.Count()) return NS_ERROR_INVALID_ARG; // Find the that corresponds to the column we want. nsCOMPtr cell; GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell)); if (cell) { nsAutoString raw; cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, raw); SubstituteText(mRows[aRow]->mMatch->mResult, raw, aResult); } else aResult.Truncate(); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* aResult) { NS_ENSURE_ARG_POINTER(aCol); NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index"); if (aRow < 0 || aRow >= mRows.Count()) return NS_ERROR_INVALID_ARG; *aResult = nsITreeView::PROGRESS_NONE; // Find the that corresponds to the column we want. nsCOMPtr cell; GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell)); if (cell) { nsAutoString raw; cell->GetAttr(kNameSpaceID_None, nsGkAtoms::mode, raw); nsAutoString mode; SubstituteText(mRows[aRow]->mMatch->mResult, raw, mode); if (mode.EqualsLiteral("normal")) *aResult = nsITreeView::PROGRESS_NORMAL; else if (mode.EqualsLiteral("undetermined")) *aResult = nsITreeView::PROGRESS_UNDETERMINED; } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& aResult) { NS_ENSURE_ARG_POINTER(aCol); NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index"); if (aRow < 0 || aRow >= mRows.Count()) return NS_ERROR_INVALID_ARG; // Find the that corresponds to the column we want. nsCOMPtr cell; GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell)); if (cell) { nsAutoString raw; cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, raw); SubstituteText(mRows[aRow]->mMatch->mResult, raw, aResult); } else aResult.Truncate(); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& aResult) { NS_ENSURE_ARG_POINTER(aCol); NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index"); if (aRow < 0 || aRow >= mRows.Count()) return NS_ERROR_INVALID_ARG; // Find the that corresponds to the column we want. nsCOMPtr cell; GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell)); if (cell) { nsAutoString raw; cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, raw); SubstituteText(mRows[aRow]->mMatch->mResult, raw, aResult); } else aResult.Truncate(); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::SetTree(nsITreeBoxObject* aTree) { mBoxObject = aTree; // If this is teardown time, then we're done. if (!mBoxObject) { Uninit(false); return NS_OK; } NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED); // Only use the XUL store if the root's principal is trusted. bool isTrusted = false; nsresult rv = IsSystemPrincipal(mRoot->NodePrincipal(), &isTrusted); if (NS_SUCCEEDED(rv) && isTrusted) { mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1"); if(NS_WARN_IF(!mLocalStore)){ return NS_ERROR_NOT_INITIALIZED; } } Rebuild(); EnsureSortVariables(); if (mSortVariable) SortSubtree(mRows.GetRoot()); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::ToggleOpenState(int32_t aIndex) { if (aIndex < 0 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsIXULTemplateResult* result = mRows[aIndex]->mMatch->mResult; if (! result) return NS_ERROR_FAILURE; if (mFlags & eDontRecurse) return NS_OK; if (result && result != mRootResult) { // don't open containers if child processing isn't allowed bool mayProcessChildren; nsresult rv = result->GetMayProcessChildren(&mayProcessChildren); if (NS_FAILED(rv) || !mayProcessChildren) return rv; } uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) observer->OnToggleOpenState(aIndex); } if (mLocalStore && mRoot) { bool isOpen; IsContainerOpen(aIndex, &isOpen); nsIDocument* doc = mRoot->GetComposedDoc(); if (!doc) { return NS_ERROR_FAILURE; } nsIURI* docURI = doc->GetDocumentURI(); nsTreeRows::Row& row = *(mRows[aIndex]); nsAutoString nodeid; nsresult rv = row.mMatch->mResult->GetId(nodeid); if (NS_FAILED(rv)) { return rv; } nsAutoCString utf8uri; rv = docURI->GetSpec(utf8uri); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } NS_ConvertUTF8toUTF16 uri(utf8uri); if (isOpen) { mLocalStore->RemoveValue(uri, nodeid, NS_LITERAL_STRING("open")); CloseContainer(aIndex); } else { mLocalStore->SetValue(uri, nodeid, NS_LITERAL_STRING("open"), NS_LITERAL_STRING("true")); OpenContainer(aIndex, result); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::CycleHeader(nsITreeColumn* aCol) { NS_ENSURE_ARG_POINTER(aCol); nsCOMPtr element; aCol->GetElement(getter_AddRefs(element)); nsAutoString id; aCol->GetId(id); uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) observer->OnCycleHeader(id.get(), element); } return Sort(element); } NS_IMETHODIMP nsXULTreeBuilder::SelectionChanged() { uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) observer->OnSelectionChanged(); } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::CycleCell(int32_t aRow, nsITreeColumn* aCol) { NS_ENSURE_ARG_POINTER(aCol); nsAutoString id; aCol->GetId(id); uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) observer->OnCycleCell(aRow, id.get()); } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsEditable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) { *_retval = true; NS_ENSURE_ARG_POINTER(aCol); NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index"); if (aRow < 0 || aRow >= mRows.Count()) return NS_ERROR_INVALID_ARG; // Find the that corresponds to the column we want. nsCOMPtr cell; GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell)); if (cell) { nsAutoString raw; cell->GetAttr(kNameSpaceID_None, nsGkAtoms::editable, raw); nsAutoString editable; SubstituteText(mRows[aRow]->mMatch->mResult, raw, editable); if (editable.EqualsLiteral("false")) *_retval = false; } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsSelectable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) { NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index"); if (aRow < 0 || aRow >= mRows.Count()) return NS_ERROR_INVALID_ARG; *_retval = true; // Find the that corresponds to the column we want. nsCOMPtr cell; GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell)); if (cell) { nsAutoString raw; cell->GetAttr(kNameSpaceID_None, nsGkAtoms::selectable, raw); nsAutoString selectable; SubstituteText(mRows[aRow]->mMatch->mResult, raw, selectable); if (selectable.EqualsLiteral("false")) *_retval = false; } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::SetCellValue(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) { NS_ENSURE_ARG_POINTER(aCol); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::SetCellText(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) { NS_ENSURE_ARG_POINTER(aCol); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::PerformAction(const char16_t* aAction) { uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) observer->OnPerformAction(aAction); } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::PerformActionOnRow(const char16_t* aAction, int32_t aRow) { uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) observer->OnPerformActionOnRow(aAction, aRow); } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::PerformActionOnCell(const char16_t* aAction, int32_t aRow, nsITreeColumn* aCol) { NS_ENSURE_ARG_POINTER(aCol); nsAutoString id; aCol->GetId(id); uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) observer->OnPerformActionOnCell(aAction, aRow, id.get()); } return NS_OK; } void nsXULTreeBuilder::NodeWillBeDestroyed(const nsINode* aNode) { nsCOMPtr kungFuDeathGrip(this); mObservers.Clear(); nsXULTemplateBuilder::NodeWillBeDestroyed(aNode); } NS_IMETHODIMP nsXULTreeBuilder::HasGeneratedContent(nsIRDFResource* aResource, nsIAtom* aTag, bool* aGenerated) { *aGenerated = false; NS_ENSURE_ARG_POINTER(aResource); if (!mRootResult) return NS_OK; nsCOMPtr rootresource; nsresult rv = mRootResult->GetResource(getter_AddRefs(rootresource)); if (NS_FAILED(rv)) return rv; if (aResource == rootresource || mRows.FindByResource(aResource) != mRows.Last()) *aGenerated = true; return NS_OK; } bool nsXULTreeBuilder::GetInsertionLocations(nsIXULTemplateResult* aResult, nsCOMArray** aLocations) { *aLocations = nullptr; // Get the reference point and check if it is an open container. Rows // should not be generated otherwise. nsAutoString ref; nsresult rv = aResult->GetBindingFor(mRefVariable, ref); if (NS_FAILED(rv) || ref.IsEmpty()) return false; nsCOMPtr container; rv = gRDFService->GetUnicodeResource(ref, getter_AddRefs(container)); if (NS_FAILED(rv)) return false; // Can always insert into the root resource if (container == mRows.GetRootResource()) return true; nsTreeRows::iterator iter = mRows.FindByResource(container); if (iter == mRows.Last()) return false; return (iter->mContainerState == nsTreeRows::eContainerState_Open); } struct ResultComparator { nsXULTreeBuilder* const mTreebuilder; nsIXULTemplateResult* const mResult; ResultComparator(nsXULTreeBuilder* aTreebuilder, nsIXULTemplateResult* aResult) : mTreebuilder(aTreebuilder), mResult(aResult) {} int operator()(const nsTreeRows::Row& aSubtree) const { return mTreebuilder->CompareResults(mResult, aSubtree.mMatch->mResult); } }; nsresult nsXULTreeBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult, nsTemplateMatch* aNewMatch, nsTemplateRule* aNewMatchRule, void *aLocation) { if (! mBoxObject) return NS_OK; if (aOldResult) { // Grovel through the rows looking for oldresult. nsTreeRows::iterator iter = mRows.Find(aOldResult); NS_ASSERTION(iter != mRows.Last(), "couldn't find row"); if (iter == mRows.Last()) return NS_ERROR_FAILURE; // Remove the rows from the view int32_t row = iter.GetRowIndex(); // If the row contains children, remove the matches from the // children so that they can be regenerated again if the element // gets added back. int32_t delta = mRows.GetSubtreeSizeFor(iter); if (delta) RemoveMatchesFor(*(iter->mSubtree)); if (mRows.RemoveRowAt(iter) == 0 && iter.GetRowIndex() >= 0) { // In this case iter now points to its parent // Invalidate the row's cached fill state iter->mContainerFill = nsTreeRows::eContainerFill_Unknown; nsCOMPtr cols; mBoxObject->GetColumns(getter_AddRefs(cols)); if (cols) { nsCOMPtr primaryCol; cols->GetPrimaryColumn(getter_AddRefs(primaryCol)); if (primaryCol) mBoxObject->InvalidateCell(iter.GetRowIndex(), primaryCol); } } // Notify the box object mBoxObject->RowCountChanged(row, -delta - 1); } if (aNewMatch && aNewMatch->mResult) { // Insertion. int32_t row = -1; nsTreeRows::Subtree* parent = nullptr; nsIXULTemplateResult* result = aNewMatch->mResult; nsAutoString ref; nsresult rv = result->GetBindingFor(mRefVariable, ref); if (NS_FAILED(rv) || ref.IsEmpty()) return rv; nsCOMPtr container; rv = gRDFService->GetUnicodeResource(ref, getter_AddRefs(container)); if (NS_FAILED(rv)) return rv; if (container != mRows.GetRootResource()) { nsTreeRows::iterator iter = mRows.FindByResource(container); row = iter.GetRowIndex(); NS_ASSERTION(iter != mRows.Last(), "couldn't find container row"); if (iter == mRows.Last()) return NS_ERROR_FAILURE; // Use the persist store to remember if the container // is open or closed. bool open = false; IsContainerOpen(row, &open); // If it's open, make sure that we've got a subtree structure ready. if (open) parent = mRows.EnsureSubtreeFor(iter); // We know something has just been inserted into the // container, so whether its open or closed, make sure // that we've got our tree row's state correct. if ((iter->mContainerType != nsTreeRows::eContainerType_Container) || (iter->mContainerFill != nsTreeRows::eContainerFill_Nonempty)) { iter->mContainerType = nsTreeRows::eContainerType_Container; iter->mContainerFill = nsTreeRows::eContainerFill_Nonempty; mBoxObject->InvalidateRow(iter.GetRowIndex()); } } else { parent = mRows.GetRoot(); } if (parent) { // If we get here, then we're inserting into an open // container. By default, place the new element at the // end of the container size_t index = parent->Count(); if (mSortVariable) { // Figure out where to put the new element through // binary search. mozilla::BinarySearchIf(*parent, 0, parent->Count(), ResultComparator(this, result), &index); } nsTreeRows::iterator iter = mRows.InsertRowAt(aNewMatch, parent, index); mBoxObject->RowCountChanged(iter.GetRowIndex(), +1); // See if this newly added row is open; in which case, // recursively add its children to the tree, too. if (mFlags & eDontRecurse) return NS_OK; if (result != mRootResult) { // don't open containers if child processing isn't allowed bool mayProcessChildren; nsresult rv = result->GetMayProcessChildren(&mayProcessChildren); if (NS_FAILED(rv) || ! mayProcessChildren) return NS_OK; } if (IsContainerOpen(result)) { OpenContainer(iter.GetRowIndex(), result); } } } return NS_OK; } nsresult nsXULTreeBuilder::SynchronizeResult(nsIXULTemplateResult* aResult) { if (mBoxObject) { // XXX we could be more conservative and just invalidate the cells // that got whacked... nsTreeRows::iterator iter = mRows.Find(aResult); NS_ASSERTION(iter != mRows.Last(), "couldn't find row"); if (iter == mRows.Last()) return NS_ERROR_FAILURE; int32_t row = iter.GetRowIndex(); if (row >= 0) mBoxObject->InvalidateRow(row); MOZ_LOG(gXULTemplateLog, LogLevel::Debug, ("xultemplate[%p] => row %d", this, row)); } return NS_OK; } //---------------------------------------------------------------------- nsresult nsXULTreeBuilder::EnsureSortVariables() { // Grovel through kids to find the // with the sort attributes. nsCOMPtr treecols; nsXULContentUtils::FindChildByTag(mRoot, kNameSpaceID_XUL, nsGkAtoms::treecols, getter_AddRefs(treecols)); if (!treecols) return NS_OK; for (nsIContent* child = treecols->GetFirstChild(); child; child = child->GetNextSibling()) { if (child->NodeInfo()->Equals(nsGkAtoms::treecol, kNameSpaceID_XUL)) { if (child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::sortActive, nsGkAtoms::_true, eCaseMatters)) { nsAutoString sort; child->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort); if (! sort.IsEmpty()) { mSortVariable = NS_Atomize(sort); static nsIContent::AttrValuesArray strings[] = {&nsGkAtoms::ascending, &nsGkAtoms::descending, nullptr}; switch (child->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sortDirection, strings, eCaseMatters)) { case 0: mSortDirection = eDirection_Ascending; break; case 1: mSortDirection = eDirection_Descending; break; default: mSortDirection = eDirection_Natural; break; } } break; } } } return NS_OK; } nsresult nsXULTreeBuilder::RebuildAll() { NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED); nsCOMPtr doc = mRoot->GetComposedDoc(); // Bail out early if we are being torn down. if (!doc) return NS_OK; if (! mQueryProcessor) return NS_OK; if (mBoxObject) { mBoxObject->BeginUpdateBatch(); } if (mQueriesCompiled) { Uninit(false); } else if (mBoxObject) { int32_t count = mRows.Count(); mRows.Clear(); mBoxObject->RowCountChanged(0, -count); } nsresult rv = CompileQueries(); if (NS_SUCCEEDED(rv) && mQuerySets.Length() > 0) { // Seed the rule network with assignments for the tree row variable nsAutoString ref; mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, ref); if (!ref.IsEmpty()) { rv = mQueryProcessor->TranslateRef(mDataSource, ref, getter_AddRefs(mRootResult)); if (NS_SUCCEEDED(rv) && mRootResult) { OpenContainer(-1, mRootResult); nsCOMPtr rootResource; GetResultResource(mRootResult, getter_AddRefs(rootResource)); mRows.SetRootResource(rootResource); } } } if (mBoxObject) { mBoxObject->EndUpdateBatch(); } return rv; } nsresult nsXULTreeBuilder::GetTemplateActionRowFor(int32_t aRow, nsIContent** aResult) { // Get the template in the DOM from which we're supposed to // generate text nsTreeRows::Row& row = *(mRows[aRow]); // The match stores the indices of the rule and query to use. Use these // to look up the right nsTemplateRule and use that rule's action to get // the treerow in the template. int16_t ruleindex = row.mMatch->RuleIndex(); if (ruleindex >= 0) { nsTemplateQuerySet* qs = mQuerySets[row.mMatch->QuerySetPriority()]; nsTemplateRule* rule = qs->GetRuleAt(ruleindex); if (rule) { nsCOMPtr children; nsXULContentUtils::FindChildByTag(rule->GetAction(), kNameSpaceID_XUL, nsGkAtoms::treechildren, getter_AddRefs(children)); if (children) { nsCOMPtr item; nsXULContentUtils::FindChildByTag(children, kNameSpaceID_XUL, nsGkAtoms::treeitem, getter_AddRefs(item)); if (item) return nsXULContentUtils::FindChildByTag(item, kNameSpaceID_XUL, nsGkAtoms::treerow, aResult); } } } *aResult = nullptr; return NS_OK; } nsresult nsXULTreeBuilder::GetTemplateActionCellFor(int32_t aRow, nsITreeColumn* aCol, nsIContent** aResult) { *aResult = nullptr; if (!aCol) return NS_ERROR_INVALID_ARG; nsCOMPtr row; GetTemplateActionRowFor(aRow, getter_AddRefs(row)); if (row) { nsCOMPtr colAtom; int32_t colIndex; aCol->GetAtom(getter_AddRefs(colAtom)); aCol->GetIndex(&colIndex); uint32_t j = 0; for (nsIContent* child = row->GetFirstChild(); child; child = child->GetNextSibling()) { if (child->NodeInfo()->Equals(nsGkAtoms::treecell, kNameSpaceID_XUL)) { if (colAtom && child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref, colAtom, eCaseMatters)) { *aResult = child; break; } else if (j == (uint32_t)colIndex) *aResult = child; j++; } } } NS_IF_ADDREF(*aResult); return NS_OK; } nsresult nsXULTreeBuilder::GetResourceFor(int32_t aRow, nsIRDFResource** aResource) { nsTreeRows::Row& row = *(mRows[aRow]); return GetResultResource(row.mMatch->mResult, aResource); } nsresult nsXULTreeBuilder::OpenContainer(int32_t aIndex, nsIXULTemplateResult* aResult) { // A row index of -1 in this case means ``open tree body'' NS_ASSERTION(aIndex >= -1 && aIndex < mRows.Count(), "bad row"); if (aIndex < -1 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsTreeRows::Subtree* container; if (aIndex >= 0) { nsTreeRows::iterator iter = mRows[aIndex]; container = mRows.EnsureSubtreeFor(iter.GetParent(), iter.GetChildIndex()); iter->mContainerState = nsTreeRows::eContainerState_Open; } else container = mRows.GetRoot(); if (! container) return NS_ERROR_OUT_OF_MEMORY; int32_t count; OpenSubtreeOf(container, aIndex, aResult, &count); // Notify the box object if (mBoxObject) { if (aIndex >= 0) mBoxObject->InvalidateRow(aIndex); if (count) mBoxObject->RowCountChanged(aIndex + 1, count); } return NS_OK; } nsresult nsXULTreeBuilder::OpenSubtreeOf(nsTreeRows::Subtree* aSubtree, int32_t aIndex, nsIXULTemplateResult *aResult, int32_t* aDelta) { AutoTArray open; int32_t count = 0; int32_t rulecount = mQuerySets.Length(); for (int32_t r = 0; r < rulecount; r++) { nsTemplateQuerySet* queryset = mQuerySets[r]; OpenSubtreeForQuerySet(aSubtree, aIndex, aResult, queryset, &count, open); } // Now recursively deal with any open sub-containers that just got // inserted. We need to do this back-to-front to avoid skewing offsets. for (int32_t i = open.Length() - 1; i >= 0; --i) { int32_t index = open[i]; nsTreeRows::Subtree* child = mRows.EnsureSubtreeFor(aSubtree, index); nsIXULTemplateResult* result = (*aSubtree)[index].mMatch->mResult; int32_t delta; OpenSubtreeOf(child, aIndex + index, result, &delta); count += delta; } // Sort the container. if (mSortVariable) { NS_QuickSort(mRows.GetRowsFor(aSubtree), aSubtree->Count(), sizeof(nsTreeRows::Row), Compare, this); } *aDelta = count; return NS_OK; } nsresult nsXULTreeBuilder::OpenSubtreeForQuerySet(nsTreeRows::Subtree* aSubtree, int32_t aIndex, nsIXULTemplateResult* aResult, nsTemplateQuerySet* aQuerySet, int32_t* aDelta, nsTArray& open) { int32_t count = *aDelta; nsCOMPtr results; nsresult rv = mQueryProcessor->GenerateResults(mDataSource, aResult, aQuerySet->mCompiledQuery, getter_AddRefs(results)); if (NS_FAILED(rv)) return rv; bool hasMoreResults; rv = results->HasMoreElements(&hasMoreResults); for (; NS_SUCCEEDED(rv) && hasMoreResults; rv = results->HasMoreElements(&hasMoreResults)) { nsCOMPtr nr; rv = results->GetNext(getter_AddRefs(nr)); if (NS_FAILED(rv)) return rv; nsCOMPtr nextresult = do_QueryInterface(nr); if (!nextresult) return NS_ERROR_UNEXPECTED; nsCOMPtr resultid; rv = GetResultResource(nextresult, getter_AddRefs(resultid)); if (NS_FAILED(rv)) return rv; if (! resultid) continue; // check if there is already an existing match. If so, a previous // query already generated content so the match is just added to the // end of the set of matches. bool generateContent = true; nsTemplateMatch* prevmatch = nullptr; nsTemplateMatch* existingmatch = nullptr; if (mMatchMap.Get(resultid, &existingmatch)){ // check if there is an existing match that matched a rule while (existingmatch) { if (existingmatch->IsActive()) generateContent = false; prevmatch = existingmatch; existingmatch = existingmatch->mNext; } } nsTemplateMatch *newmatch = nsTemplateMatch::Create(aQuerySet->Priority(), nextresult, nullptr); if (!newmatch) return NS_ERROR_OUT_OF_MEMORY; if (generateContent) { // Don't allow cyclic graphs to get our knickers in a knot. bool cyclic = false; if (aIndex >= 0) { for (nsTreeRows::iterator iter = mRows[aIndex]; iter.GetDepth() > 0; iter.Pop()) { nsCOMPtr parentid; rv = GetResultResource(iter->mMatch->mResult, getter_AddRefs(parentid)); if (NS_FAILED(rv)) { nsTemplateMatch::Destroy(newmatch, false); return rv; } if (resultid == parentid) { cyclic = true; break; } } } if (cyclic) { NS_WARNING("tree cannot handle cyclic graphs"); nsTemplateMatch::Destroy(newmatch, false); continue; } int16_t ruleindex; nsTemplateRule* matchedrule = nullptr; rv = DetermineMatchedRule(nullptr, nextresult, aQuerySet, &matchedrule, &ruleindex); if (NS_FAILED(rv)) { nsTemplateMatch::Destroy(newmatch, false); return rv; } if (matchedrule) { rv = newmatch->RuleMatched(aQuerySet, matchedrule, ruleindex, nextresult); if (NS_FAILED(rv)) { nsTemplateMatch::Destroy(newmatch, false); return rv; } // Remember that this match applied to this row mRows.InsertRowAt(newmatch, aSubtree, count); // If this is open, then remember it so we can recursively add // *its* rows to the tree. if (IsContainerOpen(nextresult)) { if (open.AppendElement(count) == nullptr) return NS_ERROR_OUT_OF_MEMORY; } ++count; } if (mFlags & eLoggingEnabled) OutputMatchToLog(resultid, newmatch, true); } if (prevmatch) { prevmatch->mNext = newmatch; } else { mMatchMap.Put(resultid, newmatch); } } *aDelta = count; return rv; } nsresult nsXULTreeBuilder::CloseContainer(int32_t aIndex) { NS_ASSERTION(aIndex >= 0 && aIndex < mRows.Count(), "bad row"); if (aIndex < 0 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsTreeRows::iterator iter = mRows[aIndex]; if (iter->mSubtree) RemoveMatchesFor(*iter->mSubtree); int32_t count = mRows.GetSubtreeSizeFor(iter); mRows.RemoveSubtreeFor(iter); iter->mContainerState = nsTreeRows::eContainerState_Closed; if (mBoxObject) { mBoxObject->InvalidateRow(aIndex); if (count) mBoxObject->RowCountChanged(aIndex + 1, -count); } return NS_OK; } nsresult nsXULTreeBuilder::RemoveMatchesFor(nsTreeRows::Subtree& subtree) { for (int32_t i = subtree.Count() - 1; i >= 0; --i) { nsTreeRows::Row& row = subtree[i]; nsTemplateMatch* match = row.mMatch; nsCOMPtr id; nsresult rv = GetResultResource(match->mResult, getter_AddRefs(id)); if (NS_FAILED(rv)) return rv; nsTemplateMatch* existingmatch; if (mMatchMap.Get(id, &existingmatch)) { while (existingmatch) { nsTemplateMatch* nextmatch = existingmatch->mNext; nsTemplateMatch::Destroy(existingmatch, true); existingmatch = nextmatch; } mMatchMap.Remove(id); } if ((row.mContainerState == nsTreeRows::eContainerState_Open) && row.mSubtree) RemoveMatchesFor(*(row.mSubtree)); } return NS_OK; } bool nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult) { // items are never open if recursion is disabled if ((mFlags & eDontRecurse) && aResult != mRootResult) { return false; } if (!mLocalStore) { return false; } nsIDocument* doc = mRoot->GetComposedDoc(); if (!doc) { return false; } nsIURI* docURI = doc->GetDocumentURI(); nsAutoString nodeid; nsresult rv = aResult->GetId(nodeid); if (NS_FAILED(rv)) { return false; } nsAutoCString utf8uri; rv = docURI->GetSpec(utf8uri); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } NS_ConvertUTF8toUTF16 uri(utf8uri); nsAutoString val; mLocalStore->GetValue(uri, nodeid, NS_LITERAL_STRING("open"), val); return val.EqualsLiteral("true"); } int nsXULTreeBuilder::Compare(const void* aLeft, const void* aRight, void* aClosure) { nsXULTreeBuilder* self = static_cast(aClosure); nsTreeRows::Row* left = static_cast (const_cast(aLeft)); nsTreeRows::Row* right = static_cast (const_cast(aRight)); return self->CompareResults(left->mMatch->mResult, right->mMatch->mResult); } int32_t nsXULTreeBuilder::CompareResults(nsIXULTemplateResult* aLeft, nsIXULTemplateResult* aRight) { // this is an extra check done for RDF queries such that results appear in // the order they appear in their containing Seq if (mSortDirection == eDirection_Natural && mDB) { // If the sort order is ``natural'', then see if the container // is an RDF sequence. If so, we'll try to use the ordinal // properties to determine order. // // XXX the problem with this is, it doesn't always get the // *real* container; e.g., // // // // // // // // In this case mRefVariable is bound to ?uri, not // ?subheadings. (The ``container'' in the template sense != // container in the RDF sense.) nsCOMPtr ref; nsresult rv = aLeft->GetBindingObjectFor(mRefVariable, getter_AddRefs(ref)); if (NS_FAILED(rv)) return 0; nsCOMPtr container = do_QueryInterface(ref); if (container) { bool isSequence = false; gRDFContainerUtils->IsSeq(mDB, container, &isSequence); if (isSequence) { // Determine the indices of the left and right elements // in the container. int32_t lindex = 0, rindex = 0; nsCOMPtr leftitem; aLeft->GetResource(getter_AddRefs(leftitem)); if (leftitem) { gRDFContainerUtils->IndexOf(mDB, container, leftitem, &lindex); if (lindex < 0) return 0; } nsCOMPtr rightitem; aRight->GetResource(getter_AddRefs(rightitem)); if (rightitem) { gRDFContainerUtils->IndexOf(mDB, container, rightitem, &rindex); if (rindex < 0) return 0; } return lindex - rindex; } } } int32_t sortorder; if (!mQueryProcessor) return 0; mQueryProcessor->CompareResults(aLeft, aRight, mSortVariable, mSortHints, &sortorder); if (sortorder) sortorder = sortorder * mSortDirection; return sortorder; } nsresult nsXULTreeBuilder::SortSubtree(nsTreeRows::Subtree* aSubtree) { NS_QuickSort(mRows.GetRowsFor(aSubtree), aSubtree->Count(), sizeof(nsTreeRows::Row), Compare, this); for (int32_t i = aSubtree->Count() - 1; i >= 0; --i) { nsTreeRows::Subtree* child = (*aSubtree)[i].mSubtree; if (child) SortSubtree(child); } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::CanDrop(int32_t index, int32_t orientation, nsIDOMDataTransfer* dataTransfer, bool *_retval) { *_retval = false; uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) { observer->CanDrop(index, orientation, dataTransfer, _retval); if (*_retval) break; } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::Drop(int32_t row, int32_t orient, nsIDOMDataTransfer* dataTransfer) { uint32_t count = mObservers.Count(); for (uint32_t i = 0; i < count; ++i) { nsCOMPtr observer = mObservers.SafeObjectAt(i); if (observer) { bool canDrop = false; observer->CanDrop(row, orient, dataTransfer, &canDrop); if (canDrop) observer->OnDrop(row, orient, dataTransfer); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsSorted(bool *_retval) { *_retval = (mSortVariable != nullptr); return NS_OK; }