Bug 321169, add logging facility to xul templates, r=sicking,sr=neil
authorNeil Deakin <neil@mozilla.com>
Mon, 01 Feb 2010 13:09:47 -0500
changeset 37799 689d5779f815d5f5df9a1e47d0e676326342cbf1
parent 37798 6e3003aeea75aa71ad7b7e3868de07d97666b47a
child 37800 d5e4ed59c4504d2b88b499952b80f73af8f8bae8
push id11440
push userneil@mozilla.com
push dateMon, 01 Feb 2010 18:10:36 +0000
treeherdermozilla-central@689d5779f815 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking, neil
bugs321169
milestone1.9.3a1pre
Bug 321169, add logging facility to xul templates, r=sicking,sr=neil
content/xul/templates/src/nsRDFConMemberTestNode.cpp
content/xul/templates/src/nsRDFPropertyTestNode.cpp
content/xul/templates/src/nsXULContentBuilder.cpp
content/xul/templates/src/nsXULContentUtils.cpp
content/xul/templates/src/nsXULContentUtils.h
content/xul/templates/src/nsXULTemplateBuilder.cpp
content/xul/templates/src/nsXULTemplateBuilder.h
content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp
content/xul/templates/src/nsXULTemplateQueryProcessorXML.cpp
content/xul/templates/src/nsXULTreeBuilder.cpp
content/xul/templates/tests/chrome/Makefile.in
content/xul/templates/tests/chrome/templates_shared.js
content/xul/templates/tests/chrome/test_tmpl_errors.xul
content/xul/templates/tests/chrome/test_tmpl_invalidqp.xul
content/xul/templates/tests/chrome/test_tmpl_querysettwo.xul
content/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerules.xul
content/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegation.xul
content/xul/templates/tests/chrome/test_tmpl_wherenorel.xul
content/xul/templates/tests/chrome/test_tmpl_wherenosubject.xul
content/xul/templates/tests/chrome/test_tmpl_wherenovalue.xul
content/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginrule.xul
--- a/content/xul/templates/src/nsRDFConMemberTestNode.cpp
+++ b/content/xul/templates/src/nsRDFConMemberTestNode.cpp
@@ -38,20 +38,20 @@
 
 #include "nsRDFConMemberTestNode.h"
 #include "nsIRDFContainer.h"
 #include "nsIRDFContainerUtils.h"
 #include "nsRDFCID.h"
 #include "nsIServiceManager.h"
 #include "nsResourceSet.h"
 #include "nsString.h"
+#include "nsXULContentUtils.h"
 
 #include "prlog.h"
 #ifdef PR_LOGGING
-#include "nsXULContentUtils.h"
 extern PRLogModuleInfo* gXULTemplateLog;
 #endif
 
 nsRDFConMemberTestNode::nsRDFConMemberTestNode(TestNode* aParent,
                                                nsXULTemplateQueryProcessorRDF* aProcessor,
                                                nsIAtom *aContainerVariable,
                                                nsIAtom *aMemberVariable)
     : nsRDFTestNode(aParent),
@@ -469,16 +469,17 @@ nsRDFConMemberTestNode::FilterInstantiat
                     aInstantiations.Insert(inst, newinst);
                 }
             }
         }
 
         if (! hasContainerBinding && ! hasMemberBinding) {
             // Neither container nor member assignment!
             if (!aCantHandleYet) {
+                nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_MEMBER_UNBOUND);
                 return NS_ERROR_UNEXPECTED;
             }
 
             *aCantHandleYet = PR_TRUE;
             return NS_OK;
         }
 
         // finally, remove the "under specified" instantiation.
--- a/content/xul/templates/src/nsRDFPropertyTestNode.cpp
+++ b/content/xul/templates/src/nsRDFPropertyTestNode.cpp
@@ -33,22 +33,22 @@
  * 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 "nsRDFPropertyTestNode.h"
 #include "nsString.h"
+#include "nsXULContentUtils.h"
 
 #include "prlog.h"
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gXULTemplateLog;
 #include "nsIRDFLiteral.h"
-#include "nsXULContentUtils.h"
 #endif
 
 nsRDFPropertyTestNode::nsRDFPropertyTestNode(TestNode* aParent,
                                              nsXULTemplateQueryProcessorRDF* aProcessor,
                                              nsIAtom* aSourceVariable,
                                              nsIRDFResource* aProperty,
                                              nsIAtom* aTargetVariable)
     : nsRDFTestNode(aParent),
@@ -331,16 +331,17 @@ nsRDFPropertyTestNode::FilterInstantiati
                 aInstantiations.Insert(inst, newinst);
             }
 
             // finally, remove the "under specified" instantiation.
             aInstantiations.Erase(inst--);
         }
         else {
             if (!aCantHandleYet) {
+                nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_UNBOUND);
                 // Neither source nor target assignment!
                 return NS_ERROR_UNEXPECTED;
             }
 
             *aCantHandleYet = PR_TRUE;
             return NS_OK;
         }
     }
--- a/content/xul/templates/src/nsXULContentBuilder.cpp
+++ b/content/xul/templates/src/nsXULContentBuilder.cpp
@@ -1206,16 +1206,19 @@ nsXULContentBuilder::CreateContainerCont
             }
         }
 
         if (removematch) {
             // remove the generated content for the existing match
             rv = ReplaceMatch(removematch->mResult, nsnull, nsnull, aElement);
             if (NS_FAILED(rv))
                 return rv;
+
+            if (mFlags & eLoggingEnabled)
+                OutputMatchToLog(resultid, removematch, PR_FALSE);
         }
 
         if (generateContent) {
             // find the rule that matches. If none match, the content does not
             // need to be generated
 
             PRInt16 ruleindex;
             nsTemplateRule* matchedrule = nsnull;
@@ -1238,16 +1241,19 @@ nsXULContentBuilder::CreateContainerCont
                 nsCOMPtr<nsIContent> action = matchedrule->GetAction();
                 BuildContentFromTemplate(action, aElement, aElement, PR_TRUE,
                                          mRefVariable == matchedrule->GetMemberVariable(),
                                          nextresult, aNotify, newmatch,
                                          aContainer, aNewIndexInContainer);
             }
         }
 
+        if (mFlags & eLoggingEnabled)
+            OutputMatchToLog(resultid, newmatch, PR_TRUE);
+
         if (prevmatch) {
             prevmatch->mNext = newmatch;
         }
         else if (!mMatchMap.Put(resultid, newmatch)) {
             nsTemplateMatch::Destroy(mPool, newmatch, PR_TRUE);
             return NS_ERROR_OUT_OF_MEMORY;
         }
 
--- a/content/xul/templates/src/nsXULContentUtils.cpp
+++ b/content/xul/templates/src/nsXULContentUtils.cpp
@@ -84,25 +84,30 @@
 #include "nsContentUtils.h"
 #include "nsIDateTimeFormat.h"
 #include "nsDateTimeFormatCID.h"
 #include "nsIScriptableDateFormat.h"
 #include "nsICollation.h"
 #include "nsCollationCID.h"
 #include "nsILocale.h"
 #include "nsILocaleService.h"
+#include "nsIConsoleService.h"
 
 static NS_DEFINE_CID(kRDFServiceCID,        NS_RDFSERVICE_CID);
 
 //------------------------------------------------------------------------
 
 nsIRDFService* nsXULContentUtils::gRDF;
 nsIDateTimeFormat* nsXULContentUtils::gFormat;
 nsICollation *nsXULContentUtils::gCollation;
 
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gXULTemplateLog;
+#endif
+
 #define XUL_RESOURCE(ident, uri) nsIRDFResource* nsXULContentUtils::ident
 #define XUL_LITERAL(ident, val) nsIRDFLiteral* nsXULContentUtils::ident
 #include "nsXULResourceList.h"
 #undef XUL_RESOURCE
 #undef XUL_LITERAL
 
 //------------------------------------------------------------------------
 // Constructors n' stuff
@@ -479,8 +484,22 @@ nsXULContentUtils::SetCommandUpdater(nsI
     if (! domelement)
         return NS_ERROR_UNEXPECTED;
 
     rv = dispatcher->AddCommandUpdater(domelement, events, targets);
     if (NS_FAILED(rv)) return rv;
 
     return NS_OK;
 }
