/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Chris Waterson * Ben Goodger * Jan Varga * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nscore.h" #include "nsIContent.h" #include "nsINodeInfo.h" #include "nsIDOMElement.h" #include "nsILocalStore.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" // For sorting #include "nsICollation.h" #include "nsILocale.h" #include "nsILocaleService.h" #include "nsCollationCID.h" #include "nsQuickSort.h" #include "nsClusterKeySet.h" #include "nsTreeRows.h" #include "nsTreeRowTestNode.h" #include "nsRDFConMemberTestNode.h" #include "nsTemplateRule.h" #include "nsXULAtoms.h" #include "nsHTMLAtoms.h" #include "nsXULContentUtils.h" #include "nsXULTemplateBuilder.h" #include "nsVoidArray.h" #include "nsUnicharUtils.h" #include "nsINameSpaceManager.h" #include "nsIDOMClassInfo.h" // 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 // nsIXULTreeBuilder NS_DECL_NSIXULTREEBUILDER // nsITreeView NS_DECL_NSITREEVIEW // nsINativeTreeView: Untrusted code can use us NS_IMETHOD EnsureNative() { return NS_OK; } virtual void DocumentWillBeDestroyed(nsIDocument *aDocument); protected: friend NS_IMETHODIMP NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult); nsXULTreeBuilder(); virtual ~nsXULTreeBuilder(); /** * Initialize the template builder */ nsresult Init(); /** * Get sort variables from the active */ nsresult EnsureSortVariables(); virtual nsresult InitializeRuleNetworkForSimpleRules(InnerNode** aChildNode); virtual nsresult RebuildAll(); /** * Override default behavior to additionally handle the * condition. */ virtual nsresult CompileCondition(nsIAtom* aTag, nsTemplateRule* aRule, nsIContent* aCondition, InnerNode* aParentNode, TestNode** aResult); /** * Compile a condition */ nsresult CompileTreeRowCondition(nsTemplateRule* aRule, nsIContent* aCondition, InnerNode* aParentNode, TestNode** aResult); /** * Given a row, use the row's match to figure out the appropriate * in the rule's . */ nsresult GetTemplateActionRowFor(PRInt32 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(PRInt32 aRow, nsITreeColumn* aCol, nsIContent** aResult); /** * Return the resource corresponding to a row in the tree. The * result is *not* addref'd */ nsIRDFResource* GetResourceFor(PRInt32 aRow); /** * Open a container row, inserting the container's children into * the view. */ nsresult OpenContainer(PRInt32 aIndex, nsIRDFResource* aContainer); /** * Helper for OpenContainer, recursively open subtrees, remembering * persisted ``open'' state */ nsresult OpenSubtreeOf(nsTreeRows::Subtree* aSubtree, PRInt32 aIndex, nsIRDFResource* aContainer, PRInt32* aDelta); /** * Close a container row, removing the container's childrem from * the view. */ nsresult CloseContainer(PRInt32 aIndex, nsIRDFResource* aContainer); /** * Helper for CloseContainer(), recursively remove a subtree from * the view. Cleans up the conflict set. */ nsresult RemoveMatchesFor(nsIRDFResource* aContainer, nsIRDFResource* aMember); /** * A helper method that determines if the specified container is open. */ nsresult IsContainerOpen(nsIRDFResource* aContainer, PRBool* aResult); /** * A sorting callback for NS_QuickSort(). */ static int PR_CALLBACK Compare(const void* aLeft, const void* aRight, void* aClosure); /** * The real sort routine */ PRInt32 CompareMatches(nsTemplateMatch* aLeft, nsTemplateMatch* aRight); /** * Sort the specified subtree, and recursively sort any subtrees * beneath it. */ nsresult SortSubtree(nsTreeRows::Subtree* aSubtree); /** * Implement match replacement */ virtual nsresult ReplaceMatch(nsIRDFResource* aMember, const nsTemplateMatch* aOldMatch, nsTemplateMatch* aNewMatch); /** * Implement match synchronization */ virtual nsresult SynchronizeMatch(nsTemplateMatch* aMatch, const VariableSet& aModifiedVars); /** * 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 */ PRInt32 mSortVariable; enum Direction { eDirection_Descending = -1, eDirection_Natural = 0, eDirection_Ascending = +1 }; /** * The currently active sort order */ Direction mSortDirection; /** * The current collation */ nsCOMPtr mCollation; /** * The builder observers. */ nsCOMPtr mObservers; // pseudo-constants static PRInt32 gRefCnt; static nsIRDFResource* kRDF_type; static nsIRDFResource* kNC_BookmarkSeparator; }; PRInt32 nsXULTreeBuilder::gRefCnt = 0; nsIRDFResource* nsXULTreeBuilder::kRDF_type; nsIRDFResource* nsXULTreeBuilder::kNC_BookmarkSeparator; //---------------------------------------------------------------------- NS_IMETHODIMP NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult) { NS_PRECONDITION(aOuter == nsnull, "no aggregation"); if (aOuter) return NS_ERROR_NO_AGGREGATION; nsresult rv; nsXULTreeBuilder* result = new nsXULTreeBuilder(); if (! result) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(result); // stabilize rv = result->Init(); if (NS_SUCCEEDED(rv)) rv = result->QueryInterface(aIID, aResult); NS_RELEASE(result); return rv; } NS_IMPL_ADDREF(nsXULTreeBuilder) NS_IMPL_RELEASE(nsXULTreeBuilder) NS_INTERFACE_MAP_BEGIN(nsXULTreeBuilder) NS_INTERFACE_MAP_ENTRY(nsIXULTreeBuilder) NS_INTERFACE_MAP_ENTRY(nsITreeView) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTreeBuilder) NS_INTERFACE_MAP_ENTRY_DOM_CLASSINFO(XULTreeBuilder) NS_INTERFACE_MAP_END_INHERITING(nsXULTemplateBuilder) nsXULTreeBuilder::nsXULTreeBuilder() : mSortVariable(0), mSortDirection(eDirection_Natural) { } nsresult nsXULTreeBuilder::Init() { nsresult rv = nsXULTemplateBuilder::Init(); if (NS_FAILED(rv)) return rv; if (gRefCnt++ == 0) { gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"), &kRDF_type); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "BookmarkSeparator"), &kNC_BookmarkSeparator); } // Try to acquire a collation object for sorting nsCOMPtr ls = do_GetService(NS_LOCALESERVICE_CONTRACTID); if (ls) { nsCOMPtr locale; ls->GetApplicationLocale(getter_AddRefs(locale)); if (locale) { static NS_DEFINE_CID(kCollationFactoryCID, NS_COLLATIONFACTORY_CID); nsCOMPtr cfact = do_CreateInstance(kCollationFactoryCID); if (cfact) cfact->CreateCollation(locale, getter_AddRefs(mCollation)); } } return rv; } nsXULTreeBuilder::~nsXULTreeBuilder() { if (--gRefCnt == 0) { NS_IF_RELEASE(kRDF_type); NS_IF_RELEASE(kNC_BookmarkSeparator); } } //---------------------------------------------------------------------- // // nsIXULTreeBuilder methods // NS_IMETHODIMP nsXULTreeBuilder::GetResourceAtIndex(PRInt32 aRowIndex, nsIRDFResource** aResult) { if (aRowIndex < 0 || aRowIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; NS_IF_ADDREF(*aResult = GetResourceFor(aRowIndex)); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetIndexOfResource(nsIRDFResource* aResource, PRInt32* aResult) { nsTreeRows::iterator iter = mRows.Find(mConflictSet, aResource); if (iter == mRows.Last()) *aResult = -1; else *aResult = iter.GetRowIndex(); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::AddObserver(nsIXULTreeBuilderObserver* aObserver) { nsresult rv; if (!mObservers) { rv = NS_NewISupportsArray(getter_AddRefs(mObservers)); if (NS_FAILED(rv)) return rv; } return mObservers->AppendElement(aObserver); } NS_IMETHODIMP nsXULTreeBuilder::RemoveObserver(nsIXULTreeBuilderObserver* aObserver) { return mObservers ? mObservers->RemoveElement(aObserver) : NS_ERROR_FAILURE; } NS_IMETHODIMP nsXULTreeBuilder::Sort(nsIDOMElement* aElement) { nsCOMPtr header = do_QueryInterface(aElement); if (! header) return NS_ERROR_FAILURE; nsAutoString sortLocked; header->GetAttr(kNameSpaceID_None, nsXULAtoms::sortLocked, sortLocked); if (sortLocked.EqualsLiteral("true")) return NS_OK; nsAutoString sort; header->GetAttr(kNameSpaceID_None, nsXULAtoms::sort, sort); if (sort.IsEmpty()) return NS_OK; // Grab the new sort variable mSortVariable = mRules.LookupSymbol(sort.get()); // Cycle the sort direction nsAutoString dir; header->GetAttr(kNameSpaceID_None, nsXULAtoms::sortDirection, dir); if (dir.EqualsLiteral("ascending")) { dir.AssignLiteral("descending"); mSortDirection = eDirection_Descending; } else if (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(PRInt32* 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) { mSelection = aSelection; return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetRowProperties(PRInt32 aIndex, nsISupportsArray* aProperties) { 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, nsXULAtoms::properties, raw); if (!raw.IsEmpty()) { nsAutoString cooked; SubstituteText(*(mRows[aIndex]->mMatch), raw, cooked); nsTreeUtils::TokenizeProperties(cooked, aProperties); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetCellProperties(PRInt32 aRow, nsITreeColumn* aCol, nsISupportsArray* aProperties) { 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, nsXULAtoms::properties, raw); if (!raw.IsEmpty()) { nsAutoString cooked; SubstituteText(*(mRows[aRow]->mMatch), raw, cooked); nsTreeUtils::TokenizeProperties(cooked, aProperties); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetColumnProperties(nsITreeColumn* aCol, nsISupportsArray* aProperties) { // XXX sortactive fu return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsContainer(PRInt32 aIndex, PRBool* 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]; if (iter->mContainerType == nsTreeRows::eContainerType_Unknown) { PRBool isContainer; CheckContainer(GetResourceFor(aIndex), &isContainer, nsnull); iter->mContainerType = isContainer ? nsTreeRows::eContainerType_Container : nsTreeRows::eContainerType_Noncontainer; } *aResult = (iter->mContainerType == nsTreeRows::eContainerType_Container); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsContainerOpen(PRInt32 aIndex, PRBool* 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]; if (iter->mContainerState == nsTreeRows::eContainerState_Unknown) { PRBool isOpen; IsContainerOpen(GetResourceFor(aIndex), &isOpen); iter->mContainerState = isOpen ? nsTreeRows::eContainerState_Open : nsTreeRows::eContainerState_Closed; } *aResult = (iter->mContainerState == nsTreeRows::eContainerState_Open); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsContainerEmpty(PRInt32 aIndex, PRBool* 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 (iter->mContainerFill == nsTreeRows::eContainerFill_Unknown) { PRBool isEmpty; CheckContainer(GetResourceFor(aIndex), nsnull, &isEmpty); iter->mContainerFill = isEmpty ? nsTreeRows::eContainerFill_Empty : nsTreeRows::eContainerFill_Nonempty; } *aResult = (iter->mContainerFill == nsTreeRows::eContainerFill_Empty); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsSeparator(PRInt32 aIndex, PRBool* aResult) { NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row"); if (aIndex < 0 || aIndex >= mRows.Count()) return NS_ERROR_INVALID_ARG; nsIRDFResource* resource = GetResourceFor(aIndex); mDB->HasAssertion(resource, kRDF_type, kNC_BookmarkSeparator, PR_TRUE, aResult); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetParentIndex(PRInt32 aRowIndex, PRInt32* 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 PRInt32 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(PRInt32 aRowIndex, PRInt32 aAfterIndex, PRBool* 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 = PRBool(iter.GetChildIndex() != parent->Count() - 1); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetLevel(PRInt32 aRowIndex, PRInt32* 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(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aResult) { 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, nsHTMLAtoms::src, raw); SubstituteText(*(mRows[aRow]->mMatch), raw, aResult); } else aResult.SetCapacity(0); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetProgressMode(PRInt32 aRow, nsITreeColumn* aCol, PRInt32* aResult) { 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, nsXULAtoms::mode, raw); nsAutoString mode; SubstituteText(*(mRows[aRow]->mMatch), 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(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aResult) { 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, nsXULAtoms::value, raw); SubstituteText(*(mRows[aRow]->mMatch), raw, aResult); } else aResult.SetCapacity(0); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aResult) { 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, nsXULAtoms::label, raw); SubstituteText(*(mRows[aRow]->mMatch), raw, aResult); } else aResult.SetCapacity(0); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::SetTree(nsITreeBoxObject* tree) { NS_PRECONDITION(mRoot, "not initialized"); mBoxObject = tree; // If this is teardown time, then we're done. if (! mBoxObject) return NS_OK; nsCOMPtr doc = mRoot->GetDocument(); NS_ASSERTION(doc, "element has no document"); if (!doc) return NS_ERROR_UNEXPECTED; // Grab the doc's principal... nsIPrincipal* docPrincipal = doc->GetPrincipal(); if (!docPrincipal) return NS_ERROR_FAILURE; PRBool isTrusted = PR_FALSE; nsresult rv = IsSystemPrincipal(docPrincipal, &isTrusted); if (NS_SUCCEEDED(rv) && isTrusted) { // Get the datasource we intend to use to remember open state. nsAutoString datasourceStr; mRoot->GetAttr(kNameSpaceID_None, nsXULAtoms::statedatasource, datasourceStr); // since we are trusted, use the user specified datasource // if non specified, use localstore, which gives us // persistence across sessions if (! datasourceStr.IsEmpty()) { gRDFService->GetDataSource(NS_ConvertUCS2toUTF8(datasourceStr).get(), getter_AddRefs(mPersistStateStore)); } else { gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(mPersistStateStore)); } } // Either no specific datasource was specified, or we failed // to get one because we are not trusted. // // XXX if it were possible to ``write an arbitrary datasource // back'', then we could also allow an untrusted document to // use a statedatasource from the same codebase. if (! mPersistStateStore) { mPersistStateStore = do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource"); } NS_ASSERTION(mPersistStateStore, "failed to get a persistent state store"); if (! mPersistStateStore) return NS_ERROR_FAILURE; Rebuild(); EnsureSortVariables(); if (mSortVariable) SortSubtree(mRows.GetRoot()); return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::ToggleOpenState(PRInt32 aIndex) { if (mObservers) { PRUint32 count; mObservers->Count(&count); for (PRUint32 i = 0; i < count; ++i) { nsCOMPtr observer; mObservers->QueryElementAt(i, NS_GET_IID(nsIXULTreeBuilderObserver), getter_AddRefs(observer)); if (observer) observer->OnToggleOpenState(aIndex); } } if (mPersistStateStore) { PRBool isOpen; IsContainerOpen(aIndex, &isOpen); nsIRDFResource* container = GetResourceFor(aIndex); if (! container) return NS_ERROR_FAILURE; PRBool hasProperty; IsContainerOpen(container, &hasProperty); if (isOpen) { if (hasProperty) { mPersistStateStore->Unassert(container, nsXULContentUtils::NC_open, nsXULContentUtils::true_); } CloseContainer(aIndex, container); } else { if (! hasProperty) { mPersistStateStore->Assert(VALUE_TO_IRDFRESOURCE(container), nsXULContentUtils::NC_open, nsXULContentUtils::true_, PR_TRUE); } OpenContainer(aIndex, container); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::CycleHeader(nsITreeColumn* aCol) { nsCOMPtr element; aCol->GetElement(getter_AddRefs(element)); if (mObservers) { nsAutoString id; aCol->GetId(id); PRUint32 count; mObservers->Count(&count); for (PRUint32 i = 0; i < count; ++i) { nsCOMPtr observer; mObservers->QueryElementAt(i, NS_GET_IID(nsIXULTreeBuilderObserver), getter_AddRefs(observer)); if (observer) observer->OnCycleHeader(id.get(), element); } } return Sort(element); } NS_IMETHODIMP nsXULTreeBuilder::SelectionChanged() { if (mObservers) { PRUint32 count; mObservers->Count(&count); for (PRUint32 i = 0; i < count; ++i) { nsCOMPtr observer; mObservers->QueryElementAt(i, NS_GET_IID(nsIXULTreeBuilderObserver), getter_AddRefs(observer)); if (observer) observer->OnSelectionChanged(); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::CycleCell(PRInt32 row, nsITreeColumn* col) { if (mObservers) { nsAutoString id; col->GetId(id); PRUint32 count; mObservers->Count(&count); for (PRUint32 i = 0; i < count; ++i) { nsCOMPtr observer; mObservers->QueryElementAt(i, NS_GET_IID(nsIXULTreeBuilderObserver), getter_AddRefs(observer)); if (observer) observer->OnCycleCell(row, id.get()); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::IsEditable(PRInt32 aRow, nsITreeColumn* aCol, PRBool* _retval) { NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index"); if (aRow < 0 || aRow >= mRows.Count()) return NS_ERROR_INVALID_ARG; *_retval = PR_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, nsXULAtoms::editable, raw); nsAutoString editable; SubstituteText(*(mRows[aRow]->mMatch), raw, editable); if (editable.EqualsLiteral("false")) *_retval = PR_FALSE; } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::SetCellValue(PRInt32 row, nsITreeColumn* col, const nsAString& value) { return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::SetCellText(PRInt32 row, nsITreeColumn* col, const nsAString& value) { return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::PerformAction(const PRUnichar* action) { if (mObservers) { PRUint32 count; mObservers->Count(&count); for (PRUint32 i = 0; i < count; ++i) { nsCOMPtr observer; mObservers->QueryElementAt(i, NS_GET_IID(nsIXULTreeBuilderObserver), getter_AddRefs(observer)); if (observer) observer->OnPerformAction(action); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::PerformActionOnRow(const PRUnichar* action, PRInt32 row) { if (mObservers) { PRUint32 count; mObservers->Count(&count); for (PRUint32 i = 0; i < count; ++i) { nsCOMPtr observer; mObservers->QueryElementAt(i, NS_GET_IID(nsIXULTreeBuilderObserver), getter_AddRefs(observer)); if (observer) observer->OnPerformActionOnRow(action, row); } } return NS_OK; } NS_IMETHODIMP nsXULTreeBuilder::PerformActionOnCell(const PRUnichar* action, PRInt32 row, nsITreeColumn* col) { if (mObservers) { nsAutoString id; col->GetId(id); PRUint32 count; mObservers->Count(&count); for (PRUint32 i = 0; i < count; ++i) { nsCOMPtr observer; mObservers->QueryElementAt(i, NS_GET_IID(nsIXULTreeBuilderObserver), getter_AddRefs(observer)); if (observer) observer->OnPerformActionOnCell(action, row, id.get()); } } return NS_OK; } void nsXULTreeBuilder::DocumentWillBeDestroyed(nsIDocument* aDocument) { if (mObservers) mObservers->Clear(); nsXULTemplateBuilder::DocumentWillBeDestroyed(aDocument); } nsresult nsXULTreeBuilder::ReplaceMatch(nsIRDFResource* aMember, const nsTemplateMatch* aOldMatch, nsTemplateMatch* aNewMatch) { if (! mBoxObject) return NS_OK; if (aOldMatch) { // Either replacement or removal. Grovel through the rows // looking for aOldMatch. nsTreeRows::iterator iter = mRows.Find(mConflictSet, aMember); NS_ASSERTION(iter != mRows.Last(), "couldn't find row"); if (iter == mRows.Last()) return NS_ERROR_FAILURE; if (aNewMatch) { // replacement iter->mMatch = aNewMatch; mBoxObject->InvalidateRow(iter.GetRowIndex()); } else { // Removal. Clean up the conflict set. Value val; NS_CONST_CAST(nsTemplateMatch*, aOldMatch)->GetAssignmentFor(mConflictSet, mContainerVar, &val); nsIRDFResource* container = VALUE_TO_IRDFRESOURCE(val); RemoveMatchesFor(container, aMember); // Remove the rows from the view PRInt32 row = iter.GetRowIndex(); PRInt32 delta = mRows.GetSubtreeSizeFor(iter); 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); } } else if (aNewMatch) { // Insertion. Value val; aNewMatch->GetAssignmentFor(mConflictSet, mContainerVar, &val); nsIRDFResource* container = VALUE_TO_IRDFRESOURCE(val); PRInt32 row = -1; nsTreeRows::Subtree* parent = nsnull; if (container != mRows.GetRootResource()) { nsTreeRows::iterator iter = mRows.Find(mConflictSet, 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. PRBool open = PR_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 PRInt32 index = parent->Count(); if (mSortVariable) { // Figure out where to put the new element by doing an // insertion sort. PRInt32 left = 0; PRInt32 right = parent->Count(); while (left < right) { index = (left + right) / 2; PRInt32 cmp = CompareMatches((*parent)[index].mMatch, aNewMatch); if (cmp < 0) left = ++index; else if (cmp > 0) right = index; else break; } } 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. Value memberValue; aNewMatch->GetAssignmentFor(mConflictSet, mMemberVar, &memberValue); nsIRDFResource* member = VALUE_TO_IRDFRESOURCE(memberValue); PRBool open; IsContainerOpen(member, &open); if (open) OpenContainer(iter.GetRowIndex(), member); } } return NS_OK; } nsresult nsXULTreeBuilder::SynchronizeMatch(nsTemplateMatch* aMatch, const VariableSet& aModifiedVars) { if (mBoxObject) { // XXX we could be more conservative and just invalidate the cells // that got whacked... Value val; aMatch->GetAssignmentFor(mConflictSet, aMatch->mRule->GetMemberVariable(), &val); #ifdef PR_LOGGING if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { nsIRDFResource* res = VALUE_TO_IRDFRESOURCE(val); const char* str = "(null)"; if (res) res->GetValueConst(&str); PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, ("xultemplate[%p] synchronizing %s (match=%p)", this, str, aMatch)); } #endif nsTreeRows::iterator iter = mRows.Find(mConflictSet, VALUE_TO_IRDFRESOURCE(val)); NS_ASSERTION(iter != mRows.Last(), "couldn't find row"); if (iter == mRows.Last()) return NS_ERROR_FAILURE; PRInt32 row = iter.GetRowIndex(); if (row >= 0) mBoxObject->InvalidateRow(row); PR_LOG(gXULTemplateLog, PR_LOG_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, nsXULAtoms::treecols, getter_AddRefs(treecols)); if (!treecols) return NS_OK; PRUint32 count = treecols->GetChildCount(); for (PRUint32 i = 0; i < count; ++i) { nsIContent *child = treecols->GetChildAt(i); nsINodeInfo *ni = child->GetNodeInfo(); if (ni && ni->Equals(nsXULAtoms::treecol, kNameSpaceID_XUL)) { nsAutoString sortActive; child->GetAttr(kNameSpaceID_None, nsXULAtoms::sortActive, sortActive); if (sortActive.EqualsLiteral("true")) { nsAutoString sort; child->GetAttr(kNameSpaceID_None, nsXULAtoms::sort, sort); if (!sort.IsEmpty()) { mSortVariable = mRules.LookupSymbol(sort.get(), PR_TRUE); nsAutoString sortDirection; child->GetAttr(kNameSpaceID_None, nsXULAtoms::sortDirection, sortDirection); if (sortDirection.EqualsLiteral("ascending")) mSortDirection = eDirection_Ascending; else if (sortDirection.EqualsLiteral("descending")) mSortDirection = eDirection_Descending; else mSortDirection = eDirection_Natural; } break; } } } return NS_OK; } nsresult nsXULTreeBuilder::InitializeRuleNetworkForSimpleRules(InnerNode** aChildNode) { // For simple rules, the rule network will start off looking // something like this: // // (root)-->(treerow ^id ?a)-->(?a ^member ?b) // TestNode* rowtestnode = new nsTreeRowTestNode(mRules.GetRoot(), mConflictSet, mRows, mContainerVar); if (! rowtestnode) return NS_ERROR_OUT_OF_MEMORY; mRules.GetRoot()->AddChild(rowtestnode); mRules.AddNode(rowtestnode); // Create (?container ^member ?member) nsRDFConMemberTestNode* membernode = new nsRDFConMemberTestNode(rowtestnode, mConflictSet, mDB, mContainmentProperties, mContainerVar, mMemberVar); if (! membernode) return NS_ERROR_OUT_OF_MEMORY; rowtestnode->AddChild(membernode); mRules.AddNode(membernode); mRDFTests.Add(membernode); *aChildNode = membernode; return NS_OK; } nsresult nsXULTreeBuilder::RebuildAll() { NS_PRECONDITION(mRoot != nsnull, "not initialized"); if (! mRoot) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr doc = mRoot->GetDocument(); // Bail out early if we are being torn down. if (!doc) return NS_OK; PRInt32 count = mRows.Count(); mRows.Clear(); mConflictSet.Clear(); if (mBoxObject) { mBoxObject->BeginUpdateBatch(); mBoxObject->RowCountChanged(0, -count); } nsresult rv = CompileRules(); if (NS_FAILED(rv)) return rv; // Seed the rule network with assignments for the tree row // variable nsCOMPtr root; nsXULContentUtils::GetElementRefResource(mRoot, getter_AddRefs(root)); mRows.SetRootResource(root); #ifdef PR_LOGGING if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { const char* s = "(null)"; if (root) root->GetValueConst(&s); PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, ("xultemplate[%p] root=%s", this, s)); } #endif if (root) OpenContainer(-1, root); if (mBoxObject) { mBoxObject->EndUpdateBatch(); } return NS_OK; } nsresult nsXULTreeBuilder::CompileCondition(nsIAtom* aTag, nsTemplateRule* aRule, nsIContent* aCondition, InnerNode* aParentNode, TestNode** aResult) { nsresult rv; if (aTag == nsXULAtoms::content || aTag == nsXULAtoms::treeitem) rv = CompileTreeRowCondition(aRule, aCondition, aParentNode, aResult); else rv = nsXULTemplateBuilder::CompileCondition(aTag, aRule, aCondition, aParentNode, aResult); return rv; } nsresult nsXULTreeBuilder::CompileTreeRowCondition(nsTemplateRule* aRule, nsIContent* aCondition, InnerNode* aParentNode, TestNode** aResult) { // Compile a condition, which must be of the form: // // // // Right now, exactly one condition is required per rule. It // creates an nsTreeRowTestNode, binding the test's variable // to the global row variable that's used during match // propagation. The ``uri'' attribute must be set. nsAutoString uri; aCondition->GetAttr(kNameSpaceID_None, nsXULAtoms::uri, uri); if (uri[0] != PRUnichar('?')) { PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, ("xultemplate[%p] on test, expected 'uri' attribute to name a variable", this)); return NS_OK; } PRInt32 urivar = mRules.LookupSymbol(uri.get()); if (! urivar) { if (mContainerSymbol.IsEmpty()) { // If the container symbol was not explictly declared on // the