+
+void
+nsXULContentUtils::LogTemplateError(const char* aStr)
+{
+  nsAutoString message;
+  message.AssignLiteral("Error parsing template: ");
+  message.Append(NS_ConvertUTF8toUTF16(aStr).get());
+
+  nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+  if (cs) {
+    cs->LogStringMessage(message.get());
+    PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, ("Error parsing template: %s", aStr));
+  }
+}
--- a/content/xul/templates/src/nsXULContentUtils.h
+++ b/content/xul/templates/src/nsXULContentUtils.h
@@ -56,16 +56,62 @@ class nsCString;
 class nsString;
 class nsIRDFResource;
 class nsIRDFLiteral;
 class nsIRDFService;
 class nsINameSpaceManager;
 class nsIDateTimeFormat;
 class nsICollation;
 
+// errors to pass to LogTemplateError
+#define ERROR_TEMPLATE_INVALID_QUERYPROCESSOR                           \
+        "querytype attribute doesn't specify a valid query processor"
+#define ERROR_TEMPLATE_INVALID_QUERYSET                                 \
+        "unexpected <queryset> element"
+#define ERROR_TEMPLATE_NO_MEMBERVAR                                     \
+        "no member variable found. Action body should have an element with uri attribute"
+#define ERROR_TEMPLATE_WHERE_NO_SUBJECT                                 \
+        "<where> element is missing a subject attribute"
+#define ERROR_TEMPLATE_WHERE_NO_RELATION                                \
+        "<where> element is missing a rel attribute"
+#define ERROR_TEMPLATE_WHERE_NO_VALUE                                   \
+        "<where> element is missing a value attribute"
+#define ERROR_TEMPLATE_WHERE_NO_VAR                                     \
+        "<where> element must have at least one variable as a subject or value"
+#define ERROR_TEMPLATE_BINDING_BAD_SUBJECT                              \
+        "<binding> requires a variable for its subject attribute"
+#define ERROR_TEMPLATE_BINDING_BAD_PREDICATE                            \
+        "<binding> element is missing a predicate attribute"
+#define ERROR_TEMPLATE_BINDING_BAD_OBJECT                               \
+        "<binding> requires a variable for its object attribute"
+#define ERROR_TEMPLATE_CONTENT_NOT_FIRST                                \
+        "expected <content> to be first"
+#define ERROR_TEMPLATE_MEMBER_NOCONTAINERVAR                            \
+        "<member> requires a variable for its container attribute"
+#define ERROR_TEMPLATE_MEMBER_NOCHILDVAR                                \
+        "<member> requires a variable for its child attribute"
+#define ERROR_TEMPLATE_TRIPLE_NO_VAR                                    \
+        "<triple> should have at least one variable as a subject or object"
+#define ERROR_TEMPLATE_TRIPLE_BAD_SUBJECT                               \
+        "<triple> requires a variable for its subject attribute"
+#define ERROR_TEMPLATE_TRIPLE_BAD_PREDICATE                             \
+        "<triple> should have a non-variable value as a predicate"
+#define ERROR_TEMPLATE_TRIPLE_BAD_OBJECT                                \
+        "<triple> requires a variable for its object attribute"
+#define ERROR_TEMPLATE_MEMBER_UNBOUND                                   \
+        "neither container or child variables of <member> has a value"
+#define ERROR_TEMPLATE_TRIPLE_UNBOUND                                   \
+        "neither subject or object variables of <triple> has a value"
+#define ERROR_TEMPLATE_BAD_XPATH                                        \
+        "XPath expression in query could not be parsed"
+#define ERROR_TEMPLATE_BAD_ASSIGN_XPATH                                 \
+        "XPath expression in <assign> could not be parsed"
+#define ERROR_TEMPLATE_BAD_BINDING_XPATH                                \
+        "XPath expression in <binding> could not be parsed"
+
 class nsXULContentUtils
 {
 protected:
     static nsIRDFService* gRDF;
     static nsIDateTimeFormat* gFormat;
     static nsICollation *gCollation;
 
     static PRBool gDisableXULCache;
@@ -119,17 +165,23 @@ public:
     static nsresult
     GetResource(PRInt32 aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult);
 
     static nsresult
     GetResource(PRInt32 aNameSpaceID, const nsAString& aAttribute, nsIRDFResource** aResult);
 
     static nsresult
     SetCommandUpdater(nsIDocument* aDocument, nsIContent* aElement);
-    
+
+    /**
+     * Log a message to the error console
+     */
+    static void
+    LogTemplateError(const char* aMsg);
+
     static nsIRDFService*
     RDFService()
     {
         return gRDF;
     }
 
     static nsICollation*
     GetCollation();
--- a/content/xul/templates/src/nsXULTemplateBuilder.cpp
+++ b/content/xul/templates/src/nsXULTemplateBuilder.cpp
@@ -77,16 +77,17 @@
 #include "nsIXULDocument.h"
 #include "nsIXULTemplateBuilder.h"
 #include "nsIXULBuilderListener.h"
 #include "nsIRDFRemoteDataSource.h"
 #include "nsIRDFService.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIServiceManager.h"
 #include "nsISimpleEnumerator.h"
+#include "nsISupportsArray.h"
 #include "nsIMutableArray.h"
 #include "nsIURL.h"
 #include "nsIXPConnect.h"
 #include "nsContentCID.h"
 #include "nsRDFCID.h"
 #include "nsXULContentUtils.h"
 #include "nsString.h"
 #include "nsTArray.h"
@@ -96,17 +97,17 @@
 #include "nsXULElement.h"
 #include "jsapi.h"
 #include "prlog.h"
 #include "rdf.h"
 #include "pldhash.h"
 #include "plhash.h"
 #include "nsIDOMClassInfo.h"
 #include "nsPIDOMWindow.h"
-
+#include "nsIConsoleService.h" 
 #include "nsNetUtil.h"
 #include "nsXULTemplateBuilder.h"
 #include "nsXULTemplateQueryProcessorRDF.h"
 #include "nsXULTemplateQueryProcessorXML.h"
 #include "nsXULTemplateQueryProcessorStorage.h"
 
 //----------------------------------------------------------------------
 
@@ -763,31 +764,33 @@ nsXULTemplateBuilder::UpdateResultInCont
                         mMatchMap.Remove(aOldId);
                     }
                 }
 
                 if (prevmatch)
                     prevmatch->mNext = nextmatch;
 
                 removedmatch = oldmatch;
+                if (mFlags & eLoggingEnabled)
+                    OutputMatchToLog(aOldId, removedmatch, PR_FALSE);
             }
         }
     }
 
+    nsTemplateMatch *newmatch = nsnull;
     if (aNewResult) {
         // only allow a result to be inserted into containers with a matching tag
         nsIAtom* tag = aQuerySet->GetTag();
         if (aInsertionPoint && tag && tag != aInsertionPoint->Tag())
             return NS_OK;
 
         PRInt32 findpriority = aQuerySet->Priority();
 
-        nsTemplateMatch *newmatch =
-            nsTemplateMatch::Create(mPool, findpriority,
-                                    aNewResult, aInsertionPoint);
+        newmatch = nsTemplateMatch::Create(mPool, findpriority,
+                                           aNewResult, aInsertionPoint);
         if (!newmatch)
             return NS_ERROR_OUT_OF_MEMORY;
 
         nsTemplateMatch* firstmatch;
         if (mMatchMap.Get(aNewId, &firstmatch)) {
             PRBool hasEarlierActiveMatch = PR_FALSE;
 
             // Scan through the existing matches to find where the new one
@@ -969,19 +972,23 @@ nsXULTemplateBuilder::UpdateResultInCont
             }
         }
     }
 
     // The ReplaceMatch method is builder specific and removes the generated
     // content for a match.
 
     // Remove the content for a match that was active and needs to be replaced.
-    if (replacedmatch)
+    if (replacedmatch) {
         rv = ReplaceMatch(replacedmatch->mResult, nsnull, nsnull,
                           aInsertionPoint);
+
+        if (mFlags & eLoggingEnabled)
+            OutputMatchToLog(aNewId, replacedmatch, PR_FALSE);
+    }
  
     // remove a match that needs to be deleted.
     if (replacedmatchtodelete)
         nsTemplateMatch::Destroy(mPool, replacedmatchtodelete, PR_TRUE);
 
     // If the old match was active, the content for it needs to be removed.
     // If the old match was not active, it shouldn't have had any content,
     // so just pass null to ReplaceMatch. If acceptedmatch was set, then
@@ -989,16 +996,19 @@ nsXULTemplateBuilder::UpdateResultInCont
     if (oldMatchWasActive || acceptedmatch)
         rv = ReplaceMatch(oldMatchWasActive ? aOldResult : nsnull,
                           acceptedmatch, matchedrule, aInsertionPoint);
 
     // delete the old match that was replaced
     if (removedmatch)
         nsTemplateMatch::Destroy(mPool, removedmatch, PR_TRUE);
 
+    if (mFlags & eLoggingEnabled && newmatch)
+        OutputMatchToLog(aNewId, newmatch, PR_TRUE);
+
     return rv;
 }
 
 NS_IMETHODIMP
 nsXULTemplateBuilder::ResultBindingChanged(nsIXULTemplateResult* aResult)
 {
     // A binding update is used when only the values of the bindings have
     // changed, so the same rule still applies. Just synchronize the content.
@@ -1229,18 +1239,21 @@ nsXULTemplateBuilder::LoadDataSources(ns
     else if (querytype.EqualsLiteral("storage")) {
         mQueryProcessor = new nsXULTemplateQueryProcessorStorage();
         NS_ENSURE_TRUE(mQueryProcessor, NS_ERROR_OUT_OF_MEMORY);
     }
     else {
         nsCAutoString cid(NS_QUERY_PROCESSOR_CONTRACTID_PREFIX);
         AppendUTF16toUTF8(querytype, cid);
         mQueryProcessor = do_CreateInstance(cid.get(), &rv);
-        // XXXndeakin log an error here - bug 321169
-        NS_ENSURE_TRUE(mQueryProcessor, rv);
+
+        if (!mQueryProcessor) {
+            nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYPROCESSOR);
+            return rv;
+        }
     }
 
     rv = LoadDataSourceUrls(aDocument, datasources,
                             isRDFQuery, aShouldDelayBuilding);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Now set the database on the element, so that script writers can
     // access it.
@@ -1728,26 +1741,36 @@ nsXULTemplateBuilder::CompileQueries()
     // Determine if there are any special settings we need to observe
     mFlags = 0;
 
     nsAutoString flags;
     mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags);
 
     // if the dont-test-empty flag is set, containers should not be checked to
     // see if they are empty. If dont-recurse is set, then don't process the
-    // template recursively and only show one level of results.
+    // template recursively and only show one level of results. The logging
+    // flag logs errors and results to the console, which is useful when
+    // debugging templates.
     nsWhitespaceTokenizer tokenizer(flags);
     while (tokenizer.hasMoreTokens()) {
       const nsDependentSubstring& token(tokenizer.nextToken());
       if (token.EqualsLiteral("dont-test-empty"))
         mFlags |= eDontTestEmpty;
       else if (token.EqualsLiteral("dont-recurse"))
         mFlags |= eDontRecurse;
+      else if (token.EqualsLiteral("logging"))
+        mFlags |= eLoggingEnabled;
     }
 
+#ifdef PR_LOGGING
+    // always enable logging if the debug setting is used
+    if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG))
+        mFlags |= eLoggingEnabled;
+#endif
+
     nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot);
     nsresult rv =
         mQueryProcessor->InitializeForBuilding(mDataSource, this, rootnode);
     if (NS_FAILED(rv))
         return rv;
 
     // Set the "container" and "member" variables, if the user has specified
     // them. The container variable may be specified with the container
@@ -1806,18 +1829,16 @@ nsresult
 nsXULTemplateBuilder::CompileTemplate(nsIContent* aTemplate,
                                       nsTemplateQuerySet* aQuerySet,
                                       PRBool aIsQuerySet,
                                       PRInt32* aPriority,
                                       PRBool* aCanUseTemplate)
 {
     NS_ASSERTION(aQuerySet, "No queryset supplied");
 
-    // XXXndeakin log syntax errors
-
     nsresult rv = NS_OK;
 
     PRBool isQuerySetMode = PR_FALSE;
     PRBool hasQuerySet = PR_FALSE, hasRule = PR_FALSE, hasQuery = PR_FALSE;
 
     PRUint32 count = aTemplate->GetChildCount();
 
     for (PRUint32 i = 0; i < count; i++) {
@@ -1826,18 +1847,20 @@ nsXULTemplateBuilder::CompileTemplate(ns
 
         // don't allow more queries than can be supported
         if (*aPriority == PR_INT16_MAX)
             return NS_ERROR_FAILURE;
 
         // XXXndeakin queryset isn't a good name for this tag since it only
         //            ever contains one query
         if (!aIsQuerySet && ni->Equals(nsGkAtoms::queryset, kNameSpaceID_XUL)) {
-            if (hasRule || hasQuery)
+            if (hasRule || hasQuery) {
+              nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYSET);
               continue;
+            }
 
             isQuerySetMode = PR_TRUE;
 
             // only create a queryset for those after the first since the
             // first one is always created by CompileQueries
             if (hasQuerySet) {
                 aQuerySet = new nsTemplateQuerySet(++*aPriority);
                 if (!aQuerySet)
@@ -1868,17 +1891,20 @@ nsXULTemplateBuilder::CompileTemplate(ns
                                               kNameSpaceID_XUL,
                                               nsGkAtoms::action,
                                               getter_AddRefs(action));
 
             if (action){
                 nsCOMPtr<nsIAtom> memberVariable = mMemberVariable;
                 if (!memberVariable) {
                     memberVariable = DetermineMemberVariable(action);
-                    if (!memberVariable) continue;
+                    if (!memberVariable) {
+                        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_NO_MEMBERVAR);
+                        continue;
+                    }
                 }
 
                 if (hasQuery) {
                     nsCOMPtr<nsIAtom> tag;
                     DetermineRDFQueryRef(aQuerySet->mQueryNode,
                                          getter_AddRefs(tag));
                     if (tag)
                         aQuerySet->SetTag(tag);
@@ -1994,17 +2020,20 @@ nsXULTemplateBuilder::CompileTemplate(ns
             nsCOMPtr<nsIAtom> tag;
             DetermineRDFQueryRef(aQuerySet->mQueryNode, getter_AddRefs(tag));
             if (tag)
                 aQuerySet->SetTag(tag);
 
             nsCOMPtr<nsIAtom> memberVariable = mMemberVariable;
             if (!memberVariable) {
                 memberVariable = DetermineMemberVariable(rulenode);
-                if (!memberVariable) continue;
+                if (!memberVariable) {
+                    nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_NO_MEMBERVAR);
+                    continue;
+                }
             }
 
             nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode));
 
             rv = mQueryProcessor->CompileQuery(this, query,
                                                mRefVariable, memberVariable,
                                                getter_AddRefs(aQuerySet->mCompiledQuery));
 
@@ -2230,33 +2259,39 @@ nsXULTemplateBuilder::CompileWhereCondit
     //      startswith - subject must start with object
     //      endswith - subject must end with object
     //      contains - subject must contain object
     //    Comparisons are done as strings unless the subject is an integer.
 
     // subject
     nsAutoString subject;
     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject);
-    if (subject.IsEmpty())
+    if (subject.IsEmpty()) {
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_SUBJECT);
         return NS_OK;
+    }
 
     nsCOMPtr<nsIAtom> svar;
     if (subject[0] == PRUnichar('?'))
         svar = do_GetAtom(subject);
 
     nsAutoString relstring;
     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relstring);
-    if (relstring.IsEmpty())
+    if (relstring.IsEmpty()) {
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_RELATION);
         return NS_OK;
+    }
 
     // object
     nsAutoString value;
     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
-    if (value.IsEmpty())
+    if (value.IsEmpty()) {
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VALUE);
         return NS_OK;
+    }
 
     // multiple
     PRBool shouldMultiple =
       aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::multiple,
                               nsGkAtoms::_true, eCaseMatters);
 
     nsCOMPtr<nsIAtom> vvar;
     if (!shouldMultiple && (value[0] == PRUnichar('?'))) {
@@ -2283,18 +2318,17 @@ nsXULTemplateBuilder::CompileWhereCondit
         condition = new nsTemplateCondition(svar, relstring, value,
                                             shouldIgnoreCase, shouldNegate, shouldMultiple);
     }
     else if (vvar) {
         condition = new nsTemplateCondition(subject, relstring, vvar,
                                             shouldIgnoreCase, shouldNegate);
     }
     else {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] on <where> test, expected at least one variable", this));
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VAR);
         return NS_OK;
     }
 
     if (! condition)
         return NS_ERROR_OUT_OF_MEMORY;
 
     if (*aCurrentCondition) {
         (*aCurrentCondition)->SetNext(condition);
@@ -2317,34 +2351,19 @@ nsXULTemplateBuilder::CompileBindings(ns
     PRUint32 count = aBindings->GetChildCount();
 
     for (PRUint32 i = 0; i < count; ++i) {
         nsIContent *binding = aBindings->GetChildAt(i);
 
         if (binding->NodeInfo()->Equals(nsGkAtoms::binding,
                                         kNameSpaceID_XUL)) {
             rv = CompileBinding(aRule, binding);
+            if (NS_FAILED(rv))
+                return rv;
         }
-        else {
-#ifdef PR_LOGGING
-            nsAutoString tagstr;
-            binding->NodeInfo()->GetQualifiedName(tagstr);
-
-            nsCAutoString tagstrC;
-            tagstrC.AssignWithConversion(tagstr);
-            PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-                   ("xultemplate[%p] unrecognized binding <%s>",
-                    this, tagstrC.get()));
-#endif
-
-            continue;
-        }
-
-        if (NS_FAILED(rv))
-            return rv;
     }
 
     aRule->AddBindingsToQueryProcessor(mQueryProcessor);
 
     return NS_OK;
 }
 
 
@@ -2359,64 +2378,53 @@ nsXULTemplateBuilder::CompileBinding(nsT
     //            object="?var2" />
     //
     // XXXwaterson Some day it would be cool to allow the 'predicate'
     // to be bound to a variable.
 
     // subject
     nsAutoString subject;
     aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject);
-
     if (subject.IsEmpty()) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] <binding> requires `subject'", this));
-
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT);
         return NS_OK;
     }
 
     nsCOMPtr<nsIAtom> svar;
     if (subject[0] == PRUnichar('?')) {
         svar = do_GetAtom(subject);
     }
     else {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] <binding> requires `subject' to be a variable", this));
-
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT);
         return NS_OK;
     }
 
     // predicate
     nsAutoString predicate;
     aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate);
     if (predicate.IsEmpty()) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] <binding> requires `predicate'", this));
-
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_PREDICATE);
         return NS_OK;
     }
 
     // object
     nsAutoString object;
     aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object);
 
     if (object.IsEmpty()) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] <binding> requires `object'", this));
-
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT);
         return NS_OK;
     }
 
     nsCOMPtr<nsIAtom> ovar;
     if (object[0] == PRUnichar('?')) {
         ovar = do_GetAtom(object);
     }
     else {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] <binding> requires `object' to be a variable", this));
-
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT);
         return NS_OK;
     }
 
     return aRule->AddBinding(svar, predicate, ovar);
 }
 
 nsresult
 nsXULTemplateBuilder::AddSimpleRuleBindings(nsTemplateRule* aRule,
@@ -2533,8 +2541,107 @@ nsXULTemplateBuilder::GetResultResource(
         if (NS_FAILED(rv))
             return rv;
 
         return gRDFService->GetUnicodeResource(id, aResource);
     }
 
     return rv;
 }
+
+
+void
+nsXULTemplateBuilder::OutputMatchToLog(nsIRDFResource* aId,
+                                       nsTemplateMatch* aMatch,
+                                       PRBool aIsNew)
+{
+    PRInt32 priority = aMatch->QuerySetPriority() + 1;
+    PRInt32 activePriority = -1;
+
+    nsAutoString msg;
+
+    nsAutoString templateid;
+    mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::id, templateid);
+    msg.AppendLiteral("In template");
+    if (!templateid.IsEmpty()) {
+        msg.AppendLiteral(" with id ");
+        msg.Append(templateid);
+    }
+
+    nsAutoString refstring;
+    aMatch->mResult->GetBindingFor(mRefVariable, refstring);
+    if (!refstring.IsEmpty()) {
+        msg.AppendLiteral(" using ref ");
+        msg.Append(refstring);
+    }
+
+    msg.AppendLiteral("\n    ");
+
+    nsTemplateMatch* match = nsnull;
+    if (mMatchMap.Get(aId, &match)){
+        while (match) {
+            if (match == aMatch)
+                break;
+            if (match->IsActive() &&
+                match->GetContainer() == aMatch->GetContainer()) {
+                activePriority = match->QuerySetPriority() + 1;
+                break;
+            }
+            match = match->mNext;
+        }
+    }
+
+    if (aMatch->IsActive()) {
+        if (aIsNew) {
+            msg.AppendLiteral("New active result for query ");
+            msg.AppendInt(priority);
+            msg.AppendLiteral(" matching rule ");
+            msg.AppendInt(aMatch->RuleIndex() + 1);
+        }
+        else {
+            msg.AppendLiteral("Removed active result for query ");
+            msg.AppendInt(priority);
+            if (activePriority > 0) {
+                msg.AppendLiteral(" (new active query is ");
+                msg.AppendInt(activePriority);
+                msg.Append(')');
+            }
+            else {
+                msg.AppendLiteral(" (no new active query)");
+            }
+        }
+    }
+    else {
+        if (aIsNew) {
+            msg.AppendLiteral("New inactive result for query ");
+            msg.AppendInt(priority);
+            if (activePriority > 0) {
+                msg.AppendLiteral(" (overridden by query ");
+                msg.AppendInt(activePriority);
+                msg.Append(')');
+            }
+            else {
+                msg.AppendLiteral(" (didn't match a rule)");
+            }
+        }
+        else {
+            msg.AppendLiteral("Removed inactive result for query ");
+            msg.AppendInt(priority);
+            if (activePriority > 0) {
+                msg.AppendLiteral(" (active query is ");
+                msg.AppendInt(activePriority);
+                msg.Append(')');
+            }
+            else {
+                msg.AppendLiteral(" (no active query)");
+            }
+        }
+    }
+
+    nsAutoString idstring;
+    nsXULContentUtils::GetTextForNode(aId, idstring);
+    msg.AppendLiteral(": ");
+    msg.Append(idstring);
+
+    nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+    if (cs)
+      cs->LogStringMessage(msg.get());
+}
--- a/content/xul/templates/src/nsXULTemplateBuilder.h
+++ b/content/xul/templates/src/nsXULTemplateBuilder.h
@@ -415,17 +415,18 @@ protected:
     static nsIRDFService*            gRDFService;
     static nsIRDFContainerUtils*     gRDFContainerUtils;
     static nsIScriptSecurityManager* gScriptSecurityManager;
     static nsIPrincipal*             gSystemPrincipal;
     static nsIObserverService*       gObserverService;
 
     enum {
         eDontTestEmpty = (1 << 0),
-        eDontRecurse = (2 << 0)
+        eDontRecurse = (1 << 1),
+        eLoggingEnabled = (1 << 2)
     };
 
     PRInt32 mFlags;
 
     /**
      * Stack-based helper class to maintain a list of ``activated''
      * resources; i.e., resources for which we are currently building
      * content.
@@ -487,16 +488,28 @@ protected:
      * of variables that have changed.
      * @param aResult the ersult for which variable bindings has changed.
      * @param aModifiedVars the set of variables for which the bindings
      * have changed.
      */
     virtual nsresult
     SynchronizeResult(nsIXULTemplateResult* aResult) = 0;
 
+    /**
+     * Output a new match or removed match to the console.
+     *
+     * @param aId id of the result
+     * @param aMatch new or removed match
+     * @param aIsNew true for new matched, false for removed matches
+     */
+    void
+    OutputMatchToLog(nsIRDFResource* aId,
+                     nsTemplateMatch* aMatch,
+                     PRBool aIsNew);
+
     virtual void Traverse(nsCycleCollectionTraversalCallback &cb) const
     {
     }
 
     /**
      * Document that we're observing. Weak ref!
      */
     nsIDocument* mObservedDocument;
--- a/content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp
+++ b/content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp
@@ -1273,17 +1273,22 @@ nsXULTemplateQueryProcessorRDF::CompileE
     TestNode* prevnode = idnode;
 
     PRUint32 count = aConditions->GetChildCount();
 
     for (PRUint32 i = 0; i < count; ++i) {
         nsIContent *condition = aConditions->GetChildAt(i);
 
         // the <content> condition should always be the first child
-        if (condition->Tag() == nsGkAtoms::content && !i) {
+        if (condition->Tag() == nsGkAtoms::content) {
+            if (i) {
+                nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_CONTENT_NOT_FIRST);
+                continue;
+            }
+
             // check for <content tag='tag'/> which indicates that matches
             // should only be generated for items inside content with that tag
             nsAutoString tagstr;
             condition->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tagstr);
 
             nsCOMPtr<nsIAtom> tag;
             if (! tagstr.IsEmpty()) {
                 tag = do_GetAtom(tagstr);
@@ -1396,56 +1401,43 @@ nsXULTemplateQueryProcessorRDF::CompileT
 
     // subject
     nsAutoString subject;
     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject);
 
     nsCOMPtr<nsIAtom> svar;
     nsCOMPtr<nsIRDFResource> sres;
     if (subject.IsEmpty()) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] has empty <triple> 'subject'", this));
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_SUBJECT);
         return NS_OK;
     }
-
     if (subject[0] == PRUnichar('?'))
         svar = do_GetAtom(subject);
     else
         gRDFService->GetUnicodeResource(subject, getter_AddRefs(sres));
 
     // predicate
     nsAutoString predicate;
     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate);
 
     nsCOMPtr<nsIRDFResource> pres;
-    if (predicate.IsEmpty()) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] has empty <triple> 'predicate'", this));
-
+    if (predicate.IsEmpty() || predicate[0] == PRUnichar('?')) {
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_PREDICATE);
         return NS_OK;
     }
-
-    if (predicate[0] == PRUnichar('?')) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] cannot handle variables in <triple> 'predicate'", this));
-
-        return NS_OK;
-    }
-
     gRDFService->GetUnicodeResource(predicate, getter_AddRefs(pres));
 
     // object
     nsAutoString object;
     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object);
 
     nsCOMPtr<nsIAtom> ovar;
     nsCOMPtr<nsIRDFNode> onode;
     if (object.IsEmpty()) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] has empty <triple> 'object'", this));
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_OBJECT);
         return NS_OK;
     }
 
     if (object[0] == PRUnichar('?')) {
         ovar = do_GetAtom(object);
     }
     else if (object.FindChar(':') != -1) { // XXXwaterson evil.
         // treat as resource
@@ -1468,19 +1460,17 @@ nsXULTemplateQueryProcessorRDF::CompileT
     }
     else if (svar) {
         testnode = new nsRDFPropertyTestNode(aParentNode, this, svar, pres, onode);
     }
     else if (ovar) {
         testnode = new nsRDFPropertyTestNode(aParentNode, this, sres, pres, ovar);
     }
     else {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] tautology in <triple> test", this));
-
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_NO_VAR);
         return NS_OK;
     }
 
     if (! testnode)
         return NS_ERROR_OUT_OF_MEMORY;
 
     // add testnode to mAllTests first. If adding to mRDFTests fails, just
     // leave it in the list so that it can be deleted later.
@@ -1509,32 +1499,28 @@ nsXULTemplateQueryProcessorRDF::CompileM
     //   <member container="?var1" child="?var2" />
     //
 
     // container
     nsAutoString container;
     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::container, container);
 
     if (!container.IsEmpty() && container[0] != PRUnichar('?')) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] on <member> test, expected 'container' attribute to name a variable", this));
-
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_MEMBER_NOCONTAINERVAR);
         return NS_OK;
     }
 
     nsCOMPtr<nsIAtom> containervar = do_GetAtom(container);
 
     // child
     nsAutoString child;
     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::child, child);
 
     if (!child.IsEmpty() && child[0] != PRUnichar('?')) {
-        PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
-               ("xultemplate[%p] on <member> test, expected 'child' attribute to name a variable", this));
-
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_MEMBER_NOCHILDVAR);
         return NS_OK;
     }
 
     nsCOMPtr<nsIAtom> childvar = do_GetAtom(child);
 
     TestNode* testnode =
         new nsRDFConMemberTestNode(aParentNode,
                                    this,
--- a/content/xul/templates/src/nsXULTemplateQueryProcessorXML.cpp
+++ b/content/xul/templates/src/nsXULTemplateQueryProcessorXML.cpp
@@ -51,16 +51,17 @@
 #include "nsGkAtoms.h"
 #include "nsIServiceManager.h"
 #include "nsUnicharUtils.h"
 #include "nsIURI.h"
 #include "nsIArray.h"
 #include "nsContentUtils.h"
 #include "nsArrayUtils.h"
 #include "nsPIDOMWindow.h"
+#include "nsXULContentUtils.h"
 
 #include "nsXULTemplateBuilder.h"
 #include "nsXULTemplateQueryProcessorXML.h"
 #include "nsXULTemplateResultXML.h"
 
 NS_IMPL_ISUPPORTS1(nsXMLQuery, nsXMLQuery)
 
 //----------------------------------------------------------------------
@@ -269,17 +270,20 @@ nsXULTemplateQueryProcessorXML::CompileQ
 
     // if an expression is not specified, then the default is to
     // just take all of the children
     if (expr.IsEmpty())
         expr.AssignLiteral("*");
 
     nsCOMPtr<nsIDOMXPathExpression> compiledexpr;
     rv = CreateExpression(expr, aQueryNode, getter_AddRefs(compiledexpr));
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_FAILED(rv)) {
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BAD_XPATH);
+        return rv;
+    }
 
     nsRefPtr<nsXMLQuery> query =
         new nsXMLQuery(this, aMemberVariable, compiledexpr);
     NS_ENSURE_TRUE(query, NS_ERROR_OUT_OF_MEMORY);
 
     PRUint32 count = content->GetChildCount();
     for (PRUint32 i = 0; i < count; ++i) {
         nsIContent *condition = content->GetChildAt(i);
@@ -292,17 +296,20 @@ nsXULTemplateQueryProcessorXML::CompileQ
             condition->GetAttr(kNameSpaceID_None, nsGkAtoms::expr, expr);
 
             // ignore assignments without a variable or an expression
             if (!var.IsEmpty() && !expr.IsEmpty()) {
                 nsCOMPtr<nsIDOMNode> conditionNode =
                     do_QueryInterface(condition);
                 rv = CreateExpression(expr, conditionNode,
                                       getter_AddRefs(compiledexpr));
-                NS_ENSURE_SUCCESS(rv, rv);
+                if (NS_FAILED(rv)) {
+                    nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BAD_ASSIGN_XPATH);
+                    return rv;
+                }
 
                 nsCOMPtr<nsIAtom> varatom = do_GetAtom(var);
 
                 rv = query->AddBinding(varatom, compiledexpr);
                 NS_ENSURE_SUCCESS(rv, rv);
             }
         }
     }
@@ -373,17 +380,20 @@ nsXULTemplateQueryProcessorXML::AddBindi
         bindings = new nsXMLBindingSet();
         if (!bindings || !mRuleToBindingsMap.Put(aRuleNode, bindings))
             return NS_ERROR_OUT_OF_MEMORY;
     }
 
     nsCOMPtr<nsIDOMXPathExpression> compiledexpr;
     nsresult rv =
         CreateExpression(aExpr, aRuleNode, getter_AddRefs(compiledexpr));
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_FAILED(rv)) {
+        nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BAD_BINDING_XPATH);
+        return NS_OK;
+    }
 
     // aRef isn't currently used for XML query processors
     return bindings->AddBinding(aVar, compiledexpr);
 }
 
 NS_IMETHODIMP
 nsXULTemplateQueryProcessorXML::TranslateRef(nsISupports* aDatasource,
                                              const nsAString& aRefString,
--- a/content/xul/templates/src/nsXULTreeBuilder.cpp
+++ b/content/xul/templates/src/nsXULTreeBuilder.cpp
@@ -1673,16 +1673,20 @@ nsXULTreeBuilder::OpenSubtreeForQuerySet
                 IsContainerOpen(nextresult, &isOpen);
                 if (isOpen) {
                     if (open.AppendElement(count) == nsnull)
                         return NS_ERROR_OUT_OF_MEMORY;
                 }
 
                 ++count;
             }
+
+            if (mFlags & eLoggingEnabled)
+                OutputMatchToLog(resultid, newmatch, PR_TRUE);
+
         }
 
         if (prevmatch) {
             prevmatch->mNext = newmatch;
         }
         else if (!mMatchMap.Put(resultid, newmatch)) {
             nsTemplateMatch::Destroy(mPool, newmatch, PR_TRUE);
             return NS_ERROR_OUT_OF_MEMORY;
--- a/content/xul/templates/tests/chrome/Makefile.in
+++ b/content/xul/templates/tests/chrome/Makefile.in
@@ -247,12 +247,14 @@ include $(topsrcdir)/config/rules.mk
 		test_tmpl_xmlquerywithbindinginbindings.xul \
 		test_tmpl_xmlquerywithbindinginrule.xul \
 		test_tmpl_xmlquerywithsort.xul \
 		test_tmpl_xmlquerywithsortotherfield.xul \
 		test_tmpl_xmlquerywithmultiplequeries.xul \
 		test_tmpl_xmlquerywithothertypes.xul \
 		test_tmpl_xmlquerywithinlinedata.xul \
 		test_tmpl_xmlquerywithinlinedatawithmultiplequeries.xul \
+		test_tmpl_invalidqp.xul \
+		test_tmpl_errors.xul \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
--- a/content/xul/templates/tests/chrome/templates_shared.js
+++ b/content/xul/templates/tests/chrome/templates_shared.js
@@ -46,16 +46,18 @@
  * match. This is used, for example, for xml datasources, where the ids set on
  * the generated output are pseudo-random.
  */
 
 const ZOO_NS = "http://www.some-fictitious-zoo.com/";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const debug = false;
 
+var expectedConsoleMessages = [];
+
 try {
   const RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"].
                 getService(Components.interfaces.nsIRDFService);
   const ContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"].
                            getService(Components.interfaces.nsIRDFContainerUtils);
 } catch(ex) { }
 
 var xmlDoc;
@@ -97,29 +99,35 @@ function test_template()
       usedds = copyRDFDataSource(root, ds);
     if (needsOpen)
       root.open = true;
     setTimeout(iterateChanged, 0, root, usedds);
   }
   else {
     if (needsOpen)
       root.open = false;
+    if (expectedConsoleMessages.length)
+      compareConsoleMessages();
     SimpleTest.finish();
   }
 }
 
 function iterateChanged(root, ds)
 {
+  Components.classes["@mozilla.org/consoleservice;1"].
+             getService(Components.interfaces.nsIConsoleService).reset();
+
   for (var c = 0; c < changes.length; c++) {
     changes[c](ds, root);
     checkResults(root, c + 1);
   }
 
   if (needsOpen)
     root.open = false;
+  compareConsoleMessages();
   SimpleTest.finish();
 }
 
 function checkResults(root, step)
 {
   var output = expectedOutput.copy();
   setForCurrentStep(output, step);
 
@@ -393,8 +401,30 @@ function treeViewToDOMInner(columns, tre
 
         i = treeViewToDOMInner(columns, innertreechildren, view, builder, i + 1, level + 1);
       }
     }
   }
 
   return i;
 }
+
+function expectConsoleMessage(ref, id, isNew, isActive, extra)
+{
+  var message = "In template with id root" +
+                (ref ? " using ref " + ref : "") + "\n    " +
+                (isNew ? "New " : "Removed ") + (isActive ? "active" : "inactive") +
+                " result for query " + extra + ": " + id;
+  expectedConsoleMessages.push(message);
+}
+
+function compareConsoleMessages()
+{
+   var consoleService = Components.classes["@mozilla.org/consoleservice;1"].
+                          getService(Components.interfaces.nsIConsoleService);
+   var out = {};
+   consoleService.getMessageArray(out, {});
+   var messages = out.value || [];
+   is(messages.length, expectedConsoleMessages.length, "correct number of logged messages");
+   for (var m = 0; m < messages.length; m++) {
+     is(messages[m].message, expectedConsoleMessages.shift(), "logged message " + (m + 1));
+   }
+}
new file mode 100644
--- /dev/null
+++ b/content/xul/templates/tests/chrome/test_tmpl_errors.xul
@@ -0,0 +1,276 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+  tests for templates with invalid syntax
+-->
+
+<window title="XUL Invalid Template Tests" width="500" height="600"
+        onload="runTest();"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var consoleService = Components.classes["@mozilla.org/consoleservice;1"].
+                       getService(Components.interfaces.nsIConsoleService);
+
+function checkConsole(expectedError)
+{
+  var out = {};
+  consoleService.getMessageArray(out, {});
+  var messages = out.value || [];
+  is(messages[0].message, expectedError, "logged message " + expectedError);
+}
+
+// each test consists of a pre function executed before the template build, an
+// expected error message, and a post function executed after the template build
+var tests = [
+
+// <queryset> used in invalid location
+{
+  pre: function(template) template.insertBefore(document.createElement("queryset"), template.lastChild),
+  error: "Error parsing template: unexpected <queryset> element",
+  post: function(queryset) queryset.parentNode.removeChild(queryset)
+},
+
+// no member variable found
+{
+  pre: function(template) $("action").firstChild.removeAttribute("uri"),
+  error: "Error parsing template: no member variable found. Action body should have an element with uri attribute",
+  post: function() $("action").firstChild.setAttribute("uri", "?child")
+},
+
+// bad binding subject
+{
+  pre: function(template) $("binding").removeAttribute("subject"),
+  error: "Error parsing template: <binding> requires a variable for its subject attribute",
+  post: function() $("binding").setAttribute("subject", "?child"),
+},
+
+// bad binding predicate
+{
+  pre: function(template) $("binding").removeAttribute("predicate"),
+  error: "Error parsing template: <binding> element is missing a predicate attribute",
+  post: function() $("binding").setAttribute("predicate", "http://www.some-fictitious-zoo.com/rdf#name"),
+},
+
+// bad binding object
+{
+  pre: function(template) $("binding").setAttribute("object", "blah"),
+  error: "Error parsing template: <binding> requires a variable for its object attribute",
+  post: function() $("binding").setAttribute("object", "?name"),
+},
+
+// where condition missing a subject
+{
+  pre: function(template) { var rule = $("rule");
+                            var where = document.createElement("where");
+                            where.setAttribute("subject", "");
+                            where.setAttribute("rel", "equals");
+                            where.setAttribute("value", "Raven");
+                            rule.appendChild(where);
+                            return where; },
+  error: "Error parsing template: <where> element is missing a subject attribute",
+  post: function(where) { where.parentNode.removeChild(where); }
+},
+
+// where condition missing a rel
+{
+  pre: function(template) { var rule = $("rule");
+                            var where = document.createElement("where");
+                            where.setAttribute("subject", "?name");
+                            where.setAttribute("rel", "");
+                            where.setAttribute("value", "Raven");
+                            rule.appendChild(where);
+                            return where; },
+  error: "Error parsing template: <where> element is missing a rel attribute",
+  post: function(where) { where.parentNode.removeChild(where); }
+},
+
+// where condition missing a value
+{
+  pre: function(template) { var rule = $("rule");
+                            var where = document.createElement("where");
+                            where.setAttribute("subject", "?name");
+                            where.setAttribute("rel", "equals");
+                            where.setAttribute("value", "");
+                            rule.appendChild(where);
+                            return where; },
+  error: "Error parsing template: <where> element is missing a value attribute",
+  post: function(where) { where.parentNode.removeChild(where); }
+},
+
+// where condition missing a variable
+{
+  pre: function(template) { var rule = $("rule");
+                            var where = document.createElement("where");
+                            where.setAttribute("subject", "name");
+                            where.setAttribute("rel", "equals");
+                            where.setAttribute("value", "Raven");
+                            rule.appendChild(where);
+                            return where; },
+  error: "Error parsing template: <where> element must have at least one variable as a subject or value",
+  post: function(where) { where.parentNode.removeChild(where); }
+},
+
+// bad member container
+{
+  pre: function(template) $("member").setAttribute("container", "blah"),
+  error: "Error parsing template: <member> requires a variable for its container attribute",
+  post: function() $("member").setAttribute("container", "?uri"),
+},
+
+// bad member child
+{
+  pre: function(template) $("member").setAttribute("child", "blah"),
+  error: "Error parsing template: <member> requires a variable for its child attribute",
+  post: function() $("member").setAttribute("child", "?child"),
+},
+
+// bad triple subject
+{
+  pre: function(template) $("triple").removeAttribute("subject"),
+  error: "Error parsing template: <triple> requires a variable for its subject attribute",
+  post: function() $("triple").setAttribute("subject", "?child"),
+},
+
+// missing triple predicate
+{
+  pre: function(template) $("triple").removeAttribute("predicate"),
+  error: "Error parsing template: <triple> should have a non-variable value as a predicate",
+  post: function() $("triple").setAttribute("predicate", "http://www.some-fictitious-zoo.com/rdf#name"),
+},
+
+// bad triple predicate
+{
+  pre: function(template) $("triple").setAttribute("predicate", "?predicate"),
+  error: "Error parsing template: <triple> should have a non-variable value as a predicate",
+  post: function() $("triple").setAttribute("predicate", "http://www.some-fictitious-zoo.com/rdf#name"),
+},
+
+// bad triple object
+{
+  pre: function(template) $("triple").removeAttribute("object"),
+  error: "Error parsing template: <triple> requires a variable for its object attribute",
+  post: function() $("triple").setAttribute("object", "?name"),
+},
+
+// content not first element in query
+{
+  pre: function(template) { var content = $("content"); content.parentNode.appendChild(content); return content; },
+  error: "Error parsing template: expected <content> to be first",
+  post: function(content) content.parentNode.insertBefore(content, content.parentNode.firstChild),
+},
+
+// member container variable not bound
+{
+  pre: function(template) $("member").removeAttribute("container"),
+  error: "Error parsing template: neither container or child variables of <member> has a value",
+  post: function() $("member").setAttribute("container", "?uri"),
+},
+
+// neither triple subject or object variable are bound
+{
+  pre: function(template) $("triple").setAttribute("subject", "?blah"),
+  error: "Error parsing template: neither subject or object variables of <triple> has a value",
+  post: function() $("triple").setAttribute("subject", "?child"),
+},
+
+// neither triple subject or object variable are bound
+{
+  pre: function(template) { var triple = $("triple"); triple.setAttribute("subject", "blah");
+                            triple.setAttribute("object", "blah"); },
+  error: "Error parsing template: <triple> should have at least one variable as a subject or object",
+  post: function() { var triple = $("triple"); triple.setAttribute("subject", "?uri");
+                     triple.setAttribute("object", "?uri") }
+},
+
+// could not parse xml query expression
+{
+  firstXMLTest: true,
+  pre: function(template) { $("query").setAttribute("expr", "something()"); },
+  error: "Error parsing template: XPath expression in query could not be parsed",
+  post: function() { }
+},
+
+// could not parse xml assign expression
+{
+  pre: function(template) { var query = $("query");
+                            query.setAttribute("expr", "*");
+                            var assign = document.createElement("assign");
+                            assign.setAttribute("var", "?name");
+                            assign.setAttribute("expr", "something()");
+                            query.appendChild(assign);
+                            return assign; },
+  error: "Error parsing template: XPath expression in <assign> could not be parsed",
+  post: function(assign) { assign.parentNode.removeChild(assign); }
+},
+
+// could not parse xml binding expression
+{
+  pre: function(template) { $("binding").setAttribute("predicate", "something()"); },
+  error: "Error parsing template: XPath expression in <binding> could not be parsed",
+  post: function() { $("binding").setAttribute("predicate", "[name]"); },
+},
+
+];
+
+function runTest()
+{
+  var root = $("root");
+  var template = $("template");
+  while (test = tests.shift()) {
+    consoleService.reset();
+    var context = test.pre(template);
+    root.builder.rebuild();
+    checkConsole(test.error);
+    test.post(context);
+
+    // preload and set up for the xml datasource query error tests
+    if (tests.length && tests[0].firstXMLTest) {
+      var src = window.location.href.replace(/test_tmpl.*xul/, "animals.xml");
+      xmlDoc = new XMLHttpRequest();
+      xmlDoc.open("get", src, false);
+      xmlDoc.send(null);
+
+      var root = $("root");
+      root.setAttribute("querytype", "xml");
+      root.setAttribute("datasources", "animals.xml");
+      $("binding").setAttribute("predicate", "[name]");
+
+      setTimeout(runTest, 0);
+      return;
+    }
+  }
+  SimpleTest.finish();
+}
+
+]]>
+</script>
+
+<vbox id="root" datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+  <query id="query">
+    <content id="content" uri="?uri"/>
+    <member id="member" container="?uri" child="?child"/>
+    <triple id="triple" subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+  </query>
+  <rule id="rule">
+    <binding id="binding" subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+    <action id="action">
+      <label uri="?child" value="?name"/>
+    </action>
+  </rule>
+</template>
+</vbox>
+
+</window>
new file mode 100644
--- /dev/null
+++ b/content/xul/templates/tests/chrome/test_tmpl_invalidqp.xul
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+  invalid syntax - querytype="blah"
+-->
+
+<window title="XUL Template Tests" width="500" height="600"
+        onload="test_template();"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="invalid syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = <output/>;
+
+Components.classes["@mozilla.org/consoleservice;1"].
+           getService(Components.interfaces.nsIConsoleService).reset();
+expectedConsoleMessages.push("Error parsing template: querytype attribute doesn't specify a valid query processor");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.rdf"
+      ref="http://www.some-fictitious-zoo.com/birds" querytype="blah">
+<template zoo:name="Barn Owl" xmlns:zoo="http://www.some-fictitious-zoo.com/rdf#">
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</template>
+</vbox>
+
+</window>
--- a/content/xul/templates/tests/chrome/test_tmpl_querysettwo.xul
+++ b/content/xul/templates/tests/chrome/test_tmpl_querysettwo.xul
@@ -42,48 +42,59 @@ var changes = [
   // step 1
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'birds/emperorpenguin');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('Emperor Penguin'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'birds'));
     container.AppendElement(newnode);
+    expectConsoleMessage(ZOO_NS + 'birds', ZOO_NS + 'birds/emperorpenguin', true, true,
+                         '2 matching rule 1');
   },
   // step 2
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'birds/archaeopteryx');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('Archaeopteryx'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'birds'));
     container.InsertElementAt(newnode, '4', true);
+    expectConsoleMessage(ZOO_NS + 'birds', ZOO_NS + 'birds/archaeopteryx', true, true,
+                         '2 matching rule 1');
   },
   // step 3
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'birds/wren');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('Wren'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'birds'));
     container.InsertElementAt(newnode, '1', true);
+    expectConsoleMessage(ZOO_NS + 'birds', ZOO_NS + 'birds/wren', true, true,
+                         '2 matching rule 1');
   },
   // step 4
   function(targetds, root) {
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'birds'));
     var removednode = container.RemoveElementAt('3', true);
     targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                       RDF.GetLiteral('Barn Owl'), true);
+    expectConsoleMessage(ZOO_NS + 'birds', ZOO_NS + 'birds/barnowl', false, false,
+                         '2 (active query is 1)');
+    expectConsoleMessage(ZOO_NS + 'birds', ZOO_NS + 'birds/barnowl', false, true,
+                         '1 (no new active query)');
   }
 ];
 ]]>
 </script>
 
-<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/birds">
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" flags="logging"
+      datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/birds">
 <template>
 <queryset>
 <query>
 <content uri="?uri"/>
 <member container="?uri" child="?animal"/>
 <triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="Barn Owl"/>
 <triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
 </query>
--- a/content/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerules.xul
+++ b/content/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerules.xul
@@ -116,83 +116,121 @@ var expectedOutput =
       </treerow>
     </treeitem>
     <treeitem step="3,-4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="false">
       <treerow>
         <treecell label="Crustaceans"/>
         <treecell/>
       </treerow>
     </treeitem>
-    <treeitem step="4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="false" open="true">
+    <treeitem step="5" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="false" open="true">
       <treerow>
         <treecell label="Crustaceans"/>
         <treecell/>
       </treerow>
       <treechildren>
         <treeitem id="http://www.some-fictitious-zoo.com/crustaceans/lobster">
           <treerow>
             <treecell/>
             <treecell label="Is this cool: Lobster?"/>
           </treerow>
         </treeitem>
+        <treeitem id="http://www.some-fictitious-zoo.com/crustaceans/crayfish">
+          <treerow>
+            <treecell label="Crayfish"/>
+            <treecell/>
+          </treerow>
+        </treeitem>
       </treechildren>
     </treeitem>
     <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
       <treerow>
         <treecell label="Emu"/>
         <treecell/>
       </treerow>
     </treeitem>
   </treechildren>
 </output>;
 
 var changes = [
   // step 1
   function(targetds, root) {
     if (root.view && 1 < root.view.rowCount  && root.view.isContainer(1))
     root.view.toggleOpenState(1);
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/lion', true, true,
+                         '1 matching rule 1');
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/hippopotamus', true, true,
+                         '1 matching rule 2');
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/africanelephant', true, true,
+                         '1 matching rule 1');
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/llama', true, true,
+                         '1 matching rule 1');
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/polarbear', true, true,
+                         '1 matching rule 1');
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/aardvark', true, true,
+                         '1 matching rule 2');
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/ninebandedarmadillo', true, true,
+                         '1 matching rule 1');
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/gorilla', true, true,
+                         '1 matching rule 1');
   },
   // step 2
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'mammals/koala');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('Koala'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'mammals'));
     container.InsertElementAt(newnode, '4', true);
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/koala', true, true,
+                         '1 matching rule 1');
   },
   // step 3
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'crustaceans/lobster');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('Lobster'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'crustaceans'));
     container.AppendElement(newnode);
   },
   // step 4
   function(targetds, root) {
-    if (root.view && 11 < root.view.rowCount  && root.view.isContainer(11))
-    root.view.toggleOpenState(11);
+    var newnode = RDF.GetResource(ZOO_NS + 'crustaceans/crayfish');
+    targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+                    RDF.GetLiteral('Crayfish'), true);
+    var container = ContainerUtils.MakeSeq(targetds,
+                      RDF.GetResource(ZOO_NS + 'crustaceans'));
+    container.AppendElement(newnode);
   },
   // step 5
   function(targetds, root) {
     if (root.view && 11 < root.view.rowCount  && root.view.isContainer(11))
     root.view.toggleOpenState(11);
+    expectConsoleMessage(ZOO_NS + 'crustaceans', ZOO_NS + 'crustaceans/lobster', true, true,
+                         '1 matching rule 1');
+    expectConsoleMessage(ZOO_NS + 'crustaceans', ZOO_NS + 'crustaceans/crayfish', true, true,
+                         '1 matching rule 2');
   },
   // step 6
   function(targetds, root) {
+    if (root.view && 11 < root.view.rowCount  && root.view.isContainer(11))
+    root.view.toggleOpenState(11);
+  },
+  // step 7
+  function(targetds, root) {
     if (root.view && 1 < root.view.rowCount  && root.view.isContainer(1))
     root.view.toggleOpenState(1);
   }
 ];
 ]]>
 </script>
 
-<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" hidevscroll="true" hidehscroll="true" datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/marked" id="root">
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" hidevscroll="true" hidehscroll="true"
+      flags="logging" datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/marked" id="root">
 <treecols orient="horizontal" id="treecols">
 <treecol id="treecol" primary="true" label="Name"/>
 <treecol label="Species"/>
 </treecols>
 <template id="template">
 <query>
 <content uri="?uri"/>
 <member container="?uri" child="?child"/>
--- a/content/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegation.xul
+++ b/content/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegation.xul
@@ -43,63 +43,74 @@ var changes = [
   // step 1
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'mammals/arctichare');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('Arctic Hare'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'mammals'));
     container.AppendElement(newnode);
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/arctichare', true, true,
+                         '1 matching rule 1');
   },
   // step 2
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'mammals/koala');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('Koala'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'mammals'));
     container.InsertElementAt(newnode, '4', true);
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/koala', true, false,
+                         '1 (didn\'t match a rule)');
   },
   // step 3
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'mammals/zebra');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('Zebra'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'mammals'));
     container.InsertElementAt(newnode, '1', true);
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/zebra', true, true,
+                         '1 matching rule 1');
   },
   // step 4
   function(targetds, root) {
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'mammals'));
     var removednode = container.RemoveElementAt('4', true);
     targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                       RDF.GetLiteral('African Elephant'), true);
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/africanelephant', false, true,
+                         '1 (no new active query)');
   },
   // step 5
   function(targetds, root) {
     var newnode = RDF.GetResource(ZOO_NS + 'mammals/africanelephant');
     targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
                     RDF.GetLiteral('African Elephant'), true);
     var container = ContainerUtils.MakeSeq(targetds,
                       RDF.GetResource(ZOO_NS + 'mammals'));
     container.AppendElement(newnode);
+    expectConsoleMessage(ZOO_NS + 'mammals', ZOO_NS + 'mammals/africanelephant', true, true,
+                         '1 matching rule 1');
   },
   // step 6
   function(targetds, root) {
     targetds.Assert(RDF.GetResource(ZOO_NS + 'mammals/koala'),
                     RDF.GetResource(ZOO_NS + 'rdf#specimensAsString'),
                     RDF.GetLiteral('8'), true);
   }
 ];
 ]]>
 </script>
 
-<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/mammals">
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" flags="logging"
+      datasources="animals.rdf" ref="http://www.some-fictitious-zoo.com/mammals">
 <template id="template">
 <query id="query">
 <content uri="?uri"/>
 <member container="?uri" child="?animal"/>
 <triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
 </query>
 <rule>
 <conditions id="conditions">
--- a/content/xul/templates/tests/chrome/test_tmpl_wherenorel.xul
+++ b/content/xul/templates/tests/chrome/test_tmpl_wherenorel.xul
@@ -17,16 +17,20 @@
   <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
 
 <script src="templates_shared.js"/>
 
 <script>
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
+Components.classes["@mozilla.org/consoleservice;1"].
+           getService(Components.interfaces.nsIConsoleService).reset();
+expectedConsoleMessages.push("Error parsing template: <where> element is missing a rel attribute");
+
 var testid ="where - no rel";
 var queryType = "rdf";
 var isTreeBuilder = false;
 var needsOpen = false;
 var notWorkingYet = false;
 var notWorkingYetDynamic = false;
 var expectedOutput =
 <output>
--- a/content/xul/templates/tests/chrome/test_tmpl_wherenosubject.xul
+++ b/content/xul/templates/tests/chrome/test_tmpl_wherenosubject.xul
@@ -17,16 +17,20 @@
   <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
 
 <script src="templates_shared.js"/>
 
 <script>
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
+Components.classes["@mozilla.org/consoleservice;1"].
+           getService(Components.interfaces.nsIConsoleService).reset();
+expectedConsoleMessages.push("Error parsing template: <where> element is missing a subject attribute");
+
 var testid ="where - no subject";
 var queryType = "rdf";
 var isTreeBuilder = false;
 var needsOpen = false;
 var notWorkingYet = false;
 var notWorkingYetDynamic = false;
 var expectedOutput =
 <output>
--- a/content/xul/templates/tests/chrome/test_tmpl_wherenovalue.xul
+++ b/content/xul/templates/tests/chrome/test_tmpl_wherenovalue.xul
@@ -17,16 +17,20 @@
   <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
 
 <script src="templates_shared.js"/>
 
 <script>
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
+Components.classes["@mozilla.org/consoleservice;1"].
+           getService(Components.interfaces.nsIConsoleService).reset();
+expectedConsoleMessages.push("Error parsing template: <where> element is missing a value attribute");
+
 var testid ="where - no value";
 var queryType = "rdf";
 var isTreeBuilder = false;
 var needsOpen = false;
 var notWorkingYet = false;
 var notWorkingYetDynamic = false;
 var expectedOutput =
 <output>
--- a/content/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginrule.xul
+++ b/content/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginrule.xul
@@ -2,17 +2,17 @@
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
 
 <!--
   xml query with binding in rule
 -->
 
 <window title="XUL Template Tests" width="500" height="600"
-        onload="test_template();"
+        onload="expectLoggedMessages(); test_template();"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript"
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
 
@@ -29,21 +29,33 @@ var needsOpen = false;
 var notWorkingYet = false;
 var notWorkingYetDynamic = false;
 var expectedOutput =
 <output>
   <label anyid="true" container="true" empty="true" value="Class Reptiles"/>
   <label anyid="true" container="true" empty="true" value="Class Birds"/>
 </output>;
 
+Components.classes["@mozilla.org/consoleservice;1"].
+           getService(Components.interfaces.nsIConsoleService).reset();
+
+function expectLoggedMessages()
+{
+  // check log to ensure that two rows have been added
+  var initialNumber = Number(document.getElementById("root").firstChild.nextSibling.id.substring(3));
+  expectConsoleMessage('', 'row' + initialNumber, true, true, '1 matching rule 1');
+  expectConsoleMessage('', 'row' + (initialNumber + 1), true, true, '1 matching rule 1');
+}
+
 var changes = [];
 ]]>
 </script>
 
-<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref=".">
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+      flags="logging" datasources="animals.xml" querytype="xml" ref=".">
 <template>
 <query expr="class/name"/>
 <rule id="rule">
 <binding subject="?" predicate="concat('Class ', text())" object="?text"/>
 <action>
 <label uri="?" value="?text"/>
 </action>
 </rule